diff --git a/.craft.yml b/.craft.yml
index 5efdae48aa01..e8bbfaf3b3b5 100644
--- a/.craft.yml
+++ b/.craft.yml
@@ -88,6 +88,9 @@ targets:
- 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
@@ -180,3 +183,5 @@ targets:
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/.github/workflows/build.yml b/.github/workflows/build.yml
index 84c26af7e9b1..3191f92adefb 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -356,7 +356,7 @@ jobs:
- name: Pack
run: yarn build:tarball
- name: Archive artifacts
- uses: actions/upload-artifact@v3.1.2
+ uses: actions/upload-artifact@v3.1.3
with:
name: ${{ github.sha }}
path: |
@@ -379,7 +379,7 @@ jobs:
- name: Set up Node
uses: actions/setup-node@v3
with:
- node-version: ${{ env.DEFAULT_NODE_VERSION }}
+ node-version-file: 'package.json'
- name: Restore caches
uses: ./.github/actions/restore-cache
env:
@@ -406,7 +406,7 @@ jobs:
- name: Set up Node
uses: actions/setup-node@v3
with:
- node-version: ${{ matrix.node }}
+ node-version-file: 'package.json'
- name: Set up Bun
uses: oven-sh/setup-bun@v1
- name: Restore caches
@@ -419,6 +419,38 @@ jobs:
- name: Compute test coverage
uses: codecov/codecov-action@v3
+ job_deno_unit_tests:
+ name: Deno 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@v3
+ with:
+ node-version-file: 'package.json'
+ - name: Set up Deno
+ uses: denoland/setup-deno@v1.1.3
+ with:
+ deno-version: v1.37.1
+ - 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@v3
+
job_node_unit_tests:
name: Node (${{ matrix.node }}) Unit Tests
needs: [job_get_metadata, job_build]
@@ -823,6 +855,7 @@ jobs:
'standard-frontend-react-tracing-import',
'sveltekit',
'generic-ts3.8',
+ 'node-experimental-fastify-app',
]
build-command:
- false
@@ -894,6 +927,7 @@ jobs:
job_browser_build_tests,
job_browser_unit_tests,
job_bun_unit_tests,
+ job_deno_unit_tests,
job_node_unit_tests,
job_nextjs_integration_test,
job_node_integration_tests,
@@ -951,7 +985,7 @@ jobs:
GITHUB_TOKEN: ${{ github.token }}
- name: Upload results
- uses: actions/upload-artifact@v3.1.2
+ uses: actions/upload-artifact@v3.1.3
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
with:
name: ${{ steps.process.outputs.artifactName }}
diff --git a/.gitignore b/.gitignore
index 777b23658572..d6eee47e4eed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,6 +21,8 @@ jest/transformers/*.js
# node tarballs
packages/*/sentry-*.tgz
.nxcache
+# The Deno types are downloaded before building
+packages/deno/lib.deno.d.ts
# logs
yarn-error.log
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index da74f03528af..3ad96b1733d5 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -4,6 +4,7 @@
"recommendations": [
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
- "augustocdias.tasks-shell-input"
- ],
+ "augustocdias.tasks-shell-input",
+ "denoland.vscode-deno"
+ ]
}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index d3c8a08448c6..96bd2dfb42b9 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -28,5 +28,6 @@
{
"mode": "auto"
}
- ]
+ ],
+ "deno.enablePaths": ["packages/deno/test"]
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 70c9c3702fd2..58bd0ab2a355 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,67 @@
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
+## 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
diff --git a/package.json b/package.json
index 6ae7e4f1d2cf..b5cbf9ddc38e 100644
--- a/package.json
+++ b/package.json
@@ -27,23 +27,25 @@
"postpublish": "lerna run --stream --concurrency 1 postpublish",
"test": "lerna run --ignore \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests,overhead-metrics}\" test",
"test:unit": "lerna run --ignore \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests,overhead-metrics}\" test:unit",
- "test-ci-browser": "lerna run test --ignore \"@sentry/{bun,node,node-experimental,opentelemetry-node,serverless,nextjs,remix,gatsby,sveltekit,vercel-edge}\" --ignore \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests,overhead-metrics}\"",
+ "test-ci-browser": "lerna run test --ignore \"@sentry/{bun,deno,node,node-experimental,opentelemetry-node,serverless,nextjs,remix,gatsby,sveltekit,vercel-edge}\" --ignore \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests,overhead-metrics}\"",
"test-ci-node": "ts-node ./scripts/node-unit-tests.ts",
"test-ci-bun": "lerna run test --scope @sentry/bun",
"test:update-snapshots": "lerna run test:update-snapshots",
"yalc:publish": "lerna run yalc:publish"
},
"volta": {
- "node": "16.19.0",
+ "node": "18.17.0",
"yarn": "1.22.19"
},
"workspaces": [
"packages/angular",
"packages/angular-ivy",
+ "packages/astro",
"packages/browser",
"packages/browser-integration-tests",
"packages/bun",
"packages/core",
+ "packages/deno",
"packages/e2e-tests",
"packages/ember",
"packages/eslint-config-sdk",
@@ -126,7 +128,8 @@
"yalc": "^1.0.0-pre.53"
},
"resolutions": {
- "**/agent-base": "5"
+ "**/agent-base": "5",
+ "**/terser/source-map": "0.7.4"
},
"version": "0.0.0",
"name": "sentry-javascript"
diff --git a/packages/angular-ivy/src/sdk.ts b/packages/angular-ivy/src/sdk.ts
index fcbbbce399d0..b2dc1f5f34d8 100644
--- a/packages/angular-ivy/src/sdk.ts
+++ b/packages/angular-ivy/src/sdk.ts
@@ -1,6 +1,7 @@
import { VERSION } from '@angular/core';
import type { BrowserOptions } from '@sentry/browser';
import { defaultIntegrations, init as browserInit, SDK_VERSION, setContext } from '@sentry/browser';
+import type { SdkMetadata } from '@sentry/types';
import { logger } from '@sentry/utils';
import { IS_DEBUG_BUILD } from './flags';
@@ -9,8 +10,21 @@ import { IS_DEBUG_BUILD } from './flags';
* Inits the Angular SDK
*/
export function init(options: BrowserOptions): void {
- options._metadata = options._metadata || {};
- options._metadata.sdk = {
+ const opts = {
+ _metadata: {} as SdkMetadata,
+ // Filter out TryCatch integration as it interferes with our Angular `ErrorHandler`:
+ // TryCatch would catch certain errors before they reach the `ErrorHandler` and thus provide a
+ // lower fidelity error than what `SentryErrorHandler` (see errorhandler.ts) would provide.
+ // see:
+ // - https://github.com/getsentry/sentry-javascript/issues/5417#issuecomment-1453407097
+ // - https://github.com/getsentry/sentry-javascript/issues/2744
+ defaultIntegrations: defaultIntegrations.filter(integration => {
+ return integration.name !== 'TryCatch';
+ }),
+ ...options,
+ };
+
+ opts._metadata.sdk = opts._metadata.sdk || {
name: 'sentry.javascript.angular-ivy',
packages: [
{
@@ -21,20 +35,8 @@ export function init(options: BrowserOptions): void {
version: SDK_VERSION,
};
- // Filter out TryCatch integration as it interferes with our Angular `ErrorHandler`:
- // TryCatch would catch certain errors before they reach the `ErrorHandler` and thus provide a
- // lower fidelity error than what `SentryErrorHandler` (see errorhandler.ts) would provide.
- // see:
- // - https://github.com/getsentry/sentry-javascript/issues/5417#issuecomment-1453407097
- // - https://github.com/getsentry/sentry-javascript/issues/2744
- if (options.defaultIntegrations === undefined) {
- options.defaultIntegrations = defaultIntegrations.filter(integration => {
- return integration.name !== 'TryCatch';
- });
- }
-
checkAndSetAngularVersion();
- browserInit(options);
+ browserInit(opts);
}
function checkAndSetAngularVersion(): void {
diff --git a/packages/angular/src/sdk.ts b/packages/angular/src/sdk.ts
index e50cece043d0..975ec24e38d3 100755
--- a/packages/angular/src/sdk.ts
+++ b/packages/angular/src/sdk.ts
@@ -1,6 +1,7 @@
import { VERSION } from '@angular/core';
import type { BrowserOptions } from '@sentry/browser';
import { defaultIntegrations, init as browserInit, SDK_VERSION, setContext } from '@sentry/browser';
+import type { SdkMetadata } from '@sentry/types';
import { logger } from '@sentry/utils';
import { IS_DEBUG_BUILD } from './flags';
@@ -9,8 +10,21 @@ import { IS_DEBUG_BUILD } from './flags';
* Inits the Angular SDK
*/
export function init(options: BrowserOptions): void {
- options._metadata = options._metadata || {};
- options._metadata.sdk = {
+ const opts = {
+ _metadata: {} as SdkMetadata,
+ // Filter out TryCatch integration as it interferes with our Angular `ErrorHandler`:
+ // TryCatch would catch certain errors before they reach the `ErrorHandler` and thus provide a
+ // lower fidelity error than what `SentryErrorHandler` (see errorhandler.ts) would provide.
+ // see:
+ // - https://github.com/getsentry/sentry-javascript/issues/5417#issuecomment-1453407097
+ // - https://github.com/getsentry/sentry-javascript/issues/2744
+ defaultIntegrations: defaultIntegrations.filter(integration => {
+ return integration.name !== 'TryCatch';
+ }),
+ ...options,
+ };
+
+ opts._metadata.sdk = opts._metadata.sdk || {
name: 'sentry.javascript.angular',
packages: [
{
@@ -21,20 +35,8 @@ export function init(options: BrowserOptions): void {
version: SDK_VERSION,
};
- // Filter out TryCatch integration as it interferes with our Angular `ErrorHandler`:
- // TryCatch would catch certain errors before they reach the `ErrorHandler` and thus provide a
- // lower fidelity error than what `SentryErrorHandler` (see errorhandler.ts) would provide.
- // see:
- // - https://github.com/getsentry/sentry-javascript/issues/5417#issuecomment-1453407097
- // - https://github.com/getsentry/sentry-javascript/issues/2744
- if (options.defaultIntegrations === undefined) {
- options.defaultIntegrations = defaultIntegrations.filter(integration => {
- return integration.name !== 'TryCatch';
- });
- }
-
checkAndSetAngularVersion();
- browserInit(options);
+ browserInit(opts);
}
function checkAndSetAngularVersion(): void {
diff --git a/packages/astro/.eslintrc.cjs b/packages/astro/.eslintrc.cjs
new file mode 100644
index 000000000000..c706032aaf35
--- /dev/null
+++ b/packages/astro/.eslintrc.cjs
@@ -0,0 +1,22 @@
+module.exports = {
+ env: {
+ browser: true,
+ node: true,
+ },
+ extends: ['../../.eslintrc.js'],
+ overrides: [
+ {
+ files: ['vite.config.ts'],
+ parserOptions: {
+ project: ['tsconfig.test.json'],
+ },
+ },
+ {
+ files: ['src/integration/**', 'src/server/**'],
+ rules: {
+ '@sentry-internal/sdk/no-optional-chaining': 'off',
+ '@sentry-internal/sdk/no-nullish-coalescing': 'off',
+ },
+ },
+ ],
+};
diff --git a/packages/astro/.npmignore b/packages/astro/.npmignore
new file mode 100644
index 000000000000..ded80d725803
--- /dev/null
+++ b/packages/astro/.npmignore
@@ -0,0 +1,10 @@
+# The paths in this file are specified so that they align with the file structure in `./build` after this file is copied
+# into it by the prepack script `scripts/prepack.ts`.
+
+*
+
+!/cjs/**/*
+!/esm/**/*
+!/types/**/*
+!/types-ts3.8/**/*
+!/integration/**/*
diff --git a/packages/astro/LICENSE b/packages/astro/LICENSE
new file mode 100644
index 000000000000..d11896ba1181
--- /dev/null
+++ b/packages/astro/LICENSE
@@ -0,0 +1,14 @@
+Copyright (c) 2023 Sentry (https://sentry.io) and individual contributors. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
+persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
+Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/packages/astro/README.md b/packages/astro/README.md
new file mode 100644
index 000000000000..a2738298a67c
--- /dev/null
+++ b/packages/astro/README.md
@@ -0,0 +1,125 @@
+
+
+
+
+
+
+# Official Sentry SDK for Astro
+
+[](https://www.npmjs.com/package/@sentry/astro)
+[](https://www.npmjs.com/package/@sentry/astro)
+[](https://www.npmjs.com/package/@sentry/astro)
+
+
+
+## Experimental Note
+
+This SDK is experimental and in Alpha state. Breaking changes can occurr at any time.
+If you have feedback or encounter any bugs, feel free to [open an issue](https://github.com/getsentry/sentry-javascript/issues/new/choose).
+
+## General
+
+This package is a wrapper around `@sentry/node` for the server and `@sentry/browser` for the client side.
+
+## Installation and Setup
+
+### 1. Registering the Sentry Astro integration:
+
+Add the `sentryAstro` integration to your `astro.config.mjs` file:
+
+```javascript
+import { sentryAstro } from "@sentry/astro/integration";
+
+export default defineConfig({
+ // Rest of your Astro project config
+ integrations: [
+ sentryAstro({
+ dsn: '__DSN__',
+ }),
+ ],
+})
+```
+
+This is the easiest way to configure Sentry in an Astro project.
+You can pass a few additional options to `sentryAstro` but the SDK comes preconfigured in an opinionated way.
+If you want to fully customize your SDK setup, you can do so, too:
+
+### 2. [Optional] Uploading Source Maps
+
+To upload source maps to Sentry, simply add the `project` and `authToken` options to `sentryAstro`:
+
+```js
+// astro.config.mjs
+import { sentryAstro } from "@sentry/astro/integration";
+
+export default defineConfig({
+ // Rest of your Astro project config
+ integrations: [
+ sentryAstro({
+ dsn: '__DSN__',
+ project: 'your-project-slug',
+ authToken: import.meta.env('SENTRY_AUTH_TOKEN'),
+ }),
+ ],
+})
+```
+
+You can also define these values as environment variables in e.g. a `.env` file
+or in you CI configuration:
+
+```sh
+SENTRY_PROJECT="your-project"
+SENTRY_AUTH_TOKEN="your-token"
+```
+
+Follow [this guide](https://docs.sentry.io/product/accounts/auth-tokens/#organization-auth-tokens) to create an auth token.
+
+### 3. [Optional] Advanced Configuration
+
+To fully customize and configure Sentry in an Astro project, follow step 1 and in addition,
+add a `sentry.client.config.(js|ts)` and `sentry.server.config(js|ts)` file to the root directory of your project.
+Inside these files, you can call `Sentry.init()` and use the full range of Sentry options.
+
+Configuring the client SDK:
+
+```js
+// sentry.client.config.ts or sentry.server.config.ts
+import * as Sentry from "@sentry/astro";
+
+Sentry.init({
+ dsn: "__DSN__",
+ beforeSend(event) {
+ console.log("Sending event on the client");
+ return event;
+ },
+ tracesSampler: () => {/* ... */}
+});
+```
+
+**Important**: Once you created a sentry config file, the SDK options passed to `sentryAstro` will be ignored for the respective runtime. You can also only define create of the two files.
+
+#### 3.1 Custom file location
+
+If you want to move the `sentry.*.config` files to another location,
+you can specify the file path, relative to the project root, in `sentryAstro`:
+
+```js
+// astro.config.mjs
+import { sentryAstro } from "@sentry/astro/integration";
+
+export default defineConfig({
+ // Rest of your Astro project config
+ integrations: [
+ sentryAstro({
+ dsn: '__DSN__',
+ clientInitPath: '.config/sentry.client.init.js',
+ serverInitPath: '.config/sentry.server.init.js',
+ }),
+ ],
+})
+```
diff --git a/packages/astro/package.json b/packages/astro/package.json
new file mode 100644
index 000000000000..c3c193a3a6d1
--- /dev/null
+++ b/packages/astro/package.json
@@ -0,0 +1,77 @@
+{
+ "name": "@sentry/astro",
+ "version": "7.73.0",
+ "description": "Official Sentry SDK for Astro",
+ "repository": "git://github.com/getsentry/sentry-javascript.git",
+ "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/astro",
+ "keywords": [
+ "withastro",
+ "astro-component",
+ "sentry",
+ "apm"
+ ],
+ "author": "Sentry",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.14.1"
+ },
+ "type": "module",
+ "main": "build/cjs/index.client.js",
+ "module": "build/esm/index.server.js",
+ "browser": "build/esm/index.client.js",
+ "types": "build/types/index.types.d.ts",
+ "exports": {
+ ".": {
+ "node": "./build/esm/index.server.js",
+ "browser": "./build/esm/index.client.js",
+ "import": "./build/esm/index.client.js",
+ "require": "./build/cjs/index.server.js",
+ "types": "./build/types/index.types.d.ts"
+ }
+ },
+ "publishConfig": {
+ "access": "public"
+ },
+ "peerDependencies": {
+ "astro": "3.x"
+ },
+ "dependencies": {
+ "@sentry/browser": "7.73.0",
+ "@sentry/node": "7.73.0",
+ "@sentry/core": "7.73.0",
+ "@sentry/utils": "7.73.0",
+ "@sentry/types": "7.73.0",
+ "@sentry/vite-plugin": "^2.8.0"
+ },
+ "devDependencies": {
+ "astro": "^3.2.3",
+ "rollup": "^3.20.2",
+ "vite": "4.0.5"
+ },
+ "scripts": {
+ "build": "run-p build:transpile build:types",
+ "build:dev": "yarn build",
+ "build:transpile": "rollup -c rollup.npm.config.js --bundleConfigAsCjs",
+ "build:types": "tsc -p tsconfig.types.json",
+ "build:watch": "run-p build:transpile:watch build:types:watch",
+ "build:dev:watch": "yarn build:watch",
+ "build:transpile:watch": "rollup -c rollup.npm.config.js --bundleConfigAsCjs --watch",
+ "build:types:watch": "tsc -p tsconfig.types.json --watch",
+ "build:tarball": "ts-node ../../scripts/prepack.ts && npm pack ./build",
+ "circularDepCheck": "madge --circular src/index.client.ts && madge --circular src/index.server.ts && madge --circular src/index.types.ts",
+ "clean": "rimraf build coverage sentry-astro-*.tgz",
+ "fix": "run-s fix:eslint fix:prettier",
+ "fix:eslint": "eslint . --format stylish --fix",
+ "fix:prettier": "prettier --write \"{src,test,scripts}/**/**.ts\"",
+ "lint": "run-s lint:prettier lint:eslint",
+ "lint:eslint": "eslint . --format stylish",
+ "lint:prettier": "prettier --check \"{src,test,scripts}/**/**.ts\"",
+ "test": "yarn test:unit",
+ "test:unit": "vitest run",
+ "test:watch": "vitest --watch",
+ "yalc:publish": "ts-node ../../scripts/prepack.ts && yalc publish build --push"
+ },
+ "volta": {
+ "extends": "../../package.json"
+ }
+}
diff --git a/packages/astro/rollup.npm.config.js b/packages/astro/rollup.npm.config.js
new file mode 100644
index 000000000000..06dd0b3e4ec1
--- /dev/null
+++ b/packages/astro/rollup.npm.config.js
@@ -0,0 +1,17 @@
+import { makeBaseNPMConfig, makeNPMConfigVariants } from '../../rollup/index.js';
+
+const variants = makeNPMConfigVariants(
+ makeBaseNPMConfig({
+ entrypoints: ['src/index.server.ts', 'src/index.client.ts'],
+ packageSpecificConfig: {
+ output: {
+ dynamicImportInCjs: true,
+ exports: 'named',
+ },
+ },
+ // Astro is Node 18+ no need to add polyfills
+ addPolyfills: false,
+ }),
+);
+
+export default variants;
diff --git a/packages/astro/scripts/syncIntegration.ts b/packages/astro/scripts/syncIntegration.ts
new file mode 100644
index 000000000000..006d9b3237ac
--- /dev/null
+++ b/packages/astro/scripts/syncIntegration.ts
@@ -0,0 +1,20 @@
+/* eslint-disable no-console */
+
+import * as fse from 'fs-extra';
+import * as path from 'path';
+
+const buildDir = path.resolve('build');
+const srcIntegrationDir = path.resolve(path.join('src', 'integration'));
+const destIntegrationDir = path.resolve(path.join(buildDir, 'integration'));
+
+try {
+ fse.copySync(srcIntegrationDir, destIntegrationDir, {
+ filter: (src, _) => {
+ return !src.endsWith('.md');
+ },
+ });
+ console.log('\nCopied Astro integration to ./build/integration\n');
+} catch (e) {
+ console.error('\nError while copying integration to build dir:');
+ console.error(e);
+}
diff --git a/packages/astro/src/client/sdk.ts b/packages/astro/src/client/sdk.ts
new file mode 100644
index 000000000000..aa32e9dcc095
--- /dev/null
+++ b/packages/astro/src/client/sdk.ts
@@ -0,0 +1,42 @@
+import type { BrowserOptions } from '@sentry/browser';
+import { BrowserTracing, init as initBrowserSdk } from '@sentry/browser';
+import { configureScope, hasTracingEnabled } from '@sentry/core';
+import { addOrUpdateIntegration } from '@sentry/utils';
+
+import { applySdkMetadata } from '../common/metadata';
+
+// Treeshakable guard to remove all code related to tracing
+declare const __SENTRY_TRACING__: boolean;
+
+/**
+ * Initialize the client side of the Sentry Astro SDK.
+ *
+ * @param options Configuration options for the SDK.
+ */
+export function init(options: BrowserOptions): void {
+ applySdkMetadata(options, ['astro', 'browser']);
+
+ addClientIntegrations(options);
+
+ initBrowserSdk(options);
+
+ configureScope(scope => {
+ scope.setTag('runtime', 'browser');
+ });
+}
+
+function addClientIntegrations(options: BrowserOptions): void {
+ let integrations = options.integrations || [];
+
+ // This evaluates to true unless __SENTRY_TRACING__ is text-replaced with "false",
+ // in which case everything inside will get treeshaken away
+ if (typeof __SENTRY_TRACING__ === 'undefined' || __SENTRY_TRACING__) {
+ if (hasTracingEnabled(options)) {
+ const defaultBrowserTracingIntegration = new BrowserTracing({});
+
+ integrations = addOrUpdateIntegration(defaultBrowserTracingIntegration, integrations);
+ }
+ }
+
+ options.integrations = integrations;
+}
diff --git a/packages/astro/src/common/metadata.ts b/packages/astro/src/common/metadata.ts
new file mode 100644
index 000000000000..ddd53f27362a
--- /dev/null
+++ b/packages/astro/src/common/metadata.ts
@@ -0,0 +1,31 @@
+import { SDK_VERSION } from '@sentry/core';
+import type { Options, SdkInfo } from '@sentry/types';
+
+const PACKAGE_NAME_PREFIX = 'npm:@sentry/';
+
+/**
+ * A builder for the SDK metadata in the options for the SDK initialization.
+ *
+ * Note: This function is identical to `buildMetadata` in Remix and NextJS and SvelteKit.
+ * We don't extract it for bundle size reasons.
+ * @see https://github.com/getsentry/sentry-javascript/pull/7404
+ * @see https://github.com/getsentry/sentry-javascript/pull/4196
+ *
+ * If you make changes to this function consider updating the others as well.
+ *
+ * @param options SDK options object that gets mutated
+ * @param names list of package names
+ */
+export function applySdkMetadata(options: Options, names: string[]): void {
+ options._metadata = options._metadata || {};
+ options._metadata.sdk =
+ options._metadata.sdk ||
+ ({
+ name: 'sentry.javascript.astro',
+ packages: names.map(name => ({
+ name: `${PACKAGE_NAME_PREFIX}${name}`,
+ version: SDK_VERSION,
+ })),
+ version: SDK_VERSION,
+ } as SdkInfo);
+}
diff --git a/packages/astro/src/index.client.ts b/packages/astro/src/index.client.ts
new file mode 100644
index 000000000000..2b85c05c3af1
--- /dev/null
+++ b/packages/astro/src/index.client.ts
@@ -0,0 +1,3 @@
+export * from '@sentry/browser';
+
+export { init } from './client/sdk';
diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts
new file mode 100644
index 000000000000..7a28bf907d48
--- /dev/null
+++ b/packages/astro/src/index.server.ts
@@ -0,0 +1,65 @@
+// Node SDK exports
+// Unfortunately, we cannot `export * from '@sentry/node'` because in prod builds,
+// Vite puts these exports into a `default` property (Sentry.default) rather than
+// on the top - level namespace.
+
+import { sentryAstro } from './integration';
+
+// Hence, we export everything from the Node SDK explicitly:
+export {
+ addGlobalEventProcessor,
+ addBreadcrumb,
+ captureException,
+ captureEvent,
+ captureMessage,
+ captureCheckIn,
+ configureScope,
+ createTransport,
+ extractTraceparentData,
+ getActiveTransaction,
+ getHubFromCarrier,
+ getCurrentHub,
+ Hub,
+ makeMain,
+ Scope,
+ startTransaction,
+ SDK_VERSION,
+ setContext,
+ setExtra,
+ setExtras,
+ setTag,
+ setTags,
+ setUser,
+ spanStatusfromHttpCode,
+ trace,
+ withScope,
+ autoDiscoverNodePerformanceMonitoringIntegrations,
+ makeNodeTransport,
+ defaultIntegrations,
+ defaultStackParser,
+ lastEventId,
+ flush,
+ close,
+ getSentryRelease,
+ addRequestDataToEvent,
+ DEFAULT_USER_INCLUDES,
+ extractRequestData,
+ deepReadDirSync,
+ Integrations,
+ Handlers,
+ setMeasurement,
+ getActiveSpan,
+ startSpan,
+ // eslint-disable-next-line deprecation/deprecation
+ startActiveSpan,
+ startInactiveSpan,
+ startSpanManual,
+ continueTrace,
+} from '@sentry/node';
+
+// We can still leave this for the carrier init and type exports
+export * from '@sentry/node';
+
+export { init } from './server/sdk';
+
+export default sentryAstro;
diff --git a/packages/astro/src/index.types.ts b/packages/astro/src/index.types.ts
new file mode 100644
index 000000000000..e8ff7457f597
--- /dev/null
+++ b/packages/astro/src/index.types.ts
@@ -0,0 +1,25 @@
+/* eslint-disable import/export */
+
+// We export everything from both the client part of the SDK and from the server part.
+// Some of the exports collide, which is not allowed, unless we redifine the colliding
+// exports in this file - which we do below.
+export * from './index.client';
+export * from './index.server';
+
+import type { Integration, Options, StackParser } from '@sentry/types';
+
+import type * as clientSdk from './index.client';
+import type * as serverSdk from './index.server';
+
+/** Initializes Sentry Astro SDK */
+export declare function init(options: Options | clientSdk.BrowserOptions | serverSdk.NodeOptions): void;
+
+// We export a merged Integrations object so that users can (at least typing-wise) use all integrations everywhere.
+export declare const Integrations: typeof clientSdk.Integrations & typeof serverSdk.Integrations;
+
+export declare const defaultIntegrations: Integration[];
+export declare const defaultStackParser: StackParser;
+
+export declare function close(timeout?: number | undefined): PromiseLike;
+export declare function flush(timeout?: number | undefined): PromiseLike;
+export declare function lastEventId(): string | undefined;
diff --git a/packages/astro/src/integration/index.ts b/packages/astro/src/integration/index.ts
new file mode 100644
index 000000000000..0432fb8108d3
--- /dev/null
+++ b/packages/astro/src/integration/index.ts
@@ -0,0 +1,81 @@
+/* eslint-disable no-console */
+import { sentryVitePlugin } from '@sentry/vite-plugin';
+import type { AstroIntegration } from 'astro';
+import * as fs from 'fs';
+import * as path from 'path';
+
+import { buildClientSnippet, buildSdkInitFileImportSnippet, buildServerSnippet } from './snippets';
+import type { SentryOptions } from './types';
+
+const PKG_NAME = '@sentry/astro';
+
+export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => {
+ return {
+ name: PKG_NAME,
+ hooks: {
+ 'astro:config:setup': async ({ updateConfig, injectScript }) => {
+ // The third param here enables loading of all env vars, regardless of prefix
+ // see: https://main.vitejs.dev/config/#using-environment-variables-in-config
+
+ // TODO: Ideally, we want to load the environment with vite like this:
+ // const env = loadEnv('production', process.cwd(), '');
+ // However, this currently throws a build error.
+ // Will revisit this later.
+ const env = process.env;
+
+ const uploadOptions = options.sourceMapsUploadOptions || {};
+
+ const shouldUploadSourcemaps = uploadOptions?.enabled ?? true;
+ const authToken = uploadOptions.authToken || env.SENTRY_AUTH_TOKEN;
+
+ if (shouldUploadSourcemaps && authToken) {
+ updateConfig({
+ vite: {
+ build: {
+ sourcemap: true,
+ },
+ plugins: [
+ sentryVitePlugin({
+ org: uploadOptions.org ?? env.SENTRY_ORG,
+ project: uploadOptions.project ?? env.SENTRY_PROJECT,
+ authToken: uploadOptions.authToken ?? env.SENTRY_AUTH_TOKEN,
+ telemetry: uploadOptions.telemetry ?? true,
+ }),
+ ],
+ },
+ });
+ }
+
+ const pathToClientInit = options.clientInitPath
+ ? path.resolve(options.clientInitPath)
+ : findDefaultSdkInitFile('client');
+ const pathToServerInit = options.serverInitPath
+ ? path.resolve(options.serverInitPath)
+ : findDefaultSdkInitFile('server');
+
+ if (pathToClientInit) {
+ options.debug && console.log(`[sentry-astro] Using ${pathToClientInit} for client init.`);
+ injectScript('page', buildSdkInitFileImportSnippet(pathToClientInit));
+ } else {
+ options.debug && console.log('[sentry-astro] Using default client init.');
+ injectScript('page', buildClientSnippet(options || {}));
+ }
+
+ if (pathToServerInit) {
+ options.debug && console.log(`[sentry-astro] Using ${pathToServerInit} for server init.`);
+ injectScript('page-ssr', buildSdkInitFileImportSnippet(pathToServerInit));
+ } else {
+ options.debug && console.log('[sentry-astro] Using default server init.');
+ injectScript('page-ssr', buildServerSnippet(options || {}));
+ }
+ },
+ },
+ };
+};
+
+function findDefaultSdkInitFile(type: 'server' | 'client'): string | undefined {
+ const fileExtensions = ['ts', 'js', 'tsx', 'jsx', 'mjs', 'cjs', 'mts'];
+ return fileExtensions
+ .map(ext => path.resolve(path.join(process.cwd(), `sentry.${type}.config.${ext}`)))
+ .find(filename => fs.existsSync(filename));
+}
diff --git a/packages/astro/src/integration/snippets.ts b/packages/astro/src/integration/snippets.ts
new file mode 100644
index 000000000000..28d03ea443eb
--- /dev/null
+++ b/packages/astro/src/integration/snippets.ts
@@ -0,0 +1,45 @@
+import type { SentryOptions } from './types';
+
+/**
+ * Creates a snippet that imports a Sentry.init file.
+ */
+export function buildSdkInitFileImportSnippet(filePath: string): string {
+ return `import "${filePath}";`;
+}
+
+/**
+ * Creates a snippet that initializes Sentry on the client by choosing
+ * default options.
+ */
+export function buildClientSnippet(options: SentryOptions): string {
+ return `import * as Sentry from "@sentry/astro";
+
+Sentry.init({
+ ${buildCommonInitOptions(options)}
+ integrations: [new Sentry.BrowserTracing(), new Sentry.Replay()],
+ replaysSessionSampleRate: ${options.replaysSessionSampleRate ?? 0.1},
+ replaysOnErrorSampleRate: ${options.replaysOnErrorSampleRate ?? 1.0},
+});`;
+}
+
+/**
+ * Creates a snippet that initializes Sentry on the server by choosing
+ * default options.
+ */
+export function buildServerSnippet(options: SentryOptions): string {
+ return `import * as Sentry from "@sentry/astro";
+
+Sentry.init({
+ ${buildCommonInitOptions(options)}
+});`;
+}
+
+const buildCommonInitOptions = (options: SentryOptions): string => `dsn: ${
+ options.dsn ? JSON.stringify(options.dsn) : 'import.meta.env.PUBLIC_SENTRY_DSN'
+},
+ debug: ${options.debug ? true : false},
+ environment: ${options.environment ? JSON.stringify(options.environment) : 'import.meta.env.PUBLIC_VERCEL_ENV'},
+ release: ${options.release ? JSON.stringify(options.release) : 'import.meta.env.PUBLIC_VERCEL_GIT_COMMIT_SHA'},
+ tracesSampleRate: ${options.tracesSampleRate ?? 1.0},${
+ options.sampleRate ? `\n sampleRate: ${options.sampleRate},` : ''
+}`;
diff --git a/packages/astro/src/integration/types.ts b/packages/astro/src/integration/types.ts
new file mode 100644
index 000000000000..d54c948401ca
--- /dev/null
+++ b/packages/astro/src/integration/types.ts
@@ -0,0 +1,86 @@
+import type { BrowserOptions } from '@sentry/browser';
+import type { Options } from '@sentry/types';
+
+type SdkInitPaths = {
+ /**
+ * Path to a `sentry.client.config.(js|ts)` file that contains a `Sentry.init` call.
+ *
+ * If this option is not specified, the default location (`/sentry.client.config.(js|ts)`)
+ * will be used to look up the config file.
+ * If there is no file at the default location either, the SDK will initalize with the options
+ * specified in the `sentryAstro` integration or with default options.
+ */
+ clientInitPath?: string;
+
+ /**
+ * Path to a `sentry.server.config.(js|ts)` file that contains a `Sentry.init` call.
+ *
+ * If this option is not specified, the default location (`/sentry.server.config.(js|ts)`)
+ * will be used to look up the config file.
+ * If there is no file at the default location either, the SDK will initalize with the options
+ * specified in the `sentryAstro` integration or with default options.
+ */
+ serverInitPath?: string;
+};
+
+type SourceMapsOptions = {
+ /**
+ * Options for the Sentry Vite plugin to customize the source maps upload process.
+ *
+ * These options are always read from the `sentryAstro` integration.
+ * Do not define them in the `sentry.client.config.(js|ts)` or `sentry.server.config.(js|ts)` files.
+ */
+ sourceMapsUploadOptions?: {
+ /**
+ * If this flag is `true`, and an auth token is detected, the Sentry integration will
+ * automatically generate and upload source maps to Sentry during a production build.
+ *
+ * @default true
+ */
+ enabled?: boolean;
+
+ /**
+ * The auth token to use when uploading source maps to Sentry.
+ *
+ * Instead of specifying this option, you can also set the `SENTRY_AUTH_TOKEN` environment variable.
+ *
+ * To create an auth token, follow this guide:
+ * @see https://docs.sentry.io/product/accounts/auth-tokens/#organization-auth-tokens
+ */
+ authToken?: string;
+
+ /**
+ * The organization slug of your Sentry organization.
+ * Instead of specifying this option, you can also set the `SENTRY_ORG` environment variable.
+ */
+ org?: string;
+
+ /**
+ * The project slug of your Sentry project.
+ * Instead of specifying this option, you can also set the `SENTRY_PROJECT` environment variable.
+ */
+ project?: string;
+
+ /**
+ * If this flag is `true`, the Sentry plugin will collect some telemetry data and send it to Sentry.
+ * It will not collect any sensitive or user-specific data.
+ *
+ * @default true
+ */
+ telemetry?: boolean;
+ };
+};
+
+/**
+ * A subset of Sentry SDK options that can be set via the `sentryAstro` integration.
+ * Some options (e.g. integrations) are set by default and cannot be changed here.
+ *
+ * If you want a more fine-grained control over the SDK, with all options,
+ * you can call Sentry.init in `sentry.client.config.(js|ts)` or `sentry.server.config.(js|ts)` files.
+ *
+ * If you specify a dedicated init file, the SDK options passed to `sentryAstro` will be ignored.
+ */
+export type SentryOptions = SdkInitPaths &
+ Pick &
+ Pick &
+ SourceMapsOptions;
diff --git a/packages/astro/src/server/sdk.ts b/packages/astro/src/server/sdk.ts
new file mode 100644
index 000000000000..8c867ca46fc2
--- /dev/null
+++ b/packages/astro/src/server/sdk.ts
@@ -0,0 +1,19 @@
+import { configureScope } from '@sentry/core';
+import type { NodeOptions } from '@sentry/node';
+import { init as initNodeSdk } from '@sentry/node';
+
+import { applySdkMetadata } from '../common/metadata';
+
+/**
+ *
+ * @param options
+ */
+export function init(options: NodeOptions): void {
+ applySdkMetadata(options, ['astro', 'node']);
+
+ initNodeSdk(options);
+
+ configureScope(scope => {
+ scope.setTag('runtime', 'node');
+ });
+}
diff --git a/packages/astro/test/client/sdk.test.ts b/packages/astro/test/client/sdk.test.ts
new file mode 100644
index 000000000000..74a4dc4562ef
--- /dev/null
+++ b/packages/astro/test/client/sdk.test.ts
@@ -0,0 +1,124 @@
+import type { BrowserClient } from '@sentry/browser';
+import * as SentryBrowser from '@sentry/browser';
+import { BrowserTracing, getCurrentHub, SDK_VERSION, WINDOW } from '@sentry/browser';
+import { vi } from 'vitest';
+
+import { init } from '../../../astro/src/client/sdk';
+
+const browserInit = vi.spyOn(SentryBrowser, 'init');
+
+describe('Sentry client SDK', () => {
+ describe('init', () => {
+ afterEach(() => {
+ vi.clearAllMocks();
+ WINDOW.__SENTRY__.hub = undefined;
+ });
+
+ it('adds Astro metadata to the SDK options', () => {
+ expect(browserInit).not.toHaveBeenCalled();
+
+ init({});
+
+ expect(browserInit).toHaveBeenCalledTimes(1);
+ expect(browserInit).toHaveBeenCalledWith(
+ expect.objectContaining({
+ _metadata: {
+ sdk: {
+ name: 'sentry.javascript.astro',
+ version: SDK_VERSION,
+ packages: [
+ { name: 'npm:@sentry/astro', version: SDK_VERSION },
+ { name: 'npm:@sentry/browser', version: SDK_VERSION },
+ ],
+ },
+ },
+ }),
+ );
+ });
+
+ it('sets the runtime tag on the scope', () => {
+ const currentScope = getCurrentHub().getScope();
+
+ // @ts-expect-error need access to protected _tags attribute
+ expect(currentScope._tags).toEqual({});
+
+ init({ dsn: 'https://public@dsn.ingest.sentry.io/1337' });
+
+ // @ts-expect-error need access to protected _tags attribute
+ expect(currentScope._tags).toEqual({ runtime: 'browser' });
+ });
+
+ describe('automatically adds integrations', () => {
+ it.each([
+ ['tracesSampleRate', { tracesSampleRate: 0 }],
+ ['tracesSampler', { tracesSampler: () => 1.0 }],
+ ['enableTracing', { enableTracing: true }],
+ ])('adds the BrowserTracing integration if tracing is enabled via %s', (_, tracingOptions) => {
+ init({
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ ...tracingOptions,
+ });
+
+ const integrationsToInit = browserInit.mock.calls[0][0]?.integrations;
+ const browserTracing = (getCurrentHub().getClient() as BrowserClient)?.getIntegrationById('BrowserTracing');
+
+ expect(integrationsToInit).toContainEqual(expect.objectContaining({ name: 'BrowserTracing' }));
+ expect(browserTracing).toBeDefined();
+ });
+
+ it.each([
+ ['enableTracing', { enableTracing: false }],
+ ['no tracing option set', {}],
+ ])("doesn't add the BrowserTracing integration if tracing is disabled via %s", (_, tracingOptions) => {
+ init({
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ ...tracingOptions,
+ });
+
+ const integrationsToInit = browserInit.mock.calls[0][0]?.integrations;
+ const browserTracing = (getCurrentHub().getClient() as BrowserClient)?.getIntegrationById('BrowserTracing');
+
+ expect(integrationsToInit).not.toContainEqual(expect.objectContaining({ name: 'BrowserTracing' }));
+ expect(browserTracing).toBeUndefined();
+ });
+
+ it("doesn't add the BrowserTracing integration if `__SENTRY_TRACING__` is set to false", () => {
+ globalThis.__SENTRY_TRACING__ = false;
+
+ init({
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ enableTracing: true,
+ });
+
+ const integrationsToInit = browserInit.mock.calls[0][0]?.integrations;
+ const browserTracing = (getCurrentHub().getClient() as BrowserClient)?.getIntegrationById('BrowserTracing');
+
+ expect(integrationsToInit).not.toContainEqual(expect.objectContaining({ name: 'BrowserTracing' }));
+ expect(browserTracing).toBeUndefined();
+
+ delete globalThis.__SENTRY_TRACING__;
+ });
+
+ it('Overrides the automatically default BrowserTracing instance with a a user-provided instance', () => {
+ init({
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ integrations: [new BrowserTracing({ finalTimeout: 10, startTransactionOnLocationChange: false })],
+ enableTracing: true,
+ });
+
+ const integrationsToInit = browserInit.mock.calls[0][0]?.integrations;
+
+ const browserTracing = (getCurrentHub().getClient() as BrowserClient)?.getIntegrationById(
+ 'BrowserTracing',
+ ) as BrowserTracing;
+ const options = browserTracing.options;
+
+ expect(integrationsToInit).toContainEqual(expect.objectContaining({ name: 'BrowserTracing' }));
+ expect(browserTracing).toBeDefined();
+
+ // This shows that the user-configured options are still here
+ expect(options.finalTimeout).toEqual(10);
+ });
+ });
+ });
+});
diff --git a/packages/astro/test/integration/index.test.ts b/packages/astro/test/integration/index.test.ts
new file mode 100644
index 000000000000..fe876023c9c4
--- /dev/null
+++ b/packages/astro/test/integration/index.test.ts
@@ -0,0 +1,103 @@
+import { vi } from 'vitest';
+
+import { sentryAstro } from '../../src/integration';
+
+const sentryVitePluginSpy = vi.fn(() => 'sentryVitePlugin');
+
+vi.mock('@sentry/vite-plugin', () => ({
+ // @ts-expect-error - just mocking around
+ sentryVitePlugin: vi.fn(args => sentryVitePluginSpy(args)),
+}));
+
+process.env = {
+ ...process.env,
+ SENTRY_AUTH_TOKEN: 'my-token',
+};
+
+describe('sentryAstro integration', () => {
+ afterEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('has a name', () => {
+ const integration = sentryAstro({});
+ expect(integration.name).toBe('@sentry/astro');
+ });
+
+ it('enables source maps and adds the sentry vite plugin if an auth token is detected', async () => {
+ const integration = sentryAstro({
+ sourceMapsUploadOptions: { enabled: true, org: 'my-org', project: 'my-project', telemetry: false },
+ });
+ const updateConfig = vi.fn();
+ const injectScript = vi.fn();
+
+ expect(integration.hooks['astro:config:setup']).toBeDefined();
+ // @ts-expect-error - the hook exists and we only need to pass what we actually use
+ await integration.hooks['astro:config:setup']({ updateConfig, injectScript });
+
+ expect(updateConfig).toHaveBeenCalledTimes(1);
+ expect(updateConfig).toHaveBeenCalledWith({
+ vite: {
+ build: {
+ sourcemap: true,
+ },
+ plugins: ['sentryVitePlugin'],
+ },
+ });
+
+ expect(sentryVitePluginSpy).toHaveBeenCalledTimes(1);
+ expect(sentryVitePluginSpy).toHaveBeenCalledWith({
+ authToken: 'my-token',
+ org: 'my-org',
+ project: 'my-project',
+ telemetry: false,
+ });
+ });
+
+ it("doesn't enable source maps if `sourceMapsUploadOptions.enabled` is `false`", async () => {
+ const integration = sentryAstro({
+ sourceMapsUploadOptions: { enabled: false },
+ });
+ const updateConfig = vi.fn();
+ const injectScript = vi.fn();
+
+ expect(integration.hooks['astro:config:setup']).toBeDefined();
+ // @ts-expect-error - the hook exists and we only need to pass what we actually use
+ await integration.hooks['astro:config:setup']({ updateConfig, injectScript });
+
+ expect(updateConfig).toHaveBeenCalledTimes(0);
+ expect(sentryVitePluginSpy).toHaveBeenCalledTimes(0);
+ });
+
+ it('injects client and server init scripts', async () => {
+ const integration = sentryAstro({});
+ const updateConfig = vi.fn();
+ const injectScript = vi.fn();
+
+ expect(integration.hooks['astro:config:setup']).toBeDefined();
+ // @ts-expect-error - the hook exists and we only need to pass what we actually use
+ await integration.hooks['astro:config:setup']({ updateConfig, injectScript });
+
+ expect(injectScript).toHaveBeenCalledTimes(2);
+ expect(injectScript).toHaveBeenCalledWith('page', expect.stringContaining('Sentry.init'));
+ expect(injectScript).toHaveBeenCalledWith('page-ssr', expect.stringContaining('Sentry.init'));
+ });
+
+ it('injects client and server init scripts from custom paths', async () => {
+ const integration = sentryAstro({
+ clientInitPath: 'my-client-init-path.js',
+ serverInitPath: 'my-server-init-path.js',
+ });
+
+ const updateConfig = vi.fn();
+ const injectScript = vi.fn();
+
+ expect(integration.hooks['astro:config:setup']).toBeDefined();
+ // @ts-expect-error - the hook exists and we only need to pass what we actually use
+ await integration.hooks['astro:config:setup']({ updateConfig, injectScript });
+
+ expect(injectScript).toHaveBeenCalledTimes(2);
+ expect(injectScript).toHaveBeenCalledWith('page', expect.stringContaining('my-client-init-path.js'));
+ expect(injectScript).toHaveBeenCalledWith('page-ssr', expect.stringContaining('my-server-init-path.js'));
+ });
+});
diff --git a/packages/astro/test/integration/snippets.test.ts b/packages/astro/test/integration/snippets.test.ts
new file mode 100644
index 000000000000..60406b652bf8
--- /dev/null
+++ b/packages/astro/test/integration/snippets.test.ts
@@ -0,0 +1,93 @@
+import { buildClientSnippet, buildSdkInitFileImportSnippet, buildServerSnippet } from '../../src/integration/snippets';
+
+const allSdkOptions = {
+ dsn: 'my-dsn',
+ release: '1.0.0',
+ environment: 'staging',
+ sampleRate: 0.2,
+ tracesSampleRate: 0.3,
+ replaysOnErrorSampleRate: 0.4,
+ replaysSessionSampleRate: 0.5,
+ debug: true,
+};
+
+describe('buildClientSnippet', () => {
+ it('returns a basic Sentry init call with default options', () => {
+ const snippet = buildClientSnippet({});
+ expect(snippet).toMatchInlineSnapshot(`
+ "import * as Sentry from \\"@sentry/astro\\";
+
+ Sentry.init({
+ dsn: import.meta.env.PUBLIC_SENTRY_DSN,
+ debug: false,
+ environment: import.meta.env.PUBLIC_VERCEL_ENV,
+ release: import.meta.env.PUBLIC_VERCEL_GIT_COMMIT_SHA,
+ tracesSampleRate: 1,
+ integrations: [new Sentry.BrowserTracing(), new Sentry.Replay()],
+ replaysSessionSampleRate: 0.1,
+ replaysOnErrorSampleRate: 1,
+ });"
+ `);
+ });
+
+ it('returns a basic Sentry init call with custom options', () => {
+ const snippet = buildClientSnippet(allSdkOptions);
+
+ expect(snippet).toMatchInlineSnapshot(`
+ "import * as Sentry from \\"@sentry/astro\\";
+
+ Sentry.init({
+ dsn: \\"my-dsn\\",
+ debug: true,
+ environment: \\"staging\\",
+ release: \\"1.0.0\\",
+ tracesSampleRate: 0.3,
+ sampleRate: 0.2,
+ integrations: [new Sentry.BrowserTracing(), new Sentry.Replay()],
+ replaysSessionSampleRate: 0.5,
+ replaysOnErrorSampleRate: 0.4,
+ });"
+ `);
+ });
+});
+
+describe('buildServerSnippet', () => {
+ it('returns a basic Sentry init call with default options', () => {
+ const snippet = buildServerSnippet({});
+ expect(snippet).toMatchInlineSnapshot(`
+ "import * as Sentry from \\"@sentry/astro\\";
+
+ Sentry.init({
+ dsn: import.meta.env.PUBLIC_SENTRY_DSN,
+ debug: false,
+ environment: import.meta.env.PUBLIC_VERCEL_ENV,
+ release: import.meta.env.PUBLIC_VERCEL_GIT_COMMIT_SHA,
+ tracesSampleRate: 1,
+ });"
+ `);
+ });
+
+ it('returns a basic Sentry init call with custom options', () => {
+ const snippet = buildServerSnippet(allSdkOptions);
+
+ expect(snippet).toMatchInlineSnapshot(`
+ "import * as Sentry from \\"@sentry/astro\\";
+
+ Sentry.init({
+ dsn: \\"my-dsn\\",
+ debug: true,
+ environment: \\"staging\\",
+ release: \\"1.0.0\\",
+ tracesSampleRate: 0.3,
+ sampleRate: 0.2,
+ });"
+ `);
+ });
+});
+
+describe('buildSdkInitFileImportSnippet', () => {
+ it('returns a snippet that imports a file', () => {
+ const snippet = buildSdkInitFileImportSnippet('./my-file.ts');
+ expect(snippet).toBe('import "./my-file.ts";');
+ });
+});
diff --git a/packages/astro/test/server/index.server.test.ts b/packages/astro/test/server/index.server.test.ts
new file mode 100644
index 000000000000..f319ef90eaad
--- /dev/null
+++ b/packages/astro/test/server/index.server.test.ts
@@ -0,0 +1,7 @@
+import sentryAstro from '../../src/index.server';
+describe('server SDK', () => {
+ it('exports the astro integration as a default export', () => {
+ const integration = sentryAstro();
+ expect(integration.name).toBe('@sentry/astro');
+ });
+});
diff --git a/packages/astro/test/server/sdk.test.ts b/packages/astro/test/server/sdk.test.ts
new file mode 100644
index 000000000000..0e178f7ae45a
--- /dev/null
+++ b/packages/astro/test/server/sdk.test.ts
@@ -0,0 +1,52 @@
+import { getCurrentHub } from '@sentry/core';
+import * as SentryNode from '@sentry/node';
+import { SDK_VERSION } from '@sentry/node';
+import { GLOBAL_OBJ } from '@sentry/utils';
+import { vi } from 'vitest';
+
+import { init } from '../../src/server/sdk';
+
+const nodeInit = vi.spyOn(SentryNode, 'init');
+
+describe('Sentry server SDK', () => {
+ describe('init', () => {
+ afterEach(() => {
+ vi.clearAllMocks();
+ GLOBAL_OBJ.__SENTRY__.hub = undefined;
+ });
+
+ it('adds Astro metadata to the SDK options', () => {
+ expect(nodeInit).not.toHaveBeenCalled();
+
+ init({});
+
+ expect(nodeInit).toHaveBeenCalledTimes(1);
+ expect(nodeInit).toHaveBeenCalledWith(
+ expect.objectContaining({
+ _metadata: {
+ sdk: {
+ name: 'sentry.javascript.astro',
+ version: SDK_VERSION,
+ packages: [
+ { name: 'npm:@sentry/astro', version: SDK_VERSION },
+ { name: 'npm:@sentry/node', version: SDK_VERSION },
+ ],
+ },
+ },
+ }),
+ );
+ });
+
+ it('sets the runtime tag on the scope', () => {
+ const currentScope = getCurrentHub().getScope();
+
+ // @ts-expect-error need access to protected _tags attribute
+ expect(currentScope._tags).toEqual({});
+
+ init({ dsn: 'https://public@dsn.ingest.sentry.io/1337' });
+
+ // @ts-expect-error need access to protected _tags attribute
+ expect(currentScope._tags).toEqual({ runtime: 'node' });
+ });
+ });
+});
diff --git a/packages/astro/tsconfig.dev.json b/packages/astro/tsconfig.dev.json
new file mode 100644
index 000000000000..cc931d956044
--- /dev/null
+++ b/packages/astro/tsconfig.dev.json
@@ -0,0 +1,14 @@
+{
+ "extends": "../../tsconfig.dev.json",
+
+ "include": ["scripts/**/*"],
+
+ "compilerOptions": {
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ },
+
+ "ts-node": {
+ "esm": true
+ }
+}
diff --git a/packages/astro/tsconfig.json b/packages/astro/tsconfig.json
new file mode 100644
index 000000000000..bf45a09f2d71
--- /dev/null
+++ b/packages/astro/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "extends": "../../tsconfig.json",
+
+ "include": ["src/**/*"],
+
+ "compilerOptions": {
+ // package-specific options
+ }
+}
diff --git a/packages/astro/tsconfig.test.json b/packages/astro/tsconfig.test.json
new file mode 100644
index 000000000000..3fbe012384ee
--- /dev/null
+++ b/packages/astro/tsconfig.test.json
@@ -0,0 +1,10 @@
+{
+ "extends": "./tsconfig.json",
+
+ "include": ["test/**/*", "vite.config.ts"],
+
+ "compilerOptions": {
+ // should include all types from `./tsconfig.json` plus types for all test frameworks used
+ "types": ["node", "vitest/globals"]
+ }
+}
diff --git a/packages/astro/tsconfig.types.json b/packages/astro/tsconfig.types.json
new file mode 100644
index 000000000000..65455f66bd75
--- /dev/null
+++ b/packages/astro/tsconfig.types.json
@@ -0,0 +1,10 @@
+{
+ "extends": "./tsconfig.json",
+
+ "compilerOptions": {
+ "declaration": true,
+ "declarationMap": true,
+ "emitDeclarationOnly": true,
+ "outDir": "build/types"
+ }
+}
diff --git a/packages/astro/vite.config.ts b/packages/astro/vite.config.ts
new file mode 100644
index 000000000000..6a035a7635e7
--- /dev/null
+++ b/packages/astro/vite.config.ts
@@ -0,0 +1,13 @@
+import type { UserConfig } from 'vitest';
+
+import baseConfig from '../../vite/vite.config';
+
+export default {
+ ...baseConfig,
+ test: {
+ // test exists, no idea why TS doesn't recognize it
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ ...(baseConfig as UserConfig & { test: any }).test,
+ environment: 'jsdom',
+ },
+};
diff --git a/packages/browser-integration-tests/suites/replay/exceptions/template.html b/packages/browser-integration-tests/suites/replay/exceptions/template.html
new file mode 100644
index 000000000000..0915a77b0cd9
--- /dev/null
+++ b/packages/browser-integration-tests/suites/replay/exceptions/template.html
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/packages/browser-integration-tests/suites/replay/exceptions/test.ts b/packages/browser-integration-tests/suites/replay/exceptions/test.ts
new file mode 100644
index 000000000000..203550d55759
--- /dev/null
+++ b/packages/browser-integration-tests/suites/replay/exceptions/test.ts
@@ -0,0 +1,35 @@
+import { expect } from '@playwright/test';
+
+import { sentryTest } from '../../../utils/fixtures';
+import { shouldSkipReplayTest } from '../../../utils/replayHelpers';
+
+sentryTest('exceptions within rrweb and re-thrown and annotated', async ({ getLocalTestPath, page, browserName }) => {
+ if (shouldSkipReplayTest() || browserName !== 'chromium') {
+ sentryTest.skip();
+ }
+
+ const url = await getLocalTestPath({ testDir: __dirname });
+
+ await page.goto(url);
+
+ expect(
+ await page.evaluate(() => {
+ try {
+ const s = new CSSStyleSheet();
+ s.insertRule('body::-ms-expand{display: none}');
+ s.insertRule('body {background-color: #fff;}');
+ return s.cssRules.length;
+ } catch {
+ return false;
+ }
+ }),
+ ).toBe(false);
+
+ expect(
+ await page.evaluate(() => {
+ const s = new CSSStyleSheet();
+ s.insertRule('body {background-color: #fff;}');
+ return s.cssRules.length;
+ }),
+ ).toBe(1);
+});
diff --git a/packages/browser/package.json b/packages/browser/package.json
index 55f9e15565e8..ce789c53caaf 100644
--- a/packages/browser/package.json
+++ b/packages/browser/package.json
@@ -51,7 +51,7 @@
"node-fetch": "^2.6.0",
"playwright": "^1.31.1",
"sinon": "^7.3.2",
- "webpack": "^4.30.0"
+ "webpack": "^4.47.0"
},
"scripts": {
"build": "run-p build:transpile build:bundle build:types",
diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts
index f46b55f45214..3d3aec477731 100644
--- a/packages/browser/src/exports.ts
+++ b/packages/browser/src/exports.ts
@@ -23,6 +23,7 @@ export type { ReportDialogOptions } from './helpers';
export {
addGlobalEventProcessor,
addBreadcrumb,
+ addIntegration,
captureException,
captureEvent,
captureMessage,
@@ -41,6 +42,7 @@ export {
startSpan,
startInactiveSpan,
startSpanManual,
+ continueTrace,
SDK_VERSION,
setContext,
setExtra,
diff --git a/packages/bun/package.json b/packages/bun/package.json
index 38db742a8c84..6cb29b2e403c 100644
--- a/packages/bun/package.json
+++ b/packages/bun/package.json
@@ -44,7 +44,7 @@
"build:types:watch": "tsc -p tsconfig.types.json --watch",
"build:tarball": "ts-node ../../scripts/prepack.ts && npm pack ./build",
"circularDepCheck": "madge --circular src/index.ts",
- "clean": "rimraf build coverage sentry-node-*.tgz",
+ "clean": "rimraf build coverage sentry-bun-*.tgz",
"fix": "run-s fix:eslint fix:prettier",
"fix:eslint": "eslint . --format stylish --fix",
"fix:prettier": "prettier --write \"{src,test,scripts}/**/**.ts\"",
diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts
index d1c4a69f0ae5..c8428ab8e106 100644
--- a/packages/bun/src/index.ts
+++ b/packages/bun/src/index.ts
@@ -26,6 +26,7 @@ export type { BunOptions } from './types';
export {
addGlobalEventProcessor,
addBreadcrumb,
+ addIntegration,
captureException,
captureEvent,
captureMessage,
@@ -59,6 +60,7 @@ export {
startSpan,
startInactiveSpan,
startSpanManual,
+ continueTrace,
} from '@sentry/core';
export type { SpanStatusType } from '@sentry/core';
export { autoDiscoverNodePerformanceMonitoringIntegrations } from '@sentry/node';
diff --git a/packages/bun/src/transports/index.ts b/packages/bun/src/transports/index.ts
index 96e3e99957fb..ad9832795fc4 100644
--- a/packages/bun/src/transports/index.ts
+++ b/packages/bun/src/transports/index.ts
@@ -15,7 +15,6 @@ export function makeFetchTransport(options: BunTransportOptions): Transport {
const requestOptions: RequestInit = {
body: request.body,
method: 'POST',
- referrerPolicy: 'origin',
headers: options.headers,
};
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 81fcd7c3af8d..f14f5d4aaf2f 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -46,7 +46,7 @@ export { createTransport } from './transports/base';
export { makeOfflineTransport } from './transports/offline';
export { makeMultiplexedTransport } from './transports/multiplexed';
export { SDK_VERSION } from './version';
-export { getIntegrationsToSetup } from './integration';
+export { getIntegrationsToSetup, addIntegration } from './integration';
export { FunctionToString, InboundFilters } from './integrations';
export { prepareEvent } from './utils/prepareEvent';
export { createCheckInEnvelope } from './checkin';
diff --git a/packages/core/src/integration.ts b/packages/core/src/integration.ts
index aa8968edc8dc..b4d32ea38e87 100644
--- a/packages/core/src/integration.ts
+++ b/packages/core/src/integration.ts
@@ -124,6 +124,18 @@ export function setupIntegration(client: Client, integration: Integration, integ
__DEBUG_BUILD__ && logger.log(`Integration installed: ${integration.name}`);
}
+/** Add an integration to the current hub's client. */
+export function addIntegration(integration: Integration): void {
+ const client = getCurrentHub().getClient();
+
+ if (!client || !client.addIntegration) {
+ __DEBUG_BUILD__ && logger.warn(`Cannot add integration "${integration.name}" because no SDK Client is available.`);
+ return;
+ }
+
+ client.addIntegration(integration);
+}
+
// Polyfill for Array.findIndex(), which is not supported in ES5
function findIndex(arr: T[], callback: (item: T) => boolean): number {
for (let i = 0; i < arr.length; i++) {
diff --git a/packages/core/src/tracing/hubextensions.ts b/packages/core/src/tracing/hubextensions.ts
index ad6858d41e4a..007612d8fb34 100644
--- a/packages/core/src/tracing/hubextensions.ts
+++ b/packages/core/src/tracing/hubextensions.ts
@@ -1,11 +1,11 @@
-import type { ClientOptions, CustomSamplingContext, Options, SamplingContext, TransactionContext } from '@sentry/types';
-import { isNaN, logger } from '@sentry/utils';
+import type { ClientOptions, CustomSamplingContext, TransactionContext } from '@sentry/types';
+import { logger } from '@sentry/utils';
import type { Hub } from '../hub';
import { getMainCarrier } from '../hub';
-import { hasTracingEnabled } from '../utils/hasTracingEnabled';
import { registerErrorInstrumentation } from './errors';
import { IdleTransaction } from './idletransaction';
+import { sampleTransaction } from './sampling';
import { Transaction } from './transaction';
/** Returns all trace headers that are currently on the top scope. */
@@ -20,126 +20,6 @@ function traceHeaders(this: Hub): { [key: string]: string } {
: {};
}
-/**
- * Makes a sampling decision for the given transaction and stores it on the transaction.
- *
- * Called every time a transaction is created. Only transactions which emerge with a `sampled` value of `true` will be
- * sent to Sentry.
- *
- * @param transaction: The transaction needing a sampling decision
- * @param options: The current client's options, so we can access `tracesSampleRate` and/or `tracesSampler`
- * @param samplingContext: Default and user-provided data which may be used to help make the decision
- *
- * @returns The given transaction with its `sampled` value set
- */
-function sample(
- transaction: T,
- options: Pick,
- samplingContext: SamplingContext,
-): T {
- // nothing to do if tracing is not enabled
- if (!hasTracingEnabled(options)) {
- transaction.sampled = false;
- return transaction;
- }
-
- // if the user has forced a sampling decision by passing a `sampled` value in their transaction context, go with that
- if (transaction.sampled !== undefined) {
- transaction.setMetadata({
- sampleRate: Number(transaction.sampled),
- });
- return transaction;
- }
-
- // we would have bailed already if neither `tracesSampler` nor `tracesSampleRate` nor `enableTracing` were defined, so one of these should
- // work; prefer the hook if so
- let sampleRate;
- if (typeof options.tracesSampler === 'function') {
- sampleRate = options.tracesSampler(samplingContext);
- transaction.setMetadata({
- sampleRate: Number(sampleRate),
- });
- } else if (samplingContext.parentSampled !== undefined) {
- sampleRate = samplingContext.parentSampled;
- } else if (typeof options.tracesSampleRate !== 'undefined') {
- sampleRate = options.tracesSampleRate;
- transaction.setMetadata({
- sampleRate: Number(sampleRate),
- });
- } else {
- // When `enableTracing === true`, we use a sample rate of 100%
- sampleRate = 1;
- transaction.setMetadata({
- sampleRate,
- });
- }
-
- // Since this is coming from the user (or from a function provided by the user), who knows what we might get. (The
- // only valid values are booleans or numbers between 0 and 1.)
- if (!isValidSampleRate(sampleRate)) {
- __DEBUG_BUILD__ && logger.warn('[Tracing] Discarding transaction because of invalid sample rate.');
- transaction.sampled = false;
- return transaction;
- }
-
- // if the function returned 0 (or false), or if `tracesSampleRate` is 0, it's a sign the transaction should be dropped
- if (!sampleRate) {
- __DEBUG_BUILD__ &&
- logger.log(
- `[Tracing] Discarding transaction because ${
- typeof options.tracesSampler === 'function'
- ? 'tracesSampler returned 0 or false'
- : 'a negative sampling decision was inherited or tracesSampleRate is set to 0'
- }`,
- );
- transaction.sampled = false;
- return transaction;
- }
-
- // Now we roll the dice. Math.random is inclusive of 0, but not of 1, so strict < is safe here. In case sampleRate is
- // a boolean, the < comparison will cause it to be automatically cast to 1 if it's true and 0 if it's false.
- transaction.sampled = Math.random() < (sampleRate as number | boolean);
-
- // if we're not going to keep it, we're done
- if (!transaction.sampled) {
- __DEBUG_BUILD__ &&
- logger.log(
- `[Tracing] Discarding transaction because it's not included in the random sample (sampling rate = ${Number(
- sampleRate,
- )})`,
- );
- return transaction;
- }
-
- __DEBUG_BUILD__ && logger.log(`[Tracing] starting ${transaction.op} transaction - ${transaction.name}`);
- return transaction;
-}
-
-/**
- * Checks the given sample rate to make sure it is valid type and value (a boolean, or a number between 0 and 1).
- */
-function isValidSampleRate(rate: unknown): boolean {
- // we need to check NaN explicitly because it's of type 'number' and therefore wouldn't get caught by this typecheck
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- if (isNaN(rate) || !(typeof rate === 'number' || typeof rate === 'boolean')) {
- __DEBUG_BUILD__ &&
- logger.warn(
- `[Tracing] Given sample rate is invalid. Sample rate must be a boolean or a number between 0 and 1. Got ${JSON.stringify(
- rate,
- )} of type ${JSON.stringify(typeof rate)}.`,
- );
- return false;
- }
-
- // in case sampleRate is a boolean, it will get automatically cast to 1 if it's true and 0 if it's false
- if (rate < 0 || rate > 1) {
- __DEBUG_BUILD__ &&
- logger.warn(`[Tracing] Given sample rate is invalid. Sample rate must be between 0 and 1. Got ${rate}.`);
- return false;
- }
- return true;
-}
-
/**
* Creates a new transaction and adds a sampling decision if it doesn't yet have one.
*
@@ -177,7 +57,7 @@ The transaction will not be sampled. Please use the ${configInstrumenter} instru
}
let transaction = new Transaction(transactionContext, this);
- transaction = sample(transaction, options, {
+ transaction = sampleTransaction(transaction, options, {
parentSampled: transactionContext.parentSampled,
transactionContext,
...customSamplingContext,
@@ -207,7 +87,7 @@ export function startIdleTransaction(
const options: Partial = (client && client.getOptions()) || {};
let transaction = new IdleTransaction(transactionContext, hub, idleTimeout, finalTimeout, heartbeatInterval, onScope);
- transaction = sample(transaction, options, {
+ transaction = sampleTransaction(transaction, options, {
parentSampled: transactionContext.parentSampled,
transactionContext,
...customSamplingContext,
diff --git a/packages/core/src/tracing/index.ts b/packages/core/src/tracing/index.ts
index c5be88f8c350..f0356a528c2d 100644
--- a/packages/core/src/tracing/index.ts
+++ b/packages/core/src/tracing/index.ts
@@ -7,7 +7,15 @@ export { extractTraceparentData, getActiveTransaction } from './utils';
// eslint-disable-next-line deprecation/deprecation
export { SpanStatus } from './spanstatus';
export type { SpanStatusType } from './span';
-// eslint-disable-next-line deprecation/deprecation
-export { trace, getActiveSpan, startSpan, startInactiveSpan, startActiveSpan, startSpanManual } from './trace';
+export {
+ trace,
+ getActiveSpan,
+ startSpan,
+ startInactiveSpan,
+ // eslint-disable-next-line deprecation/deprecation
+ startActiveSpan,
+ startSpanManual,
+ continueTrace,
+} from './trace';
export { getDynamicSamplingContextFromClient } from './dynamicSamplingContext';
export { setMeasurement } from './measurement';
diff --git a/packages/core/src/tracing/sampling.ts b/packages/core/src/tracing/sampling.ts
new file mode 100644
index 000000000000..4b357b7bf1be
--- /dev/null
+++ b/packages/core/src/tracing/sampling.ts
@@ -0,0 +1,122 @@
+import type { Options, SamplingContext } from '@sentry/types';
+import { isNaN, logger } from '@sentry/utils';
+
+import { hasTracingEnabled } from '../utils/hasTracingEnabled';
+import type { Transaction } from './transaction';
+
+/**
+ * Makes a sampling decision for the given transaction and stores it on the transaction.
+ *
+ * Called every time a transaction is created. Only transactions which emerge with a `sampled` value of `true` will be
+ * sent to Sentry.
+ *
+ * This method muttes the given `transaction` and will set the `sampled` value on it.
+ * It returns the same transaction, for convenience.
+ */
+export function sampleTransaction(
+ transaction: T,
+ options: Pick,
+ samplingContext: SamplingContext,
+): T {
+ // nothing to do if tracing is not enabled
+ if (!hasTracingEnabled(options)) {
+ transaction.sampled = false;
+ return transaction;
+ }
+
+ // if the user has forced a sampling decision by passing a `sampled` value in their transaction context, go with that
+ if (transaction.sampled !== undefined) {
+ transaction.setMetadata({
+ sampleRate: Number(transaction.sampled),
+ });
+ return transaction;
+ }
+
+ // we would have bailed already if neither `tracesSampler` nor `tracesSampleRate` nor `enableTracing` were defined, so one of these should
+ // work; prefer the hook if so
+ let sampleRate;
+ if (typeof options.tracesSampler === 'function') {
+ sampleRate = options.tracesSampler(samplingContext);
+ transaction.setMetadata({
+ sampleRate: Number(sampleRate),
+ });
+ } else if (samplingContext.parentSampled !== undefined) {
+ sampleRate = samplingContext.parentSampled;
+ } else if (typeof options.tracesSampleRate !== 'undefined') {
+ sampleRate = options.tracesSampleRate;
+ transaction.setMetadata({
+ sampleRate: Number(sampleRate),
+ });
+ } else {
+ // When `enableTracing === true`, we use a sample rate of 100%
+ sampleRate = 1;
+ transaction.setMetadata({
+ sampleRate,
+ });
+ }
+
+ // Since this is coming from the user (or from a function provided by the user), who knows what we might get. (The
+ // only valid values are booleans or numbers between 0 and 1.)
+ if (!isValidSampleRate(sampleRate)) {
+ __DEBUG_BUILD__ && logger.warn('[Tracing] Discarding transaction because of invalid sample rate.');
+ transaction.sampled = false;
+ return transaction;
+ }
+
+ // if the function returned 0 (or false), or if `tracesSampleRate` is 0, it's a sign the transaction should be dropped
+ if (!sampleRate) {
+ __DEBUG_BUILD__ &&
+ logger.log(
+ `[Tracing] Discarding transaction because ${
+ typeof options.tracesSampler === 'function'
+ ? 'tracesSampler returned 0 or false'
+ : 'a negative sampling decision was inherited or tracesSampleRate is set to 0'
+ }`,
+ );
+ transaction.sampled = false;
+ return transaction;
+ }
+
+ // Now we roll the dice. Math.random is inclusive of 0, but not of 1, so strict < is safe here. In case sampleRate is
+ // a boolean, the < comparison will cause it to be automatically cast to 1 if it's true and 0 if it's false.
+ transaction.sampled = Math.random() < (sampleRate as number | boolean);
+
+ // if we're not going to keep it, we're done
+ if (!transaction.sampled) {
+ __DEBUG_BUILD__ &&
+ logger.log(
+ `[Tracing] Discarding transaction because it's not included in the random sample (sampling rate = ${Number(
+ sampleRate,
+ )})`,
+ );
+ return transaction;
+ }
+
+ __DEBUG_BUILD__ && logger.log(`[Tracing] starting ${transaction.op} transaction - ${transaction.name}`);
+ return transaction;
+}
+
+/**
+ * Checks the given sample rate to make sure it is valid type and value (a boolean, or a number between 0 and 1).
+ */
+function isValidSampleRate(rate: unknown): boolean {
+ // we need to check NaN explicitly because it's of type 'number' and therefore wouldn't get caught by this typecheck
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ if (isNaN(rate) || !(typeof rate === 'number' || typeof rate === 'boolean')) {
+ __DEBUG_BUILD__ &&
+ logger.warn(
+ `[Tracing] Given sample rate is invalid. Sample rate must be a boolean or a number between 0 and 1. Got ${JSON.stringify(
+ rate,
+ )} of type ${JSON.stringify(typeof rate)}.`,
+ );
+ return false;
+ }
+
+ // in case sampleRate is a boolean, it will get automatically cast to 1 if it's true and 0 if it's false
+ if (rate < 0 || rate > 1) {
+ __DEBUG_BUILD__ &&
+ logger.warn(`[Tracing] Given sample rate is invalid. Sample rate must be between 0 and 1. Got ${rate}.`);
+ return false;
+ }
+ return true;
+}
diff --git a/packages/core/src/tracing/trace.ts b/packages/core/src/tracing/trace.ts
index 8f9b226b4afb..4572eed79ee9 100644
--- a/packages/core/src/tracing/trace.ts
+++ b/packages/core/src/tracing/trace.ts
@@ -1,5 +1,5 @@
import type { TransactionContext } from '@sentry/types';
-import { isThenable } from '@sentry/utils';
+import { dropUndefinedKeys, isThenable, logger, tracingContextFromHeaders } from '@sentry/utils';
import type { Hub } from '../hub';
import { getCurrentHub } from '../hub';
@@ -203,6 +203,48 @@ export function getActiveSpan(): Span | undefined {
return getCurrentHub().getScope().getSpan();
}
+/**
+ * Continue a trace from `sentry-trace` and `baggage` values.
+ * These values can be obtained from incoming request headers,
+ * or in the browser from `` and `` HTML tags.
+ *
+ * It also takes an optional `request` option, which if provided will also be added to the scope & transaction metadata.
+ * The callback receives a transactionContext that may be used for `startTransaction` or `startSpan`.
+ */
+export function continueTrace(
+ {
+ sentryTrace,
+ baggage,
+ }: {
+ sentryTrace: Parameters[0];
+ baggage: Parameters[1];
+ },
+ callback: (transactionContext: Partial) => V,
+): V {
+ const hub = getCurrentHub();
+ const currentScope = hub.getScope();
+
+ const { traceparentData, dynamicSamplingContext, propagationContext } = tracingContextFromHeaders(
+ sentryTrace,
+ baggage,
+ );
+
+ currentScope.setPropagationContext(propagationContext);
+
+ if (__DEBUG_BUILD__ && traceparentData) {
+ logger.log(`[Tracing] Continuing trace ${traceparentData.traceId}.`);
+ }
+
+ const transactionContext: Partial = {
+ ...traceparentData,
+ metadata: dropUndefinedKeys({
+ dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext,
+ }),
+ };
+
+ return callback(transactionContext);
+}
+
function createChildSpanOrTransaction(
hub: Hub,
parentSpan: Span | undefined,
diff --git a/packages/core/test/lib/base.test.ts b/packages/core/test/lib/base.test.ts
index 27e34a1402d4..2d5f8ff05db8 100644
--- a/packages/core/test/lib/base.test.ts
+++ b/packages/core/test/lib/base.test.ts
@@ -78,6 +78,9 @@ describe('BaseClient', () => {
});
test('handles being passed an invalid Dsn', () => {
+ // Hide warning logs in the test
+ jest.spyOn(console, 'error').mockImplementation(() => {});
+
const options = getDefaultTestClientOptions({ dsn: 'abc' });
const client = new TestClient(options);
diff --git a/packages/core/test/lib/integration.test.ts b/packages/core/test/lib/integration.test.ts
index f431d30b2140..7ffcdb572994 100644
--- a/packages/core/test/lib/integration.test.ts
+++ b/packages/core/test/lib/integration.test.ts
@@ -1,6 +1,8 @@
import type { Integration, Options } from '@sentry/types';
+import { logger } from '@sentry/utils';
-import { getIntegrationsToSetup, installedIntegrations, setupIntegration } from '../../src/integration';
+import { Hub, makeMain } from '../../src/hub';
+import { addIntegration, getIntegrationsToSetup, installedIntegrations, setupIntegration } from '../../src/integration';
import { getDefaultTestClientOptions, TestClient } from '../mocks/client';
function getTestClient(): TestClient {
@@ -559,3 +561,51 @@ describe('setupIntegration', () => {
expect(sendEvent).not.toHaveBeenCalled();
});
});
+
+describe('addIntegration', () => {
+ beforeEach(function () {
+ // Reset the (global!) list of installed integrations
+ installedIntegrations.splice(0, installedIntegrations.length);
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('works with a client setup', () => {
+ const warnings = jest.spyOn(logger, 'warn');
+
+ class CustomIntegration implements Integration {
+ name = 'test';
+ setupOnce = jest.fn();
+ }
+
+ const client = getTestClient();
+ const hub = new Hub(client);
+ makeMain(hub);
+
+ const integration = new CustomIntegration();
+ addIntegration(integration);
+
+ expect(integration.setupOnce).toHaveBeenCalledTimes(1);
+ expect(warnings).not.toHaveBeenCalled();
+ });
+
+ it('works without a client setup', () => {
+ const warnings = jest.spyOn(logger, 'warn');
+ class CustomIntegration implements Integration {
+ name = 'test';
+ setupOnce = jest.fn();
+ }
+
+ const hub = new Hub();
+ makeMain(hub);
+
+ const integration = new CustomIntegration();
+ addIntegration(integration);
+
+ expect(integration.setupOnce).not.toHaveBeenCalled();
+ expect(warnings).toHaveBeenCalledTimes(1);
+ expect(warnings).toHaveBeenCalledWith('Cannot add integration "test" because no SDK Client is available.');
+ });
+});
diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts
index 2480d449a9d9..144ec35f1f0e 100644
--- a/packages/core/test/lib/tracing/trace.test.ts
+++ b/packages/core/test/lib/tracing/trace.test.ts
@@ -1,5 +1,5 @@
import { addTracingExtensions, Hub, makeMain } from '../../../src';
-import { startSpan } from '../../../src/tracing';
+import { continueTrace, startSpan } from '../../../src/tracing';
import { getDefaultTestClientOptions, TestClient } from '../../mocks/client';
beforeAll(() => {
@@ -170,3 +170,154 @@ describe('startSpan', () => {
});
});
});
+
+describe('continueTrace', () => {
+ beforeEach(() => {
+ const options = getDefaultTestClientOptions({ tracesSampleRate: 0.0 });
+ client = new TestClient(options);
+ hub = new Hub(client);
+ makeMain(hub);
+ });
+
+ it('works without trace & baggage data', () => {
+ const expectedContext = {
+ metadata: {},
+ };
+
+ const result = continueTrace({ sentryTrace: undefined, baggage: undefined }, ctx => {
+ expect(ctx).toEqual(expectedContext);
+ return ctx;
+ });
+
+ expect(result).toEqual(expectedContext);
+
+ const scope = hub.getScope();
+
+ expect(scope.getPropagationContext()).toEqual({
+ sampled: undefined,
+ spanId: expect.any(String),
+ traceId: expect.any(String),
+ });
+
+ expect(scope['_sdkProcessingMetadata']).toEqual({});
+ });
+
+ it('works with trace data', () => {
+ const expectedContext = {
+ metadata: {
+ dynamicSamplingContext: {},
+ },
+ parentSampled: false,
+ parentSpanId: '1121201211212012',
+ traceId: '12312012123120121231201212312012',
+ };
+
+ const result = continueTrace(
+ {
+ sentryTrace: '12312012123120121231201212312012-1121201211212012-0',
+ baggage: undefined,
+ },
+ ctx => {
+ expect(ctx).toEqual(expectedContext);
+ return ctx;
+ },
+ );
+
+ expect(result).toEqual(expectedContext);
+
+ const scope = hub.getScope();
+
+ expect(scope.getPropagationContext()).toEqual({
+ sampled: false,
+ parentSpanId: '1121201211212012',
+ spanId: expect.any(String),
+ traceId: '12312012123120121231201212312012',
+ });
+
+ expect(scope['_sdkProcessingMetadata']).toEqual({});
+ });
+
+ it('works with trace & baggage data', () => {
+ const expectedContext = {
+ metadata: {
+ dynamicSamplingContext: {
+ environment: 'production',
+ version: '1.0',
+ },
+ },
+ parentSampled: true,
+ parentSpanId: '1121201211212012',
+ traceId: '12312012123120121231201212312012',
+ };
+
+ const result = continueTrace(
+ {
+ sentryTrace: '12312012123120121231201212312012-1121201211212012-1',
+ baggage: 'sentry-version=1.0,sentry-environment=production',
+ },
+ ctx => {
+ expect(ctx).toEqual(expectedContext);
+ return ctx;
+ },
+ );
+
+ expect(result).toEqual(expectedContext);
+
+ const scope = hub.getScope();
+
+ expect(scope.getPropagationContext()).toEqual({
+ dsc: {
+ environment: 'production',
+ version: '1.0',
+ },
+ sampled: true,
+ parentSpanId: '1121201211212012',
+ spanId: expect.any(String),
+ traceId: '12312012123120121231201212312012',
+ });
+
+ expect(scope['_sdkProcessingMetadata']).toEqual({});
+ });
+
+ it('works with trace & 3rd party baggage data', () => {
+ const expectedContext = {
+ metadata: {
+ dynamicSamplingContext: {
+ environment: 'production',
+ version: '1.0',
+ },
+ },
+ parentSampled: true,
+ parentSpanId: '1121201211212012',
+ traceId: '12312012123120121231201212312012',
+ };
+
+ const result = continueTrace(
+ {
+ sentryTrace: '12312012123120121231201212312012-1121201211212012-1',
+ baggage: 'sentry-version=1.0,sentry-environment=production,dogs=great,cats=boring',
+ },
+ ctx => {
+ expect(ctx).toEqual(expectedContext);
+ return ctx;
+ },
+ );
+
+ expect(result).toEqual(expectedContext);
+
+ const scope = hub.getScope();
+
+ expect(scope.getPropagationContext()).toEqual({
+ dsc: {
+ environment: 'production',
+ version: '1.0',
+ },
+ sampled: true,
+ parentSpanId: '1121201211212012',
+ spanId: expect.any(String),
+ traceId: '12312012123120121231201212312012',
+ });
+
+ expect(scope['_sdkProcessingMetadata']).toEqual({});
+ });
+});
diff --git a/packages/core/test/lib/transports/multiplexed.test.ts b/packages/core/test/lib/transports/multiplexed.test.ts
index 446d185f9301..bcfb65736c5e 100644
--- a/packages/core/test/lib/transports/multiplexed.test.ts
+++ b/packages/core/test/lib/transports/multiplexed.test.ts
@@ -88,6 +88,9 @@ describe('makeMultiplexedTransport', () => {
});
it('Falls back to options DSN when a matched DSN is invalid', async () => {
+ // Hide warning logs in the test
+ jest.spyOn(console, 'error').mockImplementation(() => {});
+
expect.assertions(1);
const makeTransport = makeMultiplexedTransport(
@@ -99,6 +102,8 @@ describe('makeMultiplexedTransport', () => {
const transport = makeTransport({ url: DSN1_URL, ...transportOptions });
await transport.send(ERROR_ENVELOPE);
+
+ jest.clearAllMocks();
});
it('DSN can be overridden via match callback', async () => {
diff --git a/packages/deno/.eslintrc.js b/packages/deno/.eslintrc.js
new file mode 100644
index 000000000000..b92652708339
--- /dev/null
+++ b/packages/deno/.eslintrc.js
@@ -0,0 +1,18 @@
+module.exports = {
+ extends: ['../../.eslintrc.js'],
+ ignorePatterns: ['lib.deno.d.ts', 'scripts/*.mjs'],
+ rules: {
+ '@sentry-internal/sdk/no-optional-chaining': 'off',
+ '@sentry-internal/sdk/no-nullish-coalescing': 'off',
+ '@sentry-internal/sdk/no-unsupported-es6-methods': 'off',
+ '@sentry-internal/sdk/no-class-field-initializers': 'off',
+ },
+ overrides: [
+ {
+ files: ['./test/*.ts'],
+ rules: {
+ 'import/no-unresolved': 'off',
+ },
+ },
+ ],
+};
diff --git a/packages/deno/LICENSE b/packages/deno/LICENSE
new file mode 100644
index 000000000000..d11896ba1181
--- /dev/null
+++ b/packages/deno/LICENSE
@@ -0,0 +1,14 @@
+Copyright (c) 2023 Sentry (https://sentry.io) and individual contributors. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
+persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
+Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/packages/deno/README.md b/packages/deno/README.md
new file mode 100644
index 000000000000..5be987a249af
--- /dev/null
+++ b/packages/deno/README.md
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+# Official Sentry SDK for Deno (Beta)
+
+[](https://www.npmjs.com/package/@sentry/deno)
+[](https://www.npmjs.com/package/@sentry/deno)
+[](https://www.npmjs.com/package/@sentry/deno)
+
+## Links
+
+- [Official SDK Docs](https://docs.sentry.io/quickstart/)
+- [TypeDoc](http://getsentry.github.io/sentry-javascript/)
+
+The Sentry Deno SDK is in beta. Please help us improve the SDK by [reporting any issues or giving us feedback](https://github.com/getsentry/sentry-javascript/issues).
+
+## Usage
+
+To use this SDK, call `Sentry.init(options)` as early as possible in the main entry module. This will initialize the SDK and
+hook into the environment. Note that you can turn off almost all side effects using the respective options.
+
+```javascript
+import * as Sentry from 'npm:@sentry/deno';
+
+Sentry.init({
+ dsn: '__DSN__',
+ // ...
+});
+```
+
+To set context information or send manual events, use the exported functions of `@sentry/deno`. Note that these
+functions will not perform any action before you have called `init()`:
+
+```javascript
+// Set user information, as well as tags and further extras
+Sentry.configureScope(scope => {
+ scope.setExtra('battery', 0.7);
+ scope.setTag('user_mode', 'admin');
+ scope.setUser({ id: '4711' });
+ // scope.clear();
+});
+
+// Add a breadcrumb for future events
+Sentry.addBreadcrumb({
+ message: 'My Breadcrumb',
+ // ...
+});
+
+// Capture exceptions, messages or manual events
+Sentry.captureMessage('Hello, world!');
+Sentry.captureException(new Error('Good bye'));
+Sentry.captureEvent({
+ message: 'Manual',
+ stacktrace: [
+ // ...
+ ],
+});
+```
+
+
+
diff --git a/packages/deno/jest.config.js b/packages/deno/jest.config.js
new file mode 100644
index 000000000000..24f49ab59a4c
--- /dev/null
+++ b/packages/deno/jest.config.js
@@ -0,0 +1 @@
+module.exports = require('../../jest/jest.config.js');
diff --git a/packages/deno/package.json b/packages/deno/package.json
new file mode 100644
index 000000000000..be3a7aa7ca9a
--- /dev/null
+++ b/packages/deno/package.json
@@ -0,0 +1,64 @@
+{
+ "name": "@sentry/deno",
+ "version": "7.73.0",
+ "description": "Official Sentry SDK for Deno",
+ "repository": "git://github.com/getsentry/sentry-javascript.git",
+ "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/deno",
+ "author": "Sentry",
+ "license": "MIT",
+ "main": "build/index.js",
+ "module": "build/index.js",
+ "types": "build/index.d.ts",
+ "private": true,
+ "publishConfig": {
+ "access": "public"
+ },
+ "dependencies": {
+ "@sentry/core": "7.73.0",
+ "@sentry/browser": "7.73.0",
+ "@sentry/types": "7.73.0",
+ "@sentry/utils": "7.73.0",
+ "lru_map": "^0.3.3"
+ },
+ "devDependencies": {
+ "@types/node": "20.8.2",
+ "@rollup/plugin-commonjs": "^25.0.5",
+ "@rollup/plugin-typescript": "^11.1.5",
+ "rollup-plugin-dts": "^6.1.0"
+ },
+ "scripts": {
+ "deno-types": "node ./scripts/download-deno-types.mjs",
+ "build": "run-s build:transpile build:types",
+ "build:dev": "yarn build",
+ "build:transpile": "yarn deno-types && rollup -c rollup.config.js",
+ "build:types": "run-s deno-types build:types:tsc build:types:bundle",
+ "build:types:tsc": "tsc -p tsconfig.types.json",
+ "build:types:bundle": "rollup -c rollup.types.config.js",
+ "circularDepCheck": "madge --circular src/index.ts",
+ "clean": "rimraf build coverage",
+ "prefix": "yarn deno-types",
+ "fix": "run-s fix:eslint fix:prettier",
+ "fix:eslint": "eslint . --format stylish --fix",
+ "fix:prettier": "prettier --write \"{src,test,scripts}/**/**.ts\"",
+ "prelint": "yarn deno-types",
+ "lint": "run-s lint:prettier lint:eslint",
+ "lint:eslint": "eslint . --format stylish",
+ "lint:prettier": "prettier --check \"{src,test,scripts}/**/**.ts\"",
+ "install:deno": "node ./scripts/install-deno.mjs",
+ "test": "run-s deno-types install:deno test:types test:unit",
+ "test:types": "deno check ./build/index.js",
+ "test:unit": "deno test --allow-read --allow-run",
+ "test:unit:update": "deno test --allow-read --allow-write --allow-run -- --update"
+ },
+ "volta": {
+ "extends": "../../package.json"
+ },
+ "sideEffects": false,
+ "madge": {
+ "detectiveOptions": {
+ "ts": {
+ "skipTypeImports": true
+ }
+ }
+ }
+}
diff --git a/packages/deno/rollup.config.js b/packages/deno/rollup.config.js
new file mode 100644
index 000000000000..48123037a596
--- /dev/null
+++ b/packages/deno/rollup.config.js
@@ -0,0 +1,24 @@
+import nodeResolve from '@rollup/plugin-node-resolve';
+import commonjs from '@rollup/plugin-commonjs';
+import sucrase from '@rollup/plugin-sucrase';
+
+export default {
+ input: ['src/index.ts'],
+ output: {
+ dir: 'build',
+ sourcemap: true,
+ preserveModules: false,
+ strict: false,
+ freeze: false,
+ interop: 'auto',
+ format: 'esm',
+ banner: '/// ',
+ },
+ plugins: [
+ nodeResolve({
+ extensions: ['.mjs', '.js', '.json', '.node', '.ts', '.tsx'],
+ }),
+ commonjs(),
+ sucrase({ transforms: ['typescript'] }),
+ ],
+};
diff --git a/packages/deno/rollup.types.config.js b/packages/deno/rollup.types.config.js
new file mode 100644
index 000000000000..d8123b6c5cd3
--- /dev/null
+++ b/packages/deno/rollup.types.config.js
@@ -0,0 +1,17 @@
+import dts from 'rollup-plugin-dts';
+
+export default {
+ input: './build/index.d.ts',
+ output: [{ file: 'build/index.d.ts', format: 'es' }],
+ plugins: [
+ dts({ respectExternal: true }),
+ // The bundled types contain a declaration for the __DEBUG_BUILD__ global
+ // This can result in errors about duplicate global declarations so we strip it out!
+ {
+ name: 'strip-global',
+ renderChunk(code) {
+ return { code: code.replace(/declare global \{\s*const __DEBUG_BUILD__: boolean;\s*\}/g, '') };
+ },
+ },
+ ],
+};
diff --git a/packages/deno/scripts/download-deno-types.mjs b/packages/deno/scripts/download-deno-types.mjs
new file mode 100644
index 000000000000..33bdfcf5ebb7
--- /dev/null
+++ b/packages/deno/scripts/download-deno-types.mjs
@@ -0,0 +1,7 @@
+import { writeFileSync, existsSync } from 'fs';
+import { download } from './download.mjs';
+
+if (!existsSync('lib.deno.d.ts')) {
+ const code = await download('https://github.com/denoland/deno/releases/download/v1.37.1/lib.deno.d.ts');
+ writeFileSync('lib.deno.d.ts', code);
+}
diff --git a/packages/deno/scripts/download.mjs b/packages/deno/scripts/download.mjs
new file mode 100644
index 000000000000..25bcc39f583a
--- /dev/null
+++ b/packages/deno/scripts/download.mjs
@@ -0,0 +1,10 @@
+/** Download a url to a string */
+export async function download(url) {
+ try {
+ return await fetch(url).then(res => res.text());
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.error('Failed to download', url, e);
+ process.exit(1);
+ }
+}
diff --git a/packages/deno/scripts/install-deno.mjs b/packages/deno/scripts/install-deno.mjs
new file mode 100644
index 000000000000..aa7235a278ff
--- /dev/null
+++ b/packages/deno/scripts/install-deno.mjs
@@ -0,0 +1,26 @@
+import { execSync } from 'child_process';
+
+import { download } from './download.mjs';
+
+try {
+ execSync('deno --version', { stdio: 'inherit' });
+} catch (_) {
+ // eslint-disable-next-line no-console
+ console.error('Deno is not installed. Installing...');
+ if (process.platform === 'win32') {
+ // TODO
+ // eslint-disable-next-line no-console
+ console.error('Please install Deno manually: https://docs.deno.com/runtime/manual/getting_started/installation');
+ process.exit(1);
+ } else {
+ const script = await download('https://deno.land/x/install/install.sh');
+
+ try {
+ execSync(script, { stdio: 'inherit' });
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.error('Failed to install Deno', e);
+ process.exit(1);
+ }
+ }
+}
diff --git a/packages/deno/src/client.ts b/packages/deno/src/client.ts
new file mode 100644
index 000000000000..3eb7db655428
--- /dev/null
+++ b/packages/deno/src/client.ts
@@ -0,0 +1,44 @@
+import type { ServerRuntimeClientOptions } from '@sentry/core';
+import { SDK_VERSION, ServerRuntimeClient } from '@sentry/core';
+
+import type { DenoClientOptions } from './types';
+
+function getHostName(): string | undefined {
+ const result = Deno.permissions.querySync({ name: 'sys', kind: 'hostname' });
+ return result.state === 'granted' ? Deno.hostname() : undefined;
+}
+
+/**
+ * The Sentry Deno SDK Client.
+ *
+ * @see DenoClientOptions for documentation on configuration options.
+ * @see SentryClient for usage documentation.
+ */
+export class DenoClient extends ServerRuntimeClient {
+ /**
+ * Creates a new Deno SDK instance.
+ * @param options Configuration options for this SDK.
+ */
+ public constructor(options: DenoClientOptions) {
+ options._metadata = options._metadata || {};
+ options._metadata.sdk = options._metadata.sdk || {
+ name: 'sentry.javascript.deno',
+ packages: [
+ {
+ name: 'denoland:sentry',
+ version: SDK_VERSION,
+ },
+ ],
+ version: SDK_VERSION,
+ };
+
+ const clientOptions: ServerRuntimeClientOptions = {
+ ...options,
+ platform: 'deno',
+ runtime: { name: 'deno', version: Deno.version.deno },
+ serverName: options.serverName || getHostName(),
+ };
+
+ super(clientOptions);
+ }
+}
diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts
new file mode 100644
index 000000000000..6a92adea2513
--- /dev/null
+++ b/packages/deno/src/index.ts
@@ -0,0 +1,77 @@
+export type {
+ Breadcrumb,
+ BreadcrumbHint,
+ PolymorphicRequest,
+ Request,
+ SdkInfo,
+ Event,
+ EventHint,
+ Exception,
+ Session,
+ // eslint-disable-next-line deprecation/deprecation
+ Severity,
+ SeverityLevel,
+ Span,
+ StackFrame,
+ Stacktrace,
+ Thread,
+ Transaction,
+ User,
+} from '@sentry/types';
+export type { AddRequestDataToEventOptions } from '@sentry/utils';
+
+export type { DenoOptions } from './types';
+
+export {
+ addGlobalEventProcessor,
+ addBreadcrumb,
+ captureException,
+ captureEvent,
+ captureMessage,
+ close,
+ configureScope,
+ createTransport,
+ extractTraceparentData,
+ flush,
+ getActiveTransaction,
+ getHubFromCarrier,
+ getCurrentHub,
+ Hub,
+ lastEventId,
+ makeMain,
+ runWithAsyncContext,
+ Scope,
+ startTransaction,
+ SDK_VERSION,
+ setContext,
+ setExtra,
+ setExtras,
+ setTag,
+ setTags,
+ setUser,
+ spanStatusfromHttpCode,
+ trace,
+ withScope,
+ captureCheckIn,
+ setMeasurement,
+ getActiveSpan,
+ startSpan,
+ startInactiveSpan,
+ startSpanManual,
+} from '@sentry/core';
+export type { SpanStatusType } from '@sentry/core';
+
+export { DenoClient } from './client';
+
+export { defaultIntegrations, init } from './sdk';
+
+import { Integrations as CoreIntegrations } from '@sentry/core';
+
+import * as DenoIntegrations from './integrations';
+
+const INTEGRATIONS = {
+ ...CoreIntegrations,
+ ...DenoIntegrations,
+};
+
+export { INTEGRATIONS as Integrations };
diff --git a/packages/deno/src/integrations/context.ts b/packages/deno/src/integrations/context.ts
new file mode 100644
index 000000000000..49269c81be4e
--- /dev/null
+++ b/packages/deno/src/integrations/context.ts
@@ -0,0 +1,64 @@
+import type { Event, EventProcessor, Integration } from '@sentry/types';
+
+function getOSName(): string {
+ switch (Deno.build.os) {
+ case 'darwin':
+ return 'macOS';
+ case 'linux':
+ return 'Linux';
+ case 'windows':
+ return 'Windows';
+ default:
+ return Deno.build.os;
+ }
+}
+
+function getOSRelease(): string | undefined {
+ return Deno.permissions.querySync({ name: 'sys', kind: 'osRelease' }).state === 'granted'
+ ? Deno.osRelease()
+ : undefined;
+}
+
+async function denoRuntime(event: Event): Promise {
+ event.contexts = {
+ ...{
+ app: {
+ app_start_time: new Date(Date.now() - performance.now()).toISOString(),
+ },
+ device: {
+ arch: Deno.build.arch,
+ // eslint-disable-next-line no-restricted-globals
+ processor_count: navigator.hardwareConcurrency,
+ },
+ os: {
+ name: getOSName(),
+ version: getOSRelease(),
+ },
+ v8: {
+ name: 'v8',
+ version: Deno.version.v8,
+ },
+ typescript: {
+ name: 'TypeScript',
+ version: Deno.version.typescript,
+ },
+ },
+ ...event.contexts,
+ };
+
+ return event;
+}
+
+/** Adds Electron context to events. */
+export class DenoContext implements Integration {
+ /** @inheritDoc */
+ public static id = 'DenoContext';
+
+ /** @inheritDoc */
+ public name: string = DenoContext.id;
+
+ /** @inheritDoc */
+ public setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void): void {
+ addGlobalEventProcessor(async (event: Event) => denoRuntime(event));
+ }
+}
diff --git a/packages/deno/src/integrations/contextlines.ts b/packages/deno/src/integrations/contextlines.ts
new file mode 100644
index 000000000000..47cd3a09218d
--- /dev/null
+++ b/packages/deno/src/integrations/contextlines.ts
@@ -0,0 +1,116 @@
+import type { Event, EventProcessor, Integration, StackFrame } from '@sentry/types';
+import { addContextToFrame } from '@sentry/utils';
+import { LRUMap } from 'lru_map';
+
+const FILE_CONTENT_CACHE = new LRUMap(100);
+const DEFAULT_LINES_OF_CONTEXT = 7;
+
+/**
+ * Resets the file cache. Exists for testing purposes.
+ * @hidden
+ */
+export function resetFileContentCache(): void {
+ FILE_CONTENT_CACHE.clear();
+}
+
+/**
+ * Reads file contents and caches them in a global LRU cache.
+ *
+ * @param filename filepath to read content from.
+ */
+async function readSourceFile(filename: string): Promise {
+ const cachedFile = FILE_CONTENT_CACHE.get(filename);
+ // We have a cache hit
+ if (cachedFile !== undefined) {
+ return cachedFile;
+ }
+
+ let content: string | null = null;
+ try {
+ content = await Deno.readTextFile(filename);
+ } catch (_) {
+ //
+ }
+
+ FILE_CONTENT_CACHE.set(filename, content);
+ return content;
+}
+
+interface ContextLinesOptions {
+ /**
+ * Sets the number of context lines for each frame when loading a file.
+ * Defaults to 7.
+ *
+ * Set to 0 to disable loading and inclusion of source files.
+ */
+ frameContextLines?: number;
+}
+
+/** Add node modules / packages to the event */
+export class ContextLines implements Integration {
+ /**
+ * @inheritDoc
+ */
+ public static id = 'ContextLines';
+
+ /**
+ * @inheritDoc
+ */
+ public name: string = ContextLines.id;
+
+ public constructor(private readonly _options: ContextLinesOptions = {}) {}
+
+ /** Get's the number of context lines to add */
+ private get _contextLines(): number {
+ return this._options.frameContextLines !== undefined ? this._options.frameContextLines : DEFAULT_LINES_OF_CONTEXT;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void): void {
+ addGlobalEventProcessor(event => this.addSourceContext(event));
+ }
+
+ /** Processes an event and adds context lines */
+ public async addSourceContext(event: Event): Promise {
+ if (this._contextLines > 0 && event.exception && event.exception.values) {
+ for (const exception of event.exception.values) {
+ if (exception.stacktrace && exception.stacktrace.frames) {
+ await this.addSourceContextToFrames(exception.stacktrace.frames);
+ }
+ }
+ }
+
+ return event;
+ }
+
+ /** Adds context lines to frames */
+ public async addSourceContextToFrames(frames: StackFrame[]): Promise {
+ const contextLines = this._contextLines;
+
+ for (const frame of frames) {
+ // Only add context if we have a filename and it hasn't already been added
+ if (frame.filename && frame.in_app && frame.context_line === undefined) {
+ const permission = await Deno.permissions.query({
+ name: 'read',
+ path: frame.filename,
+ });
+
+ if (permission.state == 'granted') {
+ const sourceFile = await readSourceFile(frame.filename);
+
+ if (sourceFile) {
+ try {
+ const lines = sourceFile.split('\n');
+ addContextToFrame(lines, frame, contextLines);
+ } catch (_) {
+ // anomaly, being defensive in case
+ // unlikely to ever happen in practice but can definitely happen in theory
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/deno/src/integrations/globalhandlers.ts b/packages/deno/src/integrations/globalhandlers.ts
new file mode 100644
index 000000000000..7e4d2e003673
--- /dev/null
+++ b/packages/deno/src/integrations/globalhandlers.ts
@@ -0,0 +1,165 @@
+import type { ServerRuntimeClient } from '@sentry/core';
+import { flush, getCurrentHub } from '@sentry/core';
+import type { Event, EventHint, Hub, Integration, Primitive, StackParser } from '@sentry/types';
+import { addExceptionMechanism, eventFromUnknownInput, isPrimitive } from '@sentry/utils';
+
+type GlobalHandlersIntegrationsOptionKeys = 'error' | 'unhandledrejection';
+
+/** JSDoc */
+type GlobalHandlersIntegrations = Record;
+
+let isExiting = false;
+
+/** Global handlers */
+export class GlobalHandlers implements Integration {
+ /**
+ * @inheritDoc
+ */
+ public static id = 'GlobalHandlers';
+
+ /**
+ * @inheritDoc
+ */
+ public name: string = GlobalHandlers.id;
+
+ /** JSDoc */
+ private readonly _options: GlobalHandlersIntegrations;
+
+ /**
+ * Stores references functions to installing handlers. Will set to undefined
+ * after they have been run so that they are not used twice.
+ */
+ private _installFunc: Record void) | undefined> = {
+ error: installGlobalErrorHandler,
+ unhandledrejection: installGlobalUnhandledRejectionHandler,
+ };
+
+ /** JSDoc */
+ public constructor(options?: GlobalHandlersIntegrations) {
+ this._options = {
+ error: true,
+ unhandledrejection: true,
+ ...options,
+ };
+ }
+ /**
+ * @inheritDoc
+ */
+ public setupOnce(): void {
+ const options = this._options;
+
+ // We can disable guard-for-in as we construct the options object above + do checks against
+ // `this._installFunc` for the property.
+ // eslint-disable-next-line guard-for-in
+ for (const key in options) {
+ const installFunc = this._installFunc[key as GlobalHandlersIntegrationsOptionKeys];
+ if (installFunc && options[key as GlobalHandlersIntegrationsOptionKeys]) {
+ installFunc();
+ this._installFunc[key as GlobalHandlersIntegrationsOptionKeys] = undefined;
+ }
+ }
+ }
+}
+
+function installGlobalErrorHandler(): void {
+ globalThis.addEventListener('error', data => {
+ if (isExiting) {
+ return;
+ }
+
+ const [hub, stackParser] = getHubAndOptions();
+ const { message, error } = data;
+
+ const event = eventFromUnknownInput(getCurrentHub, stackParser, error || message);
+
+ event.level = 'fatal';
+
+ addMechanismAndCapture(hub, error, event, 'error');
+
+ // Stop the app from exiting for now
+ data.preventDefault();
+ isExiting = true;
+
+ void flush().then(() => {
+ // rethrow to replicate Deno default behavior
+ throw error;
+ });
+ });
+}
+
+function installGlobalUnhandledRejectionHandler(): void {
+ globalThis.addEventListener('unhandledrejection', (e: PromiseRejectionEvent) => {
+ if (isExiting) {
+ return;
+ }
+
+ const [hub, stackParser] = getHubAndOptions();
+ let error = e;
+
+ // dig the object of the rejection out of known event types
+ try {
+ if ('reason' in e) {
+ error = e.reason;
+ }
+ } catch (_oO) {
+ // no-empty
+ }
+
+ const event = isPrimitive(error)
+ ? eventFromRejectionWithPrimitive(error)
+ : eventFromUnknownInput(getCurrentHub, stackParser, error, undefined);
+
+ event.level = 'fatal';
+
+ addMechanismAndCapture(hub, error as unknown as Error, event, 'unhandledrejection');
+
+ // Stop the app from exiting for now
+ e.preventDefault();
+ isExiting = true;
+
+ void flush().then(() => {
+ // rethrow to replicate Deno default behavior
+ throw error;
+ });
+ });
+}
+
+/**
+ * Create an event from a promise rejection where the `reason` is a primitive.
+ *
+ * @param reason: The `reason` property of the promise rejection
+ * @returns An Event object with an appropriate `exception` value
+ */
+function eventFromRejectionWithPrimitive(reason: Primitive): Event {
+ return {
+ exception: {
+ values: [
+ {
+ type: 'UnhandledRejection',
+ // String() is needed because the Primitive type includes symbols (which can't be automatically stringified)
+ value: `Non-Error promise rejection captured with value: ${String(reason)}`,
+ },
+ ],
+ },
+ };
+}
+
+function addMechanismAndCapture(hub: Hub, error: EventHint['originalException'], event: Event, type: string): void {
+ addExceptionMechanism(event, {
+ handled: false,
+ type,
+ });
+ hub.captureEvent(event, {
+ originalException: error,
+ });
+}
+
+function getHubAndOptions(): [Hub, StackParser] {
+ const hub = getCurrentHub();
+ const client = hub.getClient();
+ const options = (client && client.getOptions()) || {
+ stackParser: () => [],
+ attachStacktrace: false,
+ };
+ return [hub, options.stackParser];
+}
diff --git a/packages/deno/src/integrations/index.ts b/packages/deno/src/integrations/index.ts
new file mode 100644
index 000000000000..97e439649bfc
--- /dev/null
+++ b/packages/deno/src/integrations/index.ts
@@ -0,0 +1,4 @@
+export { DenoContext } from './context';
+export { GlobalHandlers } from './globalhandlers';
+export { NormalizePaths } from './normalizepaths';
+export { ContextLines } from './contextlines';
diff --git a/packages/deno/src/integrations/normalizepaths.ts b/packages/deno/src/integrations/normalizepaths.ts
new file mode 100644
index 000000000000..bf8a3986c93d
--- /dev/null
+++ b/packages/deno/src/integrations/normalizepaths.ts
@@ -0,0 +1,100 @@
+import type { Event, EventProcessor, Integration } from '@sentry/types';
+import { createStackParser, dirname, nodeStackLineParser } from '@sentry/utils';
+
+function appRootFromErrorStack(error: Error): string | undefined {
+ // We know at the other end of the stack from here is the entry point that called 'init'
+ // We assume that this stacktrace will traverse the root of the app
+ const frames = createStackParser(nodeStackLineParser())(error.stack || '');
+
+ const paths = frames
+ // We're only interested in frames that are in_app with filenames
+ .filter(f => f.in_app && f.filename)
+ .map(
+ f =>
+ (f.filename as string)
+ .replace(/^[A-Z]:/, '') // remove Windows-style prefix
+ .replace(/\\/g, '/') // replace all `\` instances with `/`
+ .split('/')
+ .filter(seg => seg !== ''), // remove empty segments
+ ) as string[][];
+
+ if (paths.length == 0) {
+ return undefined;
+ }
+
+ if (paths.length == 1) {
+ // Assume the single file is in the root
+ return dirname(paths[0].join('/'));
+ }
+
+ // Iterate over the paths and bail out when they no longer have a common root
+ let i = 0;
+ while (paths[0][i] && paths.every(w => w[i] === paths[0][i])) {
+ i++;
+ }
+
+ return paths[0].slice(0, i).join('/');
+}
+
+function getCwd(): string | undefined {
+ // We don't want to prompt for permissions so we only get the cwd if
+ // permissions are already granted
+ const permission = Deno.permissions.querySync({ name: 'read', path: './' });
+
+ try {
+ if (permission.state == 'granted') {
+ return Deno.cwd();
+ }
+ } catch (_) {
+ //
+ }
+
+ return undefined;
+}
+
+// Cached here
+let appRoot: string | undefined;
+
+function getAppRoot(error: Error): string | undefined {
+ if (appRoot === undefined) {
+ appRoot = getCwd() || appRootFromErrorStack(error);
+ }
+
+ return appRoot;
+}
+
+/** Normalises paths to the app root directory. */
+export class NormalizePaths implements Integration {
+ /** @inheritDoc */
+ public static id = 'NormalizePaths';
+
+ /** @inheritDoc */
+ public name: string = NormalizePaths.id;
+
+ /** @inheritDoc */
+ public setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void): void {
+ // This error.stack hopefully contains paths that traverse the app cwd
+ const error = new Error();
+
+ addGlobalEventProcessor((event: Event): Event | null => {
+ const appRoot = getAppRoot(error);
+
+ if (appRoot) {
+ for (const exception of event.exception?.values || []) {
+ for (const frame of exception.stacktrace?.frames || []) {
+ if (frame.filename && frame.in_app) {
+ const startIndex = frame.filename.indexOf(appRoot);
+
+ if (startIndex > -1) {
+ const endIndex = startIndex + appRoot.length;
+ frame.filename = `app://${frame.filename.substring(endIndex)}`;
+ }
+ }
+ }
+ }
+ }
+
+ return event;
+ });
+ }
+}
diff --git a/packages/deno/src/sdk.ts b/packages/deno/src/sdk.ts
new file mode 100644
index 000000000000..cff16148453e
--- /dev/null
+++ b/packages/deno/src/sdk.ts
@@ -0,0 +1,102 @@
+import { Breadcrumbs, Dedupe, LinkedErrors } from '@sentry/browser';
+import type { ServerRuntimeClientOptions } from '@sentry/core';
+import { getIntegrationsToSetup, initAndBind, Integrations as CoreIntegrations } from '@sentry/core';
+import type { StackParser } from '@sentry/types';
+import { createStackParser, nodeStackLineParser, stackParserFromStackParserOptions } from '@sentry/utils';
+
+import { DenoClient } from './client';
+import { ContextLines, DenoContext, GlobalHandlers, NormalizePaths } from './integrations';
+import { makeFetchTransport } from './transports';
+import type { DenoOptions } from './types';
+
+export const defaultIntegrations = [
+ // Common
+ new CoreIntegrations.InboundFilters(),
+ new CoreIntegrations.FunctionToString(),
+ // From Browser
+ new Dedupe(),
+ new LinkedErrors(),
+ new Breadcrumbs({
+ dom: false,
+ history: false,
+ xhr: false,
+ }),
+ // Deno Specific
+ new DenoContext(),
+ new ContextLines(),
+ new NormalizePaths(),
+ new GlobalHandlers(),
+];
+
+const defaultStackParser: StackParser = createStackParser(nodeStackLineParser());
+
+/**
+ * The Sentry Deno SDK Client.
+ *
+ * To use this SDK, call the {@link init} function as early as possible in the
+ * main entry module. To set context information or send manual events, use the
+ * provided methods.
+ *
+ * @example
+ * ```
+ *
+ * import { init } from 'npm:@sentry/deno';
+ *
+ * init({
+ * dsn: '__DSN__',
+ * // ...
+ * });
+ * ```
+ *
+ * @example
+ * ```
+ *
+ * import { configureScope } from 'npm:@sentry/deno';
+ * configureScope((scope: Scope) => {
+ * scope.setExtra({ battery: 0.7 });
+ * scope.setTag({ user_mode: 'admin' });
+ * scope.setUser({ id: '4711' });
+ * });
+ * ```
+ *
+ * @example
+ * ```
+ *
+ * import { addBreadcrumb } from 'npm:@sentry/deno';
+ * addBreadcrumb({
+ * message: 'My Breadcrumb',
+ * // ...
+ * });
+ * ```
+ *
+ * @example
+ * ```
+ *
+ * import * as Sentry from 'npm:@sentry/deno';
+ * Sentry.captureMessage('Hello, world!');
+ * Sentry.captureException(new Error('Good bye'));
+ * Sentry.captureEvent({
+ * message: 'Manual',
+ * stacktrace: [
+ * // ...
+ * ],
+ * });
+ * ```
+ *
+ * @see {@link DenoOptions} for documentation on configuration options.
+ */
+export function init(options: DenoOptions = {}): void {
+ options.defaultIntegrations =
+ options.defaultIntegrations === false
+ ? []
+ : [...(Array.isArray(options.defaultIntegrations) ? options.defaultIntegrations : defaultIntegrations)];
+
+ const clientOptions: ServerRuntimeClientOptions = {
+ ...options,
+ stackParser: stackParserFromStackParserOptions(options.stackParser || defaultStackParser),
+ integrations: getIntegrationsToSetup(options),
+ transport: options.transport || makeFetchTransport,
+ };
+
+ initAndBind(DenoClient, clientOptions);
+}
diff --git a/packages/deno/src/transports/index.ts b/packages/deno/src/transports/index.ts
new file mode 100644
index 000000000000..62da327c5d83
--- /dev/null
+++ b/packages/deno/src/transports/index.ts
@@ -0,0 +1,46 @@
+import { createTransport } from '@sentry/core';
+import type { BaseTransportOptions, Transport, TransportMakeRequestResponse, TransportRequest } from '@sentry/types';
+import { rejectedSyncPromise } from '@sentry/utils';
+
+export interface DenoTransportOptions extends BaseTransportOptions {
+ /** Custom headers for the transport. Used by the XHRTransport and FetchTransport */
+ headers?: { [key: string]: string };
+}
+
+/**
+ * Creates a Transport that uses the Fetch API to send events to Sentry.
+ */
+export function makeFetchTransport(options: DenoTransportOptions): Transport {
+ const url = new URL(options.url);
+
+ if (Deno.permissions.querySync({ name: 'net', host: url.host }).state !== 'granted') {
+ // eslint-disable-next-line no-console
+ console.warn(`Sentry SDK requires 'net' permission to send events.
+Run with '--allow-net=${url.host}' to grant the requires permissions.`);
+ }
+
+ function makeRequest(request: TransportRequest): PromiseLike {
+ const requestOptions: RequestInit = {
+ body: request.body,
+ method: 'POST',
+ referrerPolicy: 'origin',
+ headers: options.headers,
+ };
+
+ try {
+ return fetch(options.url, requestOptions).then(response => {
+ return {
+ statusCode: response.status,
+ headers: {
+ 'x-sentry-rate-limits': response.headers.get('X-Sentry-Rate-Limits'),
+ 'retry-after': response.headers.get('Retry-After'),
+ },
+ };
+ });
+ } catch (e) {
+ return rejectedSyncPromise(e);
+ }
+ }
+
+ return createTransport(options, makeRequest);
+}
diff --git a/packages/deno/src/types.ts b/packages/deno/src/types.ts
new file mode 100644
index 000000000000..50310589666a
--- /dev/null
+++ b/packages/deno/src/types.ts
@@ -0,0 +1,59 @@
+import type { ClientOptions, Options, TracePropagationTargets } from '@sentry/types';
+
+import type { DenoTransportOptions } from './transports';
+
+export interface BaseDenoOptions {
+ /**
+ * List of strings/regex controlling to which outgoing requests
+ * the SDK will attach tracing headers.
+ *
+ * By default the SDK will attach those headers to all outgoing
+ * requests. If this option is provided, the SDK will match the
+ * request URL of outgoing requests against the items in this
+ * array, and only attach tracing headers if a match was found.
+ *
+ * @example
+ * ```js
+ * Sentry.init({
+ * tracePropagationTargets: ['api.site.com'],
+ * });
+ * ```
+ */
+ tracePropagationTargets?: TracePropagationTargets;
+
+ /** Sets an optional server name (device name) */
+ serverName?: string;
+
+ // TODO (v8): Remove this in v8
+ /**
+ * @deprecated Moved to constructor options of the `Http` and `Undici` integration.
+ * @example
+ * ```js
+ * Sentry.init({
+ * integrations: [
+ * new Sentry.Integrations.Http({
+ * tracing: {
+ * shouldCreateSpanForRequest: (url: string) => false,
+ * }
+ * });
+ * ],
+ * });
+ * ```
+ */
+ shouldCreateSpanForRequest?(this: void, url: string): boolean;
+
+ /** Callback that is executed when a fatal global error occurs. */
+ onFatalError?(this: void, error: Error): void;
+}
+
+/**
+ * Configuration options for the Sentry Deno SDK
+ * @see @sentry/types Options for more information.
+ */
+export interface DenoOptions extends Options, BaseDenoOptions {}
+
+/**
+ * Configuration options for the Sentry Deno SDK Client class
+ * @see DenoClient for more information.
+ */
+export interface DenoClientOptions extends ClientOptions, BaseDenoOptions {}
diff --git a/packages/deno/test/__snapshots__/mod.test.ts.snap b/packages/deno/test/__snapshots__/mod.test.ts.snap
new file mode 100644
index 000000000000..749d3ce9d238
--- /dev/null
+++ b/packages/deno/test/__snapshots__/mod.test.ts.snap
@@ -0,0 +1,222 @@
+export const snapshot = {};
+
+snapshot[`captureException 1`] = `
+{
+ contexts: {
+ app: {
+ app_start_time: "{{time}}",
+ },
+ device: {
+ arch: "{{arch}}",
+ processor_count: 0,
+ },
+ os: {
+ name: "{{platform}}",
+ },
+ runtime: {
+ name: "deno",
+ version: "1.37.1",
+ },
+ trace: {
+ span_id: "{{id}}",
+ trace_id: "{{id}}",
+ },
+ typescript: {
+ name: "TypeScript",
+ version: "{{version}}",
+ },
+ v8: {
+ name: "v8",
+ version: "{{version}}",
+ },
+ },
+ environment: "production",
+ event_id: "{{id}}",
+ exception: {
+ values: [
+ {
+ mechanism: {
+ handled: true,
+ type: "generic",
+ },
+ stacktrace: {
+ frames: [
+ {
+ colno: 20,
+ filename: "ext:cli/40_testing.js",
+ function: "outerWrapped",
+ in_app: false,
+ lineno: 488,
+ },
+ {
+ colno: 33,
+ filename: "ext:cli/40_testing.js",
+ function: "exitSanitizer",
+ in_app: false,
+ lineno: 474,
+ },
+ {
+ colno: 31,
+ filename: "ext:cli/40_testing.js",
+ function: "resourceSanitizer",
+ in_app: false,
+ lineno: 425,
+ },
+ {
+ colno: 33,
+ filename: "ext:cli/40_testing.js",
+ function: "asyncOpSanitizer",
+ in_app: false,
+ lineno: 192,
+ },
+ {
+ colno: 11,
+ filename: "ext:cli/40_testing.js",
+ function: "innerWrapped",
+ in_app: false,
+ lineno: 543,
+ },
+ {
+ colno: 24,
+ context_line: " hub.captureException(something());",
+ filename: "app:///test/mod.test.ts",
+ function: "",
+ in_app: true,
+ lineno: 43,
+ post_context: [
+ "",
+ " await delay(200);",
+ " await assertSnapshot(t, ev);",
+ "});",
+ "",
+ "Deno.test('captureMessage', async t => {",
+ " let ev: Event | undefined;",
+ ],
+ pre_context: [
+ " ev = event;",
+ " });",
+ "",
+ " function something() {",
+ " return new Error('Some unhandled error');",
+ " }",
+ "",
+ ],
+ },
+ {
+ colno: 12,
+ context_line: " return new Error('Some unhandled error');",
+ filename: "app:///test/mod.test.ts",
+ function: "something",
+ in_app: true,
+ lineno: 40,
+ post_context: [
+ " }",
+ "",
+ " hub.captureException(something());",
+ "",
+ " await delay(200);",
+ " await assertSnapshot(t, ev);",
+ "});",
+ ],
+ pre_context: [
+ "Deno.test('captureException', async t => {",
+ " let ev: Event | undefined;",
+ " const [hub] = getTestClient(event => {",
+ " ev = event;",
+ " });",
+ "",
+ " function something() {",
+ ],
+ },
+ ],
+ },
+ type: "Error",
+ value: "Some unhandled error",
+ },
+ ],
+ },
+ platform: "deno",
+ sdk: {
+ integrations: [
+ "InboundFilters",
+ "FunctionToString",
+ "Dedupe",
+ "LinkedErrors",
+ "Breadcrumbs",
+ "DenoContext",
+ "ContextLines",
+ "NormalizePaths",
+ "GlobalHandlers",
+ ],
+ name: "sentry.javascript.deno",
+ packages: [
+ {
+ name: "denoland:sentry",
+ version: "{{version}}",
+ },
+ ],
+ version: "{{version}}",
+ },
+ timestamp: 0,
+}
+`;
+
+snapshot[`captureMessage 1`] = `
+{
+ contexts: {
+ app: {
+ app_start_time: "{{time}}",
+ },
+ device: {
+ arch: "{{arch}}",
+ processor_count: 0,
+ },
+ os: {
+ name: "{{platform}}",
+ },
+ runtime: {
+ name: "deno",
+ version: "1.37.1",
+ },
+ trace: {
+ span_id: "{{id}}",
+ trace_id: "{{id}}",
+ },
+ typescript: {
+ name: "TypeScript",
+ version: "{{version}}",
+ },
+ v8: {
+ name: "v8",
+ version: "{{version}}",
+ },
+ },
+ environment: "production",
+ event_id: "{{id}}",
+ level: "info",
+ message: "Some error message",
+ platform: "deno",
+ sdk: {
+ integrations: [
+ "InboundFilters",
+ "FunctionToString",
+ "Dedupe",
+ "LinkedErrors",
+ "Breadcrumbs",
+ "DenoContext",
+ "ContextLines",
+ "NormalizePaths",
+ "GlobalHandlers",
+ ],
+ name: "sentry.javascript.deno",
+ packages: [
+ {
+ name: "denoland:sentry",
+ version: "{{version}}",
+ },
+ ],
+ version: "{{version}}",
+ },
+ timestamp: 0,
+}
+`;
diff --git a/packages/deno/test/example.ts b/packages/deno/test/example.ts
new file mode 100644
index 000000000000..6f93bd288afd
--- /dev/null
+++ b/packages/deno/test/example.ts
@@ -0,0 +1,14 @@
+import * as Sentry from '../build/index.js';
+
+Sentry.init({
+ dsn: 'https://1234@some-domain.com/4505526893805568',
+});
+
+Sentry.addBreadcrumb({ message: 'My Breadcrumb' });
+
+// eslint-disable-next-line no-console
+console.log('App has started');
+
+setTimeout(() => {
+ Deno.exit();
+}, 1_000);
diff --git a/packages/deno/test/mod.test.ts b/packages/deno/test/mod.test.ts
new file mode 100644
index 000000000000..1724125415cd
--- /dev/null
+++ b/packages/deno/test/mod.test.ts
@@ -0,0 +1,76 @@
+import { assertEquals } from 'https://deno.land/std@0.202.0/assert/assert_equals.ts';
+import { assertSnapshot } from 'https://deno.land/std@0.202.0/testing/snapshot.ts';
+import type { Event, Integration } from 'npm:@sentry/types';
+import { createStackParser, nodeStackLineParser } from 'npm:@sentry/utils';
+
+import { defaultIntegrations, DenoClient, Hub, Scope } from '../build/index.js';
+import { getNormalizedEvent } from './normalize.ts';
+import { makeTestTransport } from './transport.ts';
+
+function getTestClient(callback: (event?: Event) => void, integrations: Integration[] = []): [Hub, DenoClient] {
+ const client = new DenoClient({
+ dsn: 'https://233a45e5efe34c47a3536797ce15dafa@nothing.here/5650507',
+ debug: true,
+ integrations: [...defaultIntegrations, ...integrations],
+ stackParser: createStackParser(nodeStackLineParser()),
+ transport: makeTestTransport(envelope => {
+ callback(getNormalizedEvent(envelope));
+ }),
+ });
+
+ const scope = new Scope();
+ const hub = new Hub(client, scope);
+
+ return [hub, client];
+}
+
+function delay(time: number): Promise {
+ return new Promise(resolve => {
+ setTimeout(resolve, time);
+ });
+}
+
+Deno.test('captureException', async t => {
+ let ev: Event | undefined;
+ const [hub] = getTestClient(event => {
+ ev = event;
+ });
+
+ function something() {
+ return new Error('Some unhandled error');
+ }
+
+ hub.captureException(something());
+
+ await delay(200);
+ await assertSnapshot(t, ev);
+});
+
+Deno.test('captureMessage', async t => {
+ let ev: Event | undefined;
+ const [hub] = getTestClient(event => {
+ ev = event;
+ });
+
+ hub.captureMessage('Some error message');
+
+ await delay(200);
+ await assertSnapshot(t, ev);
+});
+
+Deno.test('App runs without errors', async _ => {
+ const cmd = new Deno.Command('deno', {
+ args: ['run', '--allow-net=some-domain.com', './test/example.ts'],
+ stdout: 'piped',
+ stderr: 'piped',
+ });
+
+ const output = await cmd.output();
+ assertEquals(output.success, true);
+
+ const td = new TextDecoder();
+ const outString = td.decode(output.stdout);
+ const errString = td.decode(output.stderr);
+ assertEquals(outString, 'App has started\n');
+ assertEquals(errString, '');
+});
diff --git a/packages/deno/test/normalize.ts b/packages/deno/test/normalize.ts
new file mode 100644
index 000000000000..b36cf4ac52a9
--- /dev/null
+++ b/packages/deno/test/normalize.ts
@@ -0,0 +1,207 @@
+/* eslint-disable complexity */
+import type { Envelope, Event, Session, Transaction } from 'npm:@sentry/types';
+import { forEachEnvelopeItem } from 'npm:@sentry/utils';
+
+type EventOrSession = Event | Transaction | Session;
+
+export function getNormalizedEvent(envelope: Envelope): Event | undefined {
+ let event: Event | undefined;
+
+ forEachEnvelopeItem(envelope, item => {
+ const [headers, body] = item;
+
+ if (headers.type === 'event') {
+ event = body as Event;
+ }
+ });
+
+ return normalize(event) as Event | undefined;
+}
+
+export function normalize(event: EventOrSession | undefined): EventOrSession | undefined {
+ if (event === undefined) {
+ return undefined;
+ }
+
+ if (eventIsSession(event)) {
+ return normalizeSession(event as Session);
+ } else {
+ return normalizeEvent(event as Event);
+ }
+}
+
+export function eventIsSession(data: EventOrSession): boolean {
+ return !!(data as Session)?.sid;
+}
+
+/**
+ * Normalizes a session so that in can be compared to an expected event
+ *
+ * All properties that are timestamps, versions, ids or variables that may vary
+ * by platform are replaced with placeholder strings
+ */
+function normalizeSession(session: Session): Session {
+ if (session.sid) {
+ session.sid = '{{id}}';
+ }
+
+ if (session.started) {
+ session.started = 0;
+ }
+
+ if (session.timestamp) {
+ session.timestamp = 0;
+ }
+
+ if (session.duration) {
+ session.duration = 0;
+ }
+
+ return session;
+}
+
+/**
+ * Normalizes an event so that in can be compared to an expected event
+ *
+ * All properties that are timestamps, versions, ids or variables that may vary
+ * by platform are replaced with placeholder strings
+ */
+function normalizeEvent(event: Event): Event {
+ if (event.sdk?.version) {
+ event.sdk.version = '{{version}}';
+ }
+
+ if (event?.sdk?.packages) {
+ for (const pkg of event?.sdk?.packages) {
+ if (pkg.version) {
+ pkg.version = '{{version}}';
+ }
+ }
+ }
+
+ if (event.contexts?.app?.app_start_time) {
+ event.contexts.app.app_start_time = '{{time}}';
+ }
+
+ if (event.contexts?.typescript?.version) {
+ event.contexts.typescript.version = '{{version}}';
+ }
+
+ if (event.contexts?.v8?.version) {
+ event.contexts.v8.version = '{{version}}';
+ }
+
+ if (event.contexts?.deno) {
+ if (event.contexts.deno?.version) {
+ event.contexts.deno.version = '{{version}}';
+ }
+ if (event.contexts.deno?.target) {
+ event.contexts.deno.target = '{{target}}';
+ }
+ }
+
+ if (event.contexts?.device?.arch) {
+ event.contexts.device.arch = '{{arch}}';
+ }
+
+ if (event.contexts?.device?.memory_size) {
+ event.contexts.device.memory_size = 0;
+ }
+
+ if (event.contexts?.device?.free_memory) {
+ event.contexts.device.free_memory = 0;
+ }
+
+ if (event.contexts?.device?.processor_count) {
+ event.contexts.device.processor_count = 0;
+ }
+
+ if (event.contexts?.device?.processor_frequency) {
+ event.contexts.device.processor_frequency = 0;
+ }
+
+ if (event.contexts?.device?.cpu_description) {
+ event.contexts.device.cpu_description = '{{cpu}}';
+ }
+
+ if (event.contexts?.device?.screen_resolution) {
+ event.contexts.device.screen_resolution = '{{screen}}';
+ }
+
+ if (event.contexts?.device?.screen_density) {
+ event.contexts.device.screen_density = 1;
+ }
+
+ if (event.contexts?.device?.language) {
+ event.contexts.device.language = '{{language}}';
+ }
+
+ if (event.contexts?.os?.name) {
+ event.contexts.os.name = '{{platform}}';
+ }
+
+ if (event.contexts?.os?.version) {
+ event.contexts.os.version = '{{version}}';
+ }
+
+ if (event.contexts?.trace) {
+ event.contexts.trace.span_id = '{{id}}';
+ event.contexts.trace.trace_id = '{{id}}';
+ delete event.contexts.trace.tags;
+ }
+
+ if (event.start_timestamp) {
+ event.start_timestamp = 0;
+ }
+
+ if (event.exception?.values?.[0].stacktrace?.frames) {
+ // Exlcude Deno frames since these may change between versions
+ event.exception.values[0].stacktrace.frames = event.exception.values[0].stacktrace.frames.filter(
+ frame => !frame.filename?.includes('deno:'),
+ );
+ }
+
+ event.timestamp = 0;
+ // deno-lint-ignore no-explicit-any
+ if ((event as any).start_timestamp) {
+ // deno-lint-ignore no-explicit-any
+ (event as any).start_timestamp = 0;
+ }
+
+ event.event_id = '{{id}}';
+
+ if (event.spans) {
+ for (const span of event.spans) {
+ // deno-lint-ignore no-explicit-any
+ const spanAny = span as any;
+
+ if (spanAny.span_id) {
+ spanAny.span_id = '{{id}}';
+ }
+
+ if (spanAny.parent_span_id) {
+ spanAny.parent_span_id = '{{id}}';
+ }
+
+ if (spanAny.start_timestamp) {
+ spanAny.start_timestamp = 0;
+ }
+
+ if (spanAny.timestamp) {
+ spanAny.timestamp = 0;
+ }
+
+ if (spanAny.trace_id) {
+ spanAny.trace_id = '{{id}}';
+ }
+ }
+ }
+
+ if (event.breadcrumbs) {
+ for (const breadcrumb of event.breadcrumbs) {
+ breadcrumb.timestamp = 0;
+ }
+ }
+
+ return event;
+}
diff --git a/packages/deno/test/transport.ts b/packages/deno/test/transport.ts
new file mode 100644
index 000000000000..2eaeed6eeef6
--- /dev/null
+++ b/packages/deno/test/transport.ts
@@ -0,0 +1,30 @@
+import { createTransport } from 'npm:@sentry/core';
+import type {
+ BaseTransportOptions,
+ Envelope,
+ Transport,
+ TransportMakeRequestResponse,
+ TransportRequest,
+} from 'npm:@sentry/types';
+import { parseEnvelope } from 'npm:@sentry/utils';
+
+export interface TestTransportOptions extends BaseTransportOptions {
+ callback: (envelope: Envelope) => void;
+}
+
+/**
+ * Creates a Transport that uses the Fetch API to send events to Sentry.
+ */
+export function makeTestTransport(callback: (envelope: Envelope) => void) {
+ return (options: BaseTransportOptions): Transport => {
+ async function doCallback(request: TransportRequest): Promise {
+ await callback(parseEnvelope(request.body, new TextEncoder(), new TextDecoder()));
+
+ return Promise.resolve({
+ statusCode: 200,
+ });
+ }
+
+ return createTransport(options, doCallback);
+ };
+}
diff --git a/packages/deno/tsconfig.build.json b/packages/deno/tsconfig.build.json
new file mode 100644
index 000000000000..87025d5676c5
--- /dev/null
+++ b/packages/deno/tsconfig.build.json
@@ -0,0 +1,12 @@
+{
+ "extends": "./tsconfig.json",
+ "include": ["./lib.deno.d.ts", "src/**/*"],
+ "compilerOptions": {
+ "outDir": "build",
+ "lib": ["esnext"],
+ "module": "esnext",
+ "target": "esnext",
+ "declaration": true,
+ "declarationMap": false
+ }
+}
diff --git a/packages/deno/tsconfig.json b/packages/deno/tsconfig.json
new file mode 100644
index 000000000000..fdd107c1ed78
--- /dev/null
+++ b/packages/deno/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "extends": "../../tsconfig.json",
+ "include": ["./lib.deno.d.ts", "src/**/*", "example.ts"],
+ "compilerOptions": {
+ "lib": ["esnext"],
+ "module": "esnext",
+ "target": "esnext"
+ }
+}
diff --git a/packages/deno/tsconfig.test.json b/packages/deno/tsconfig.test.json
new file mode 100644
index 000000000000..548e94149758
--- /dev/null
+++ b/packages/deno/tsconfig.test.json
@@ -0,0 +1,9 @@
+{
+ "extends": "./tsconfig.json",
+ "include": ["./lib.deno.d.ts", "test/**/*"],
+ "compilerOptions": {
+ "lib": ["esnext"],
+ "module": "esnext",
+ "target": "esnext"
+ }
+}
diff --git a/packages/deno/tsconfig.types.json b/packages/deno/tsconfig.types.json
new file mode 100644
index 000000000000..d6d1e9a548c9
--- /dev/null
+++ b/packages/deno/tsconfig.types.json
@@ -0,0 +1,9 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "declaration": true,
+ "declarationMap": true,
+ "emitDeclarationOnly": true,
+ "outDir": "build"
+ }
+}
diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json
index 73790f4ddc4a..0e713a9afed2 100644
--- a/packages/e2e-tests/package.json
+++ b/packages/e2e-tests/package.json
@@ -17,7 +17,7 @@
"test:validate-test-app-setups": "ts-node validate-test-app-setups.ts",
"test:prepare": "ts-node prepare.ts",
"test:validate": "run-s test:validate-configuration test:validate-test-app-setups",
- "clean": "rimraf tmp node_modules && yarn clean:test-applications",
+ "clean": "rimraf tmp node_modules pnpm-lock.yaml && yarn clean:test-applications",
"clean:test-applications": "rimraf test-applications/**/{node_modules,dist,build,.next,.sveltekit,pnpm-lock.yaml}"
},
"devDependencies": {
diff --git a/packages/e2e-tests/test-applications/node-experimental-fastify-app/.gitignore b/packages/e2e-tests/test-applications/node-experimental-fastify-app/.gitignore
new file mode 100644
index 000000000000..1521c8b7652b
--- /dev/null
+++ b/packages/e2e-tests/test-applications/node-experimental-fastify-app/.gitignore
@@ -0,0 +1 @@
+dist
diff --git a/packages/e2e-tests/test-applications/node-experimental-fastify-app/.npmrc b/packages/e2e-tests/test-applications/node-experimental-fastify-app/.npmrc
new file mode 100644
index 000000000000..c6b3ef9b3eaa
--- /dev/null
+++ b/packages/e2e-tests/test-applications/node-experimental-fastify-app/.npmrc
@@ -0,0 +1,2 @@
+@sentry:registry=http://localhost:4873
+@sentry-internal:registry=http://localhost:4873
diff --git a/packages/e2e-tests/test-applications/node-experimental-fastify-app/event-proxy-server.ts b/packages/e2e-tests/test-applications/node-experimental-fastify-app/event-proxy-server.ts
new file mode 100644
index 000000000000..67cf80b4dabf
--- /dev/null
+++ b/packages/e2e-tests/test-applications/node-experimental-fastify-app/event-proxy-server.ts
@@ -0,0 +1,253 @@
+import type { Envelope, EnvelopeItem, Event } from '@sentry/types';
+import { parseEnvelope } from '@sentry/utils';
+import * as fs from 'fs';
+import * as http from 'http';
+import * as https from 'https';
+import type { AddressInfo } from 'net';
+import * as os from 'os';
+import * as path from 'path';
+import * as util from 'util';
+import * as zlib from 'zlib';
+
+const readFile = util.promisify(fs.readFile);
+const writeFile = util.promisify(fs.writeFile);
+
+interface EventProxyServerOptions {
+ /** Port to start the event proxy server at. */
+ port: number;
+ /** The name for the proxy server used for referencing it with listener functions */
+ proxyServerName: string;
+}
+
+interface SentryRequestCallbackData {
+ envelope: Envelope;
+ rawProxyRequestBody: string;
+ rawSentryResponseBody: string;
+ sentryResponseStatusCode?: number;
+}
+
+/**
+ * Starts an event proxy server that will proxy events to sentry when the `tunnel` option is used. Point the `tunnel`
+ * option to this server (like this `tunnel: http://localhost:${port option}/`).
+ */
+export async function startEventProxyServer(options: EventProxyServerOptions): Promise {
+ const eventCallbackListeners: Set<(data: string) => void> = new Set();
+
+ const proxyServer = http.createServer((proxyRequest, proxyResponse) => {
+ const proxyRequestChunks: Uint8Array[] = [];
+
+ proxyRequest.addListener('data', (chunk: Buffer) => {
+ proxyRequestChunks.push(chunk);
+ });
+
+ proxyRequest.addListener('error', err => {
+ throw err;
+ });
+
+ proxyRequest.addListener('end', () => {
+ const proxyRequestBody =
+ proxyRequest.headers['content-encoding'] === 'gzip'
+ ? zlib.gunzipSync(Buffer.concat(proxyRequestChunks)).toString()
+ : Buffer.concat(proxyRequestChunks).toString();
+
+ let envelopeHeader = JSON.parse(proxyRequestBody.split('\n')[0]);
+
+ if (!envelopeHeader.dsn) {
+ throw new Error('[event-proxy-server] No dsn on envelope header. Please set tunnel option.');
+ }
+
+ const { origin, pathname, host } = new URL(envelopeHeader.dsn);
+
+ const projectId = pathname.substring(1);
+ const sentryIngestUrl = `${origin}/api/${projectId}/envelope/`;
+
+ proxyRequest.headers.host = host;
+
+ const sentryResponseChunks: Uint8Array[] = [];
+
+ const sentryRequest = https.request(
+ sentryIngestUrl,
+ { headers: proxyRequest.headers, method: proxyRequest.method },
+ sentryResponse => {
+ sentryResponse.addListener('data', (chunk: Buffer) => {
+ proxyResponse.write(chunk, 'binary');
+ sentryResponseChunks.push(chunk);
+ });
+
+ sentryResponse.addListener('end', () => {
+ eventCallbackListeners.forEach(listener => {
+ const rawSentryResponseBody = Buffer.concat(sentryResponseChunks).toString();
+
+ const data: SentryRequestCallbackData = {
+ envelope: parseEnvelope(proxyRequestBody, new TextEncoder(), new TextDecoder()),
+ rawProxyRequestBody: proxyRequestBody,
+ rawSentryResponseBody,
+ sentryResponseStatusCode: sentryResponse.statusCode,
+ };
+
+ listener(Buffer.from(JSON.stringify(data)).toString('base64'));
+ });
+ proxyResponse.end();
+ });
+
+ sentryResponse.addListener('error', err => {
+ throw err;
+ });
+
+ proxyResponse.writeHead(sentryResponse.statusCode || 500, sentryResponse.headers);
+ },
+ );
+
+ sentryRequest.write(Buffer.concat(proxyRequestChunks), 'binary');
+ sentryRequest.end();
+ });
+ });
+
+ const proxyServerStartupPromise = new Promise(resolve => {
+ proxyServer.listen(options.port, () => {
+ resolve();
+ });
+ });
+
+ const eventCallbackServer = http.createServer((eventCallbackRequest, eventCallbackResponse) => {
+ eventCallbackResponse.statusCode = 200;
+ eventCallbackResponse.setHeader('connection', 'keep-alive');
+
+ const callbackListener = (data: string): void => {
+ eventCallbackResponse.write(data.concat('\n'), 'utf8');
+ };
+
+ eventCallbackListeners.add(callbackListener);
+
+ eventCallbackRequest.on('close', () => {
+ eventCallbackListeners.delete(callbackListener);
+ });
+
+ eventCallbackRequest.on('error', () => {
+ eventCallbackListeners.delete(callbackListener);
+ });
+ });
+
+ const eventCallbackServerStartupPromise = new Promise(resolve => {
+ eventCallbackServer.listen(0, () => {
+ const port = String((eventCallbackServer.address() as AddressInfo).port);
+ void registerCallbackServerPort(options.proxyServerName, port).then(resolve);
+ });
+ });
+
+ await eventCallbackServerStartupPromise;
+ await proxyServerStartupPromise;
+ return;
+}
+
+export async function waitForRequest(
+ proxyServerName: string,
+ callback: (eventData: SentryRequestCallbackData) => Promise | boolean,
+): Promise {
+ const eventCallbackServerPort = await retrieveCallbackServerPort(proxyServerName);
+
+ return new Promise((resolve, reject) => {
+ const request = http.request(`http://localhost:${eventCallbackServerPort}/`, {}, response => {
+ let eventContents = '';
+
+ response.on('error', err => {
+ reject(err);
+ });
+
+ response.on('data', (chunk: Buffer) => {
+ const chunkString = chunk.toString('utf8');
+ chunkString.split('').forEach(char => {
+ if (char === '\n') {
+ const eventCallbackData: SentryRequestCallbackData = JSON.parse(
+ Buffer.from(eventContents, 'base64').toString('utf8'),
+ );
+ const callbackResult = callback(eventCallbackData);
+ if (typeof callbackResult !== 'boolean') {
+ callbackResult.then(
+ match => {
+ if (match) {
+ response.destroy();
+ resolve(eventCallbackData);
+ }
+ },
+ err => {
+ throw err;
+ },
+ );
+ } else if (callbackResult) {
+ response.destroy();
+ resolve(eventCallbackData);
+ }
+ eventContents = '';
+ } else {
+ eventContents = eventContents.concat(char);
+ }
+ });
+ });
+ });
+
+ request.end();
+ });
+}
+
+export function waitForEnvelopeItem(
+ proxyServerName: string,
+ callback: (envelopeItem: EnvelopeItem) => Promise | boolean,
+): Promise {
+ return new Promise((resolve, reject) => {
+ waitForRequest(proxyServerName, async eventData => {
+ const envelopeItems = eventData.envelope[1];
+ for (const envelopeItem of envelopeItems) {
+ if (await callback(envelopeItem)) {
+ resolve(envelopeItem);
+ return true;
+ }
+ }
+ return false;
+ }).catch(reject);
+ });
+}
+
+export function waitForError(
+ proxyServerName: string,
+ callback: (transactionEvent: Event) => Promise | boolean,
+): Promise {
+ return new Promise((resolve, reject) => {
+ waitForEnvelopeItem(proxyServerName, async envelopeItem => {
+ const [envelopeItemHeader, envelopeItemBody] = envelopeItem;
+ if (envelopeItemHeader.type === 'event' && (await callback(envelopeItemBody as Event))) {
+ resolve(envelopeItemBody as Event);
+ return true;
+ }
+ return false;
+ }).catch(reject);
+ });
+}
+
+export function waitForTransaction(
+ proxyServerName: string,
+ callback: (transactionEvent: Event) => Promise | boolean,
+): Promise {
+ return new Promise((resolve, reject) => {
+ waitForEnvelopeItem(proxyServerName, async envelopeItem => {
+ const [envelopeItemHeader, envelopeItemBody] = envelopeItem;
+ if (envelopeItemHeader.type === 'transaction' && (await callback(envelopeItemBody as Event))) {
+ resolve(envelopeItemBody as Event);
+ return true;
+ }
+ return false;
+ }).catch(reject);
+ });
+}
+
+const TEMP_FILE_PREFIX = 'event-proxy-server-';
+
+async function registerCallbackServerPort(serverName: string, port: string): Promise {
+ const tmpFilePath = path.join(os.tmpdir(), `${TEMP_FILE_PREFIX}${serverName}`);
+ await writeFile(tmpFilePath, port, { encoding: 'utf8' });
+}
+
+function retrieveCallbackServerPort(serverName: string): Promise {
+ const tmpFilePath = path.join(os.tmpdir(), `${TEMP_FILE_PREFIX}${serverName}`);
+ return readFile(tmpFilePath, 'utf8');
+}
diff --git a/packages/e2e-tests/test-applications/node-experimental-fastify-app/package.json b/packages/e2e-tests/test-applications/node-experimental-fastify-app/package.json
new file mode 100644
index 000000000000..8ada1cb5d82e
--- /dev/null
+++ b/packages/e2e-tests/test-applications/node-experimental-fastify-app/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "node-experimental-fastify-app",
+ "version": "1.0.0",
+ "private": true,
+ "scripts": {
+ "start": "node src/app.js",
+ "test": "playwright test",
+ "clean": "npx rimraf node_modules pnpm-lock.yaml",
+ "test:build": "pnpm install",
+ "test:assert": "pnpm test"
+ },
+ "dependencies": {
+ "@sentry/node-experimental": "latest || *",
+ "@sentry/types": "latest || *",
+ "@sentry/core": "latest || *",
+ "@sentry/utils": "latest || *",
+ "@sentry/node": "latest || *",
+ "@sentry/opentelemetry-node": "latest || *",
+ "@sentry-internal/tracing": "latest || *",
+ "@types/node": "18.15.1",
+ "fastify": "4.23.2",
+ "fastify-plugin": "4.5.1",
+ "typescript": "4.9.5",
+ "ts-node": "10.9.1"
+ },
+ "devDependencies": {
+ "@playwright/test": "^1.38.1"
+ },
+ "volta": {
+ "extends": "../../package.json"
+ }
+}
diff --git a/packages/e2e-tests/test-applications/node-experimental-fastify-app/playwright.config.ts b/packages/e2e-tests/test-applications/node-experimental-fastify-app/playwright.config.ts
new file mode 100644
index 000000000000..f39997dc76e8
--- /dev/null
+++ b/packages/e2e-tests/test-applications/node-experimental-fastify-app/playwright.config.ts
@@ -0,0 +1,62 @@
+import type { PlaywrightTestConfig } from '@playwright/test';
+import { devices } from '@playwright/test';
+
+const fastifyPort = 3030;
+const eventProxyPort = 3031;
+
+/**
+ * See https://playwright.dev/docs/test-configuration.
+ */
+const config: PlaywrightTestConfig = {
+ testDir: './tests',
+ /* Maximum time one test can run for. */
+ timeout: 60 * 1000,
+ expect: {
+ /**
+ * Maximum time expect() should wait for the condition to be met.
+ * For example in `await expect(locator).toHaveText();`
+ */
+ timeout: 10000,
+ },
+ /* Run tests in files in parallel */
+ fullyParallel: true,
+ /* Fail the build on CI if you accidentally left test.only in the source code. */
+ forbidOnly: !!process.env.CI,
+ retries: 0,
+ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
+ reporter: 'list',
+ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
+ use: {
+ /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
+ actionTimeout: 0,
+ /* Base URL to use in actions like `await page.goto('/')`. */
+ baseURL: `http://localhost:${fastifyPort}`,
+
+ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
+ trace: 'on-first-retry',
+ },
+
+ /* Configure projects for major browsers */
+ projects: [
+ {
+ name: 'chromium',
+ use: {
+ ...devices['Desktop Chrome'],
+ },
+ },
+ ],
+
+ /* Run your local dev server before starting the tests */
+ webServer: [
+ {
+ command: 'pnpm ts-node-script start-event-proxy.ts',
+ port: eventProxyPort,
+ },
+ {
+ command: 'pnpm start',
+ port: fastifyPort,
+ },
+ ],
+};
+
+export default config;
diff --git a/packages/e2e-tests/test-applications/node-experimental-fastify-app/src/app.js b/packages/e2e-tests/test-applications/node-experimental-fastify-app/src/app.js
new file mode 100644
index 000000000000..50fe45767504
--- /dev/null
+++ b/packages/e2e-tests/test-applications/node-experimental-fastify-app/src/app.js
@@ -0,0 +1,82 @@
+require('./tracing');
+
+const Sentry = require('@sentry/node-experimental');
+const { fastify } = require('fastify');
+const fastifyPlugin = require('fastify-plugin');
+const http = require('http');
+
+const FastifySentry = fastifyPlugin(async (fastify, options) => {
+ fastify.decorateRequest('_sentryContext', null);
+
+ fastify.addHook('onError', async (_request, _reply, error) => {
+ Sentry.captureException(error);
+ });
+});
+
+const app = fastify();
+const port = 3030;
+
+app.register(FastifySentry);
+
+app.get('/test-success', function (req, res) {
+ res.send({ version: 'v1' });
+});
+
+app.get('/test-param/:param', function (req, res) {
+ res.send({ paramWas: req.params.param });
+});
+
+app.get('/test-inbound-headers', function (req, res) {
+ const headers = req.headers;
+
+ res.send({ headers });
+});
+
+app.get('/test-outgoing-http', async function (req, res) {
+ const data = await makeHttpRequest('http://localhost:3030/test-inbound-headers');
+
+ res.send(data);
+});
+
+app.get('/test-outgoing-fetch', async function (req, res) {
+ const response = await fetch('http://localhost:3030/test-inbound-headers');
+ const data = await response.json();
+
+ res.send(data);
+});
+
+app.get('/test-transaction', async function (req, res) {
+ Sentry.startSpan({ name: 'test-span' }, () => {
+ Sentry.startSpan({ name: 'child-span' }, () => {});
+ });
+
+ res.send({});
+});
+
+app.get('/test-error', async function (req, res) {
+ const exceptionId = Sentry.captureException(new Error('This is an error'));
+
+ await Sentry.flush(2000);
+
+ res.send({ exceptionId });
+});
+
+app.listen({ port: port });
+
+function makeHttpRequest(url) {
+ return new Promise(resolve => {
+ const data = [];
+
+ http
+ .request(url, httpRes => {
+ httpRes.on('data', chunk => {
+ data.push(chunk);
+ });
+ httpRes.on('end', () => {
+ const json = JSON.parse(Buffer.concat(data).toString());
+ resolve(json);
+ });
+ })
+ .end();
+ });
+}
diff --git a/packages/e2e-tests/test-applications/node-experimental-fastify-app/src/tracing.js b/packages/e2e-tests/test-applications/node-experimental-fastify-app/src/tracing.js
new file mode 100644
index 000000000000..e571a4374a9e
--- /dev/null
+++ b/packages/e2e-tests/test-applications/node-experimental-fastify-app/src/tracing.js
@@ -0,0 +1,10 @@
+const Sentry = require('@sentry/node-experimental');
+
+Sentry.init({
+ environment: 'qa', // dynamic sampling bias to keep transactions
+ dsn: process.env.E2E_TEST_DSN,
+ integrations: [],
+ debug: true,
+ tracesSampleRate: 1,
+ tunnel: 'http://localhost:3031/', // proxy server
+});
diff --git a/packages/e2e-tests/test-applications/node-experimental-fastify-app/start-event-proxy.ts b/packages/e2e-tests/test-applications/node-experimental-fastify-app/start-event-proxy.ts
new file mode 100644
index 000000000000..7ae352993f3c
--- /dev/null
+++ b/packages/e2e-tests/test-applications/node-experimental-fastify-app/start-event-proxy.ts
@@ -0,0 +1,6 @@
+import { startEventProxyServer } from './event-proxy-server';
+
+startEventProxyServer({
+ port: 3031,
+ proxyServerName: 'node-experimental-fastify-app',
+});
diff --git a/packages/e2e-tests/test-applications/node-experimental-fastify-app/tests/errors.test.ts b/packages/e2e-tests/test-applications/node-experimental-fastify-app/tests/errors.test.ts
new file mode 100644
index 000000000000..4656ba23e7de
--- /dev/null
+++ b/packages/e2e-tests/test-applications/node-experimental-fastify-app/tests/errors.test.ts
@@ -0,0 +1,39 @@
+import { test, expect } from '@playwright/test';
+import axios, { AxiosError } from 'axios';
+
+const authToken = process.env.E2E_TEST_AUTH_TOKEN;
+const sentryTestOrgSlug = process.env.E2E_TEST_SENTRY_ORG_SLUG;
+const sentryTestProject = process.env.E2E_TEST_SENTRY_TEST_PROJECT;
+const EVENT_POLLING_TIMEOUT = 30_000;
+
+test('Sends exception to Sentry', async ({ baseURL }) => {
+ const { data } = await axios.get(`${baseURL}/test-error`);
+ const { exceptionId } = data;
+
+ const url = `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionId}/`;
+
+ console.log(`Polling for error eventId: ${exceptionId}`);
+
+ await expect
+ .poll(
+ async () => {
+ try {
+ const response = await axios.get(url, { headers: { Authorization: `Bearer ${authToken}` } });
+
+ return response.status;
+ } catch (e) {
+ if (e instanceof AxiosError && e.response) {
+ if (e.response.status !== 404) {
+ throw e;
+ } else {
+ return e.response.status;
+ }
+ } else {
+ throw e;
+ }
+ }
+ },
+ { timeout: EVENT_POLLING_TIMEOUT },
+ )
+ .toBe(200);
+});
diff --git a/packages/e2e-tests/test-applications/node-experimental-fastify-app/tests/propagation.test.ts b/packages/e2e-tests/test-applications/node-experimental-fastify-app/tests/propagation.test.ts
new file mode 100644
index 000000000000..8dbcb590b331
--- /dev/null
+++ b/packages/e2e-tests/test-applications/node-experimental-fastify-app/tests/propagation.test.ts
@@ -0,0 +1,191 @@
+import { test, expect } from '@playwright/test';
+import { Span } from '@sentry/types';
+import axios from 'axios';
+import { waitForTransaction } from '../event-proxy-server';
+
+const authToken = process.env.E2E_TEST_AUTH_TOKEN;
+const sentryTestOrgSlug = process.env.E2E_TEST_SENTRY_ORG_SLUG;
+const sentryTestProject = process.env.E2E_TEST_SENTRY_TEST_PROJECT;
+const EVENT_POLLING_TIMEOUT = 30_000;
+
+test('Propagates trace for outgoing http requests', async ({ baseURL }) => {
+ const inboundTransactionPromise = waitForTransaction('node-experimental-fastify-app', transactionEvent => {
+ return (
+ transactionEvent?.contexts?.trace?.op === 'http.server' &&
+ transactionEvent?.transaction === 'GET /test-inbound-headers'
+ );
+ });
+
+ const outboundTransactionPromise = waitForTransaction('node-experimental-fastify-app', transactionEvent => {
+ return (
+ transactionEvent?.contexts?.trace?.op === 'http.server' &&
+ transactionEvent?.transaction === 'GET /test-outgoing-http'
+ );
+ });
+
+ const { data } = await axios.get(`${baseURL}/test-outgoing-http`);
+
+ const inboundTransaction = await inboundTransactionPromise;
+ const outboundTransaction = await outboundTransactionPromise;
+
+ const traceId = outboundTransaction?.contexts?.trace?.trace_id;
+ const outgoingHttpSpan = outboundTransaction?.spans?.find(span => span.op === 'http.client') as
+ | ReturnType
+ | undefined;
+
+ expect(outgoingHttpSpan).toBeDefined();
+
+ const outgoingHttpSpanId = outgoingHttpSpan?.span_id;
+
+ expect(traceId).toEqual(expect.any(String));
+
+ // data is passed through from the inbound request, to verify we have the correct headers set
+ const inboundHeaderSentryTrace = data.headers?.['sentry-trace'];
+ const inboundHeaderBaggage = data.headers?.['baggage'];
+
+ expect(inboundHeaderSentryTrace).toEqual(`${traceId}-${outgoingHttpSpanId}-1`);
+ expect(inboundHeaderBaggage).toBeDefined();
+
+ const baggage = (inboundHeaderBaggage || '').split(',');
+ expect(baggage).toEqual(
+ expect.arrayContaining([
+ 'sentry-environment=qa',
+ `sentry-trace_id=${traceId}`,
+ expect.stringMatching(/sentry-public_key=/),
+ ]),
+ );
+
+ expect(outboundTransaction).toEqual(
+ expect.objectContaining({
+ contexts: expect.objectContaining({
+ trace: {
+ data: {
+ url: 'http://localhost:3030/test-outgoing-http',
+ 'otel.kind': 'SERVER',
+ 'http.response.status_code': 200,
+ },
+ op: 'http.server',
+ span_id: expect.any(String),
+ status: 'ok',
+ tags: {
+ 'http.status_code': 200,
+ },
+ trace_id: traceId,
+ },
+ }),
+ }),
+ );
+
+ expect(inboundTransaction).toEqual(
+ expect.objectContaining({
+ contexts: expect.objectContaining({
+ trace: {
+ data: {
+ url: 'http://localhost:3030/test-inbound-headers',
+ 'otel.kind': 'SERVER',
+ 'http.response.status_code': 200,
+ },
+ op: 'http.server',
+ parent_span_id: outgoingHttpSpanId,
+ span_id: expect.any(String),
+ status: 'ok',
+ tags: {
+ 'http.status_code': 200,
+ },
+ trace_id: traceId,
+ },
+ }),
+ }),
+ );
+});
+
+test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => {
+ const inboundTransactionPromise = waitForTransaction('node-experimental-fastify-app', transactionEvent => {
+ return (
+ transactionEvent?.contexts?.trace?.op === 'http.server' &&
+ transactionEvent?.transaction === 'GET /test-inbound-headers'
+ );
+ });
+
+ const outboundTransactionPromise = waitForTransaction('node-experimental-fastify-app', transactionEvent => {
+ return (
+ transactionEvent?.contexts?.trace?.op === 'http.server' &&
+ transactionEvent?.transaction === 'GET /test-outgoing-fetch'
+ );
+ });
+
+ const { data } = await axios.get(`${baseURL}/test-outgoing-fetch`);
+
+ const inboundTransaction = await inboundTransactionPromise;
+ const outboundTransaction = await outboundTransactionPromise;
+
+ const traceId = outboundTransaction?.contexts?.trace?.trace_id;
+ const outgoingHttpSpan = outboundTransaction?.spans?.find(span => span.op === 'http.client') as
+ | ReturnType
+ | undefined;
+
+ expect(outgoingHttpSpan).toBeDefined();
+
+ const outgoingHttpSpanId = outgoingHttpSpan?.span_id;
+
+ expect(traceId).toEqual(expect.any(String));
+
+ // data is passed through from the inbound request, to verify we have the correct headers set
+ const inboundHeaderSentryTrace = data.headers?.['sentry-trace'];
+ const inboundHeaderBaggage = data.headers?.['baggage'];
+
+ expect(inboundHeaderSentryTrace).toEqual(`${traceId}-${outgoingHttpSpanId}-1`);
+ expect(inboundHeaderBaggage).toBeDefined();
+
+ const baggage = (inboundHeaderBaggage || '').split(',');
+ expect(baggage).toEqual(
+ expect.arrayContaining([
+ 'sentry-environment=qa',
+ `sentry-trace_id=${traceId}`,
+ expect.stringMatching(/sentry-public_key=/),
+ ]),
+ );
+
+ expect(outboundTransaction).toEqual(
+ expect.objectContaining({
+ contexts: expect.objectContaining({
+ trace: {
+ data: {
+ url: 'http://localhost:3030/test-outgoing-fetch',
+ 'otel.kind': 'SERVER',
+ 'http.response.status_code': 200,
+ },
+ op: 'http.server',
+ span_id: expect.any(String),
+ status: 'ok',
+ tags: {
+ 'http.status_code': 200,
+ },
+ trace_id: traceId,
+ },
+ }),
+ }),
+ );
+
+ expect(inboundTransaction).toEqual(
+ expect.objectContaining({
+ contexts: expect.objectContaining({
+ trace: {
+ data: {
+ url: 'http://localhost:3030/test-inbound-headers',
+ 'otel.kind': 'SERVER',
+ 'http.response.status_code': 200,
+ },
+ op: 'http.server',
+ parent_span_id: outgoingHttpSpanId,
+ span_id: expect.any(String),
+ status: 'ok',
+ tags: {
+ 'http.status_code': 200,
+ },
+ trace_id: traceId,
+ },
+ }),
+ }),
+ );
+});
diff --git a/packages/e2e-tests/test-applications/node-experimental-fastify-app/tests/transactions.test.ts b/packages/e2e-tests/test-applications/node-experimental-fastify-app/tests/transactions.test.ts
new file mode 100644
index 000000000000..f34beaa63926
--- /dev/null
+++ b/packages/e2e-tests/test-applications/node-experimental-fastify-app/tests/transactions.test.ts
@@ -0,0 +1,124 @@
+import { test, expect } from '@playwright/test';
+import { waitForTransaction } from '../event-proxy-server';
+import axios, { AxiosError } from 'axios';
+
+const authToken = process.env.E2E_TEST_AUTH_TOKEN;
+const sentryTestOrgSlug = process.env.E2E_TEST_SENTRY_ORG_SLUG;
+const sentryTestProject = process.env.E2E_TEST_SENTRY_TEST_PROJECT;
+const EVENT_POLLING_TIMEOUT = 30_000;
+
+test('Sends an API route transaction', async ({ baseURL }) => {
+ const pageloadTransactionEventPromise = waitForTransaction('node-experimental-fastify-app', transactionEvent => {
+ return (
+ transactionEvent?.contexts?.trace?.op === 'http.server' &&
+ transactionEvent?.transaction === 'GET /test-transaction'
+ );
+ });
+
+ await axios.get(`${baseURL}/test-transaction`);
+
+ const transactionEvent = await pageloadTransactionEventPromise;
+ const transactionEventId = transactionEvent.event_id;
+
+ expect(transactionEvent).toEqual(
+ expect.objectContaining({
+ contexts: expect.objectContaining({
+ trace: {
+ data: {
+ url: 'http://localhost:3030/test-transaction',
+ 'otel.kind': 'SERVER',
+ 'http.response.status_code': 200,
+ },
+ op: 'http.server',
+ span_id: expect.any(String),
+ status: 'ok',
+ tags: {
+ 'http.status_code': 200,
+ },
+ trace_id: expect.any(String),
+ },
+ }),
+
+ spans: [
+ {
+ data: {
+ 'plugin.name': 'fastify -> app-auto-0',
+ 'fastify.type': 'request_handler',
+ 'http.route': '/test-transaction',
+ 'otel.kind': 'INTERNAL',
+ },
+ description: 'request handler - fastify -> app-auto-0',
+ parent_span_id: expect.any(String),
+ span_id: expect.any(String),
+ start_timestamp: expect.any(Number),
+ status: 'ok',
+ timestamp: expect.any(Number),
+ trace_id: expect.any(String),
+ origin: 'auto.http.otel.fastify',
+ },
+ {
+ data: {
+ 'otel.kind': 'INTERNAL',
+ },
+ description: 'test-span',
+ parent_span_id: expect.any(String),
+ span_id: expect.any(String),
+ start_timestamp: expect.any(Number),
+ status: 'ok',
+ timestamp: expect.any(Number),
+ trace_id: expect.any(String),
+ origin: 'manual',
+ },
+ {
+ data: {
+ 'otel.kind': 'INTERNAL',
+ },
+ description: 'child-span',
+ parent_span_id: expect.any(String),
+ span_id: expect.any(String),
+ start_timestamp: expect.any(Number),
+ status: 'ok',
+ timestamp: expect.any(Number),
+ trace_id: expect.any(String),
+ origin: 'manual',
+ },
+ ],
+ tags: {
+ 'http.status_code': 200,
+ },
+ transaction: 'GET /test-transaction',
+ type: 'transaction',
+ transaction_info: {
+ source: 'route',
+ },
+ }),
+ );
+
+ await expect
+ .poll(
+ async () => {
+ try {
+ const response = await axios.get(
+ `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`,
+ { headers: { Authorization: `Bearer ${authToken}` } },
+ );
+
+ return response.status;
+ } catch (e) {
+ if (e instanceof AxiosError && e.response) {
+ if (e.response.status !== 404) {
+ throw e;
+ } else {
+ return e.response.status;
+ }
+ } else {
+ throw e;
+ }
+ }
+ },
+ {
+ timeout: EVENT_POLLING_TIMEOUT,
+ },
+ )
+ .toBe(200);
+});
diff --git a/packages/e2e-tests/test-applications/node-experimental-fastify-app/tsconfig.json b/packages/e2e-tests/test-applications/node-experimental-fastify-app/tsconfig.json
new file mode 100644
index 000000000000..17bd2c1f4c00
--- /dev/null
+++ b/packages/e2e-tests/test-applications/node-experimental-fastify-app/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "types": ["node"],
+ "esModuleInterop": true,
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "strict": true,
+ "outDir": "dist"
+ },
+ "include": ["*.ts"]
+}
diff --git a/packages/e2e-tests/test-registry.npmrc b/packages/e2e-tests/test-registry.npmrc
index c35d987cca9f..fd8ba6605a28 100644
--- a/packages/e2e-tests/test-registry.npmrc
+++ b/packages/e2e-tests/test-registry.npmrc
@@ -1,3 +1,6 @@
@sentry:registry=http://localhost:4873
@sentry-internal:registry=http://localhost:4873
//localhost:4873/:_authToken=some-token
+
+# Do not notify about npm updates
+update-notifier=false
diff --git a/packages/e2e-tests/verdaccio-config/config.yaml b/packages/e2e-tests/verdaccio-config/config.yaml
index 80a5afc70008..938b877a50e5 100644
--- a/packages/e2e-tests/verdaccio-config/config.yaml
+++ b/packages/e2e-tests/verdaccio-config/config.yaml
@@ -44,6 +44,12 @@ packages:
unpublish: $all
# proxy: npmjs # Don't proxy for E2E tests!
+ '@sentry/astro':
+ access: $all
+ publish: $all
+ unpublish: $all
+ # proxy: npmjs # Don't proxy for E2E tests!
+
'@sentry/browser':
access: $all
publish: $all
@@ -62,6 +68,12 @@ packages:
unpublish: $all
# proxy: npmjs # Don't proxy for E2E tests!
+ '@sentry/deno':
+ access: $all
+ publish: $all
+ unpublish: $all
+ # proxy: npmjs # Don't proxy for E2E tests!
+
'@sentry/ember':
access: $all
publish: $all
diff --git a/packages/ember/index.js b/packages/ember/index.js
index de05e5d6089f..96e79bccf704 100644
--- a/packages/ember/index.js
+++ b/packages/ember/index.js
@@ -30,7 +30,7 @@ module.exports = {
included() {
const app = this._findHost();
const config = app.project.config(app.env);
- const addonConfig = config['@sentry/ember'] || {};
+ const addonConfig = dropUndefinedKeys(config['@sentry/ember'] || {});
if (!isSerializable(addonConfig)) {
// eslint-disable-next-line no-console
@@ -101,3 +101,15 @@ function isScalar(val) {
function isPlainObject(obj) {
return typeof obj === 'object' && obj.constructor === Object && obj.toString() === '[object Object]';
}
+
+function dropUndefinedKeys(obj) {
+ const newObj = {};
+
+ for (const key in obj) {
+ if (obj[key] !== undefined) {
+ newObj[key] = obj[key];
+ }
+ }
+
+ return newObj;
+}
diff --git a/packages/eslint-config-sdk/package.json b/packages/eslint-config-sdk/package.json
index ea8c546667c0..2ee491464a9e 100644
--- a/packages/eslint-config-sdk/package.json
+++ b/packages/eslint-config-sdk/package.json
@@ -24,7 +24,7 @@
"@typescript-eslint/eslint-plugin": "^5.48.0",
"@typescript-eslint/parser": "^5.48.0",
"eslint-config-prettier": "^6.11.0",
- "eslint-plugin-deprecation": "^1.1.0",
+ "eslint-plugin-deprecation": "^1.5.0",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-jest": "^27.2.2",
"eslint-plugin-jsdoc": "^30.0.3",
diff --git a/packages/eslint-config-sdk/src/index.js b/packages/eslint-config-sdk/src/index.js
index e9d72743f99a..efbeb3047a33 100644
--- a/packages/eslint-config-sdk/src/index.js
+++ b/packages/eslint-config-sdk/src/index.js
@@ -16,8 +16,13 @@ module.exports = {
{
// Configuration for typescript files
files: ['*.ts', '*.tsx', '*.d.ts'],
- extends: ['plugin:@typescript-eslint/recommended', 'prettier/@typescript-eslint', 'plugin:import/typescript'],
- plugins: ['@typescript-eslint', 'jsdoc', 'deprecation'],
+ extends: [
+ 'plugin:@typescript-eslint/recommended',
+ 'prettier/@typescript-eslint',
+ 'plugin:import/typescript',
+ 'plugin:deprecation/recommended',
+ ],
+ plugins: ['@typescript-eslint', 'jsdoc'],
parser: '@typescript-eslint/parser',
rules: {
// We want to guard against using the equality operator with empty arrays
@@ -87,9 +92,6 @@ module.exports = {
// Make sure Promises are handled appropriately
'@typescript-eslint/no-floating-promises': 'error',
- // Do not use deprecated methods
- 'deprecation/deprecation': 'error',
-
// sort imports
'simple-import-sort/sort': 'error',
'sort-imports': 'off',
diff --git a/packages/integrations/src/contextlines.ts b/packages/integrations/src/contextlines.ts
index 3bc483958b42..d528477718c1 100644
--- a/packages/integrations/src/contextlines.ts
+++ b/packages/integrations/src/contextlines.ts
@@ -1,4 +1,4 @@
-import type { Event, EventProcessor, Hub, Integration, StackFrame } from '@sentry/types';
+import type { Event, Integration, StackFrame } from '@sentry/types';
import { addContextToFrame, GLOBAL_OBJ, stripUrlQueryAndFragment } from '@sentry/utils';
const WINDOW = GLOBAL_OBJ as typeof GLOBAL_OBJ & Window;
@@ -44,17 +44,20 @@ export class ContextLines implements Integration {
/**
* @inheritDoc
*/
- public setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void {
- addGlobalEventProcessor(event => {
- const self = getCurrentHub().getIntegration(ContextLines);
- if (!self) {
- return event;
- }
- return this.addSourceContext(event);
- });
+ public setupOnce(_addGlobaleventProcessor: unknown, _getCurrentHub: unknown): void {
+ // noop
+ }
+
+ /** @inheritDoc */
+ public processEvent(event: Event): Event {
+ return this.addSourceContext(event);
}
- /** Processes an event and adds context lines */
+ /**
+ * Processes an event and adds context lines.
+ *
+ * TODO (v8): Make this internal/private
+ */
public addSourceContext(event: Event): Event {
const doc = WINDOW.document;
const htmlFilename = WINDOW.location && stripUrlQueryAndFragment(WINDOW.location.href);
diff --git a/packages/integrations/src/dedupe.ts b/packages/integrations/src/dedupe.ts
index 8f156e76784d..49865de3cd79 100644
--- a/packages/integrations/src/dedupe.ts
+++ b/packages/integrations/src/dedupe.ts
@@ -1,4 +1,4 @@
-import type { Event, EventProcessor, Exception, Hub, Integration, StackFrame } from '@sentry/types';
+import type { Event, Exception, Integration, StackFrame } from '@sentry/types';
import { logger } from '@sentry/utils';
/** Deduplication filter */
@@ -22,36 +22,32 @@ export class Dedupe implements Integration {
this.name = Dedupe.id;
}
+ /** @inheritDoc */
+ public setupOnce(_addGlobaleventProcessor: unknown, _getCurrentHub: unknown): void {
+ // noop
+ }
+
/**
* @inheritDoc
*/
- public setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void {
- const eventProcessor: EventProcessor = currentEvent => {
- // We want to ignore any non-error type events, e.g. transactions or replays
- // These should never be deduped, and also not be compared against as _previousEvent.
- if (currentEvent.type) {
- return currentEvent;
- }
+ public processEvent(currentEvent: Event): Event | null {
+ // We want to ignore any non-error type events, e.g. transactions or replays
+ // These should never be deduped, and also not be compared against as _previousEvent.
+ if (currentEvent.type) {
+ return currentEvent;
+ }
- const self = getCurrentHub().getIntegration(Dedupe);
- if (self) {
- // Juuust in case something goes wrong
- try {
- if (_shouldDropEvent(currentEvent, self._previousEvent)) {
- __DEBUG_BUILD__ && logger.warn('Event dropped due to being a duplicate of previously captured event.');
- return null;
- }
- } catch (_oO) {
- return (self._previousEvent = currentEvent);
- }
-
- return (self._previousEvent = currentEvent);
+ // Juuust in case something goes wrong
+ try {
+ if (_shouldDropEvent(currentEvent, this._previousEvent)) {
+ __DEBUG_BUILD__ && logger.warn('Event dropped due to being a duplicate of previously captured event.');
+ return null;
}
- return currentEvent;
- };
+ } catch (_oO) {
+ return (this._previousEvent = currentEvent);
+ }
- eventProcessor.id = this.name;
- addGlobalEventProcessor(eventProcessor);
+ return (this._previousEvent = currentEvent);
}
}
diff --git a/packages/integrations/src/extraerrordata.ts b/packages/integrations/src/extraerrordata.ts
index 86d9343ef5e3..0ac2729e3baf 100644
--- a/packages/integrations/src/extraerrordata.ts
+++ b/packages/integrations/src/extraerrordata.ts
@@ -1,9 +1,9 @@
-import type { Contexts, Event, EventHint, EventProcessor, ExtendedError, Hub, Integration } from '@sentry/types';
+import type { Contexts, Event, EventHint, ExtendedError, Integration } from '@sentry/types';
import { addNonEnumerableProperty, isError, isPlainObject, logger, normalize } from '@sentry/utils';
/** JSDoc */
interface ExtraErrorDataOptions {
- depth?: number;
+ depth: number;
}
/** Patch toString calls to return proper name for wrapped functions */
@@ -24,7 +24,7 @@ export class ExtraErrorData implements Integration {
/**
* @inheritDoc
*/
- public constructor(options?: ExtraErrorDataOptions) {
+ public constructor(options?: Partial) {
this.name = ExtraErrorData.id;
this._options = {
@@ -36,94 +36,99 @@ export class ExtraErrorData implements Integration {
/**
* @inheritDoc
*/
- public setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void {
- addGlobalEventProcessor((event: Event, hint: EventHint) => {
- const self = getCurrentHub().getIntegration(ExtraErrorData);
- if (!self) {
- return event;
- }
- return self.enhanceEventWithErrorData(event, hint);
- });
+ public setupOnce(_addGlobaleventProcessor: unknown, _getCurrentHub: unknown): void {
+ // noop
+ }
+
+ /** @inheritDoc */
+ public processEvent(event: Event, hint: EventHint): Event {
+ return this.enhanceEventWithErrorData(event, hint);
}
/**
- * Attaches extracted information from the Error object to extra field in the Event
+ * Attaches extracted information from the Error object to extra field in the Event.
+ *
+ * TODO (v8): Drop this public function.
*/
public enhanceEventWithErrorData(event: Event, hint: EventHint = {}): Event {
- if (!hint.originalException || !isError(hint.originalException)) {
- return event;
- }
- const exceptionName = (hint.originalException as ExtendedError).name || hint.originalException.constructor.name;
+ return _enhanceEventWithErrorData(event, hint, this._options.depth);
+ }
+}
- const errorData = this._extractErrorData(hint.originalException as ExtendedError);
+function _enhanceEventWithErrorData(event: Event, hint: EventHint = {}, depth: number): Event {
+ if (!hint.originalException || !isError(hint.originalException)) {
+ return event;
+ }
+ const exceptionName = (hint.originalException as ExtendedError).name || hint.originalException.constructor.name;
- if (errorData) {
- const contexts: Contexts = {
- ...event.contexts,
- };
+ const errorData = _extractErrorData(hint.originalException as ExtendedError);
- const normalizedErrorData = normalize(errorData, this._options.depth);
+ if (errorData) {
+ const contexts: Contexts = {
+ ...event.contexts,
+ };
- if (isPlainObject(normalizedErrorData)) {
- // We mark the error data as "already normalized" here, because we don't want other normalization procedures to
- // potentially truncate the data we just already normalized, with a certain depth setting.
- addNonEnumerableProperty(normalizedErrorData, '__sentry_skip_normalization__', true);
- contexts[exceptionName] = normalizedErrorData;
- }
+ const normalizedErrorData = normalize(errorData, depth);
- return {
- ...event,
- contexts,
- };
+ if (isPlainObject(normalizedErrorData)) {
+ // We mark the error data as "already normalized" here, because we don't want other normalization procedures to
+ // potentially truncate the data we just already normalized, with a certain depth setting.
+ addNonEnumerableProperty(normalizedErrorData, '__sentry_skip_normalization__', true);
+ contexts[exceptionName] = normalizedErrorData;
}
- return event;
+ return {
+ ...event,
+ contexts,
+ };
}
- /**
- * Extract extra information from the Error object
- */
- private _extractErrorData(error: ExtendedError): Record | null {
- // We are trying to enhance already existing event, so no harm done if it won't succeed
- try {
- const nativeKeys = [
- 'name',
- 'message',
- 'stack',
- 'line',
- 'column',
- 'fileName',
- 'lineNumber',
- 'columnNumber',
- 'toJSON',
- ];
-
- const extraErrorInfo: Record = {};
-
- // We want only enumerable properties, thus `getOwnPropertyNames` is redundant here, as we filter keys anyway.
- for (const key of Object.keys(error)) {
- if (nativeKeys.indexOf(key) !== -1) {
- continue;
- }
- const value = error[key];
- extraErrorInfo[key] = isError(value) ? value.toString() : value;
+ return event;
+}
+
+/**
+ * Extract extra information from the Error object
+ */
+function _extractErrorData(error: ExtendedError): Record | null {
+ // We are trying to enhance already existing event, so no harm done if it won't succeed
+ try {
+ const nativeKeys = [
+ 'name',
+ 'message',
+ 'stack',
+ 'line',
+ 'column',
+ 'fileName',
+ 'lineNumber',
+ 'columnNumber',
+ 'toJSON',
+ ];
+
+ const extraErrorInfo: Record = {};
+
+ // We want only enumerable properties, thus `getOwnPropertyNames` is redundant here, as we filter keys anyway.
+ for (const key of Object.keys(error)) {
+ if (nativeKeys.indexOf(key) !== -1) {
+ continue;
}
+ const value = error[key];
+ extraErrorInfo[key] = isError(value) ? value.toString() : value;
+ }
- // Check if someone attached `toJSON` method to grab even more properties (eg. axios is doing that)
- if (typeof error.toJSON === 'function') {
- const serializedError = error.toJSON() as Record;
+ // Check if someone attached `toJSON` method to grab even more properties (eg. axios is doing that)
+ if (typeof error.toJSON === 'function') {
+ const serializedError = error.toJSON() as Record;
- for (const key of Object.keys(serializedError)) {
- const value = serializedError[key];
- extraErrorInfo[key] = isError(value) ? value.toString() : value;
- }
+ for (const key of Object.keys(serializedError)) {
+ const value = serializedError[key];
+ extraErrorInfo[key] = isError(value) ? value.toString() : value;
}
-
- return extraErrorInfo;
- } catch (oO) {
- __DEBUG_BUILD__ && logger.error('Unable to extract extra data from the Error object:', oO);
}
- return null;
+ return extraErrorInfo;
+ } catch (oO) {
+ __DEBUG_BUILD__ && logger.error('Unable to extract extra data from the Error object:', oO);
}
+
+ return null;
}
diff --git a/packages/integrations/src/rewriteframes.ts b/packages/integrations/src/rewriteframes.ts
index 1564d54a4970..67f146e650bd 100644
--- a/packages/integrations/src/rewriteframes.ts
+++ b/packages/integrations/src/rewriteframes.ts
@@ -1,4 +1,4 @@
-import type { Event, EventProcessor, Hub, Integration, StackFrame, Stacktrace } from '@sentry/types';
+import type { Event, Integration, StackFrame, Stacktrace } from '@sentry/types';
import { basename, relative } from '@sentry/utils';
type StackFrameIteratee = (frame: StackFrame) => StackFrame;
@@ -43,17 +43,18 @@ export class RewriteFrames implements Integration {
/**
* @inheritDoc
*/
- public setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void {
- addGlobalEventProcessor(event => {
- const self = getCurrentHub().getIntegration(RewriteFrames);
- if (self) {
- return self.process(event);
- }
- return event;
- });
+ public setupOnce(_addGlobaleventProcessor: unknown, _getCurrentHub: unknown): void {
+ // noop
}
- /** JSDoc */
+ /** @inheritDoc */
+ public processEvent(event: Event): Event {
+ return this.process(event);
+ }
+
+ /**
+ * TODO (v8): Make this private/internal
+ */
public process(originalEvent: Event): Event {
let processedEvent = originalEvent;
diff --git a/packages/integrations/src/sessiontiming.ts b/packages/integrations/src/sessiontiming.ts
index 584163ce008e..016d71f336e3 100644
--- a/packages/integrations/src/sessiontiming.ts
+++ b/packages/integrations/src/sessiontiming.ts
@@ -1,4 +1,4 @@
-import type { Event, EventProcessor, Hub, Integration } from '@sentry/types';
+import type { Event, Integration } from '@sentry/types';
/** This function adds duration since Sentry was initialized till the time event was sent */
export class SessionTiming implements Integration {
@@ -23,18 +23,17 @@ export class SessionTiming implements Integration {
/**
* @inheritDoc
*/
- public setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void {
- addGlobalEventProcessor(event => {
- const self = getCurrentHub().getIntegration(SessionTiming);
- if (self) {
- return self.process(event);
- }
- return event;
- });
+ public setupOnce(_addGlobaleventProcessor: unknown, _getCurrentHub: unknown): void {
+ // noop
+ }
+
+ /** @inheritDoc */
+ public processEvent(event: Event): Event {
+ return this.process(event);
}
/**
- * @inheritDoc
+ * TODO (v8): make this private/internal
*/
public process(event: Event): Event {
const now = Date.now();
diff --git a/packages/integrations/src/transaction.ts b/packages/integrations/src/transaction.ts
index 1bb3ebfa816d..ae9f826cba55 100644
--- a/packages/integrations/src/transaction.ts
+++ b/packages/integrations/src/transaction.ts
@@ -1,4 +1,4 @@
-import type { Event, EventProcessor, Hub, Integration, StackFrame } from '@sentry/types';
+import type { Event, Integration, StackFrame } from '@sentry/types';
/** Add node transaction to the event */
export class Transaction implements Integration {
@@ -19,43 +19,40 @@ export class Transaction implements Integration {
/**
* @inheritDoc
*/
- public setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void {
- addGlobalEventProcessor(event => {
- const self = getCurrentHub().getIntegration(Transaction);
- if (self) {
- return self.process(event);
- }
- return event;
- });
+ public setupOnce(_addGlobaleventProcessor: unknown, _getCurrentHub: unknown): void {
+ // noop
+ }
+
+ /** @inheritDoc */
+ public processEvent(event: Event): Event {
+ return this.processEvent(event);
}
/**
- * @inheritDoc
+ * TODO (v8): Make this private/internal
*/
public process(event: Event): Event {
- const frames = this._getFramesFromEvent(event);
+ const frames = _getFramesFromEvent(event);
// use for loop so we don't have to reverse whole frames array
for (let i = frames.length - 1; i >= 0; i--) {
const frame = frames[i];
if (frame.in_app === true) {
- event.transaction = this._getTransaction(frame);
+ event.transaction = _getTransaction(frame);
break;
}
}
return event;
}
+}
- /** JSDoc */
- private _getFramesFromEvent(event: Event): StackFrame[] {
- const exception = event.exception && event.exception.values && event.exception.values[0];
- return (exception && exception.stacktrace && exception.stacktrace.frames) || [];
- }
+function _getFramesFromEvent(event: Event): StackFrame[] {
+ const exception = event.exception && event.exception.values && event.exception.values[0];
+ return (exception && exception.stacktrace && exception.stacktrace.frames) || [];
+}
- /** JSDoc */
- private _getTransaction(frame: StackFrame): string {
- return frame.module || frame.function ? `${frame.module || '?'}/${frame.function || '?'}` : '';
- }
+function _getTransaction(frame: StackFrame): string {
+ return frame.module || frame.function ? `${frame.module || '?'}/${frame.function || '?'}` : '';
}
diff --git a/packages/integrations/test/dedupe.test.ts b/packages/integrations/test/dedupe.test.ts
index 7ffc30d1bdcf..f4a703662e0c 100644
--- a/packages/integrations/test/dedupe.test.ts
+++ b/packages/integrations/test/dedupe.test.ts
@@ -1,4 +1,4 @@
-import type { Event as SentryEvent, EventProcessor, Exception, Hub, StackFrame, Stacktrace } from '@sentry/types';
+import type { Event as SentryEvent, Exception, StackFrame, Stacktrace } from '@sentry/types';
import { _shouldDropEvent, Dedupe } from '../src/dedupe';
@@ -176,47 +176,29 @@ describe('Dedupe', () => {
});
});
- describe('setupOnce', () => {
- let dedupeFunc: EventProcessor;
-
- beforeEach(function () {
+ describe('processEvent', () => {
+ it('ignores consecutive errors', () => {
const integration = new Dedupe();
- const addGlobalEventProcessor = (callback: EventProcessor) => {
- dedupeFunc = callback;
- };
-
- const getCurrentHub = () => {
- return {
- getIntegration() {
- return integration;
- },
- } as unknown as Hub;
- };
-
- integration.setupOnce(addGlobalEventProcessor, getCurrentHub);
- });
- it('ignores consecutive errors', () => {
- expect(dedupeFunc(clone(exceptionEvent), {})).not.toBeNull();
- expect(dedupeFunc(clone(exceptionEvent), {})).toBeNull();
- expect(dedupeFunc(clone(exceptionEvent), {})).toBeNull();
+ expect(integration.processEvent(clone(exceptionEvent))).not.toBeNull();
+ expect(integration.processEvent(clone(exceptionEvent))).toBeNull();
+ expect(integration.processEvent(clone(exceptionEvent))).toBeNull();
});
it('ignores transactions between errors', () => {
- expect(dedupeFunc(clone(exceptionEvent), {})).not.toBeNull();
+ const integration = new Dedupe();
+
+ expect(integration.processEvent(clone(exceptionEvent))).not.toBeNull();
expect(
- dedupeFunc(
- {
- event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2',
- message: 'someMessage',
- transaction: 'wat',
- type: 'transaction',
- },
- {},
- ),
+ integration.processEvent({
+ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2',
+ message: 'someMessage',
+ transaction: 'wat',
+ type: 'transaction',
+ }),
).not.toBeNull();
- expect(dedupeFunc(clone(exceptionEvent), {})).toBeNull();
- expect(dedupeFunc(clone(exceptionEvent), {})).toBeNull();
+ expect(integration.processEvent(clone(exceptionEvent))).toBeNull();
+ expect(integration.processEvent(clone(exceptionEvent))).toBeNull();
});
});
});
diff --git a/packages/nextjs/src/client/index.ts b/packages/nextjs/src/client/index.ts
index 0adda732916b..9de007bd2e95 100644
--- a/packages/nextjs/src/client/index.ts
+++ b/packages/nextjs/src/client/index.ts
@@ -44,14 +44,17 @@ const globalWithInjectedValues = global as typeof global & {
/** Inits the Sentry NextJS SDK on the browser with the React SDK. */
export function init(options: BrowserOptions): void {
- applyTunnelRouteOption(options);
- buildMetadata(options, ['nextjs', 'react']);
+ const opts = {
+ environment: getVercelEnv(true) || process.env.NODE_ENV,
+ ...options,
+ };
- options.environment = options.environment || getVercelEnv(true) || process.env.NODE_ENV;
+ applyTunnelRouteOption(opts);
+ buildMetadata(opts, ['nextjs', 'react']);
- addClientIntegrations(options);
+ addClientIntegrations(opts);
- reactInit(options);
+ reactInit(opts);
configureScope(scope => {
scope.setTag('runtime', 'browser');
diff --git a/packages/nextjs/src/edge/index.ts b/packages/nextjs/src/edge/index.ts
index 0f13ff9cfccd..336a84b6b85c 100644
--- a/packages/nextjs/src/edge/index.ts
+++ b/packages/nextjs/src/edge/index.ts
@@ -1,4 +1,5 @@
import { SDK_VERSION } from '@sentry/core';
+import type { SdkMetadata } from '@sentry/types';
import type { VercelEdgeOptions } from '@sentry/vercel-edge';
import { init as vercelEdgeInit } from '@sentry/vercel-edge';
@@ -6,8 +7,12 @@ export type EdgeOptions = VercelEdgeOptions;
/** Inits the Sentry NextJS SDK on the Edge Runtime. */
export function init(options: VercelEdgeOptions = {}): void {
- options._metadata = options._metadata || {};
- options._metadata.sdk = options._metadata.sdk || {
+ const opts = {
+ _metadata: {} as SdkMetadata,
+ ...options,
+ };
+
+ opts._metadata.sdk = opts._metadata.sdk || {
name: 'sentry.javascript.nextjs',
packages: [
{
@@ -18,7 +23,7 @@ export function init(options: VercelEdgeOptions = {}): void {
version: SDK_VERSION,
};
- vercelEdgeInit(options);
+ vercelEdgeInit(opts);
}
/**
diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts
index 23f3bc61e4a3..7a782ec63a23 100644
--- a/packages/nextjs/src/server/index.ts
+++ b/packages/nextjs/src/server/index.ts
@@ -64,7 +64,14 @@ const IS_VERCEL = !!process.env.VERCEL;
/** Inits the Sentry NextJS SDK on node. */
export function init(options: NodeOptions): void {
- if (__DEBUG_BUILD__ && options.debug) {
+ const opts = {
+ environment: process.env.SENTRY_ENVIRONMENT || getVercelEnv(false) || process.env.NODE_ENV,
+ ...options,
+ // Right now we only capture frontend sessions for Next.js
+ autoSessionTracking: false,
+ };
+
+ if (__DEBUG_BUILD__ && opts.debug) {
logger.enable();
}
@@ -75,16 +82,11 @@ export function init(options: NodeOptions): void {
return;
}
- buildMetadata(options, ['nextjs', 'node']);
-
- options.environment =
- options.environment || process.env.SENTRY_ENVIRONMENT || getVercelEnv(false) || process.env.NODE_ENV;
+ buildMetadata(opts, ['nextjs', 'node']);
- addServerIntegrations(options);
- // Right now we only capture frontend sessions for Next.js
- options.autoSessionTracking = false;
+ addServerIntegrations(opts);
- nodeInit(options);
+ nodeInit(opts);
const filterTransactions: EventProcessor = event => {
return event.type === 'transaction' && event.transaction === '/404' ? null : event;
diff --git a/packages/node-experimental/package.json b/packages/node-experimental/package.json
index 75b177ade68d..b13ce269e821 100644
--- a/packages/node-experimental/package.json
+++ b/packages/node-experimental/package.json
@@ -24,10 +24,11 @@
},
"dependencies": {
"@opentelemetry/api": "~1.6.0",
+ "@opentelemetry/core": "~1.17.0",
"@opentelemetry/context-async-hooks": "~1.17.0",
"@opentelemetry/instrumentation": "~0.43.0",
"@opentelemetry/instrumentation-express": "~0.33.1",
- "@opentelemetry/instrumentation-fastify": "~0.32.2",
+ "@opentelemetry/instrumentation-fastify": "~0.32.3",
"@opentelemetry/instrumentation-graphql": "~0.35.1",
"@opentelemetry/instrumentation-http": "~0.43.0",
"@opentelemetry/instrumentation-mongodb": "~0.37.0",
@@ -44,7 +45,8 @@
"@sentry/node": "7.73.0",
"@sentry/opentelemetry-node": "7.73.0",
"@sentry/types": "7.73.0",
- "@sentry/utils": "7.73.0"
+ "@sentry/utils": "7.73.0",
+ "opentelemetry-instrumentation-fetch-node": "1.1.0"
},
"scripts": {
"build": "run-p build:transpile build:types",
diff --git a/packages/node-experimental/src/constants.ts b/packages/node-experimental/src/constants.ts
index dc714590556a..8d06aa411c1c 100644
--- a/packages/node-experimental/src/constants.ts
+++ b/packages/node-experimental/src/constants.ts
@@ -1,3 +1,21 @@
import { createContextKey } from '@opentelemetry/api';
export const OTEL_CONTEXT_HUB_KEY = createContextKey('sentry_hub');
+
+export const OTEL_ATTR_ORIGIN = 'sentry.origin';
+export const OTEL_ATTR_OP = 'sentry.op';
+export const OTEL_ATTR_SOURCE = 'sentry.source';
+
+export const OTEL_ATTR_PARENT_SAMPLED = 'sentry.parentSampled';
+
+export const OTEL_ATTR_BREADCRUMB_TYPE = 'sentry.breadcrumb.type';
+export const OTEL_ATTR_BREADCRUMB_LEVEL = 'sentry.breadcrumb.level';
+export const OTEL_ATTR_BREADCRUMB_EVENT_ID = 'sentry.breadcrumb.event_id';
+export const OTEL_ATTR_BREADCRUMB_CATEGORY = 'sentry.breadcrumb.category';
+export const OTEL_ATTR_BREADCRUMB_DATA = 'sentry.breadcrumb.data';
+export const OTEL_ATTR_SENTRY_SAMPLE_RATE = 'sentry.sample_rate';
+
+export const SENTRY_TRACE_HEADER = 'sentry-trace';
+export const SENTRY_BAGGAGE_HEADER = 'baggage';
+
+export const SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY = createContextKey('SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY');
diff --git a/packages/node-experimental/src/index.ts b/packages/node-experimental/src/index.ts
index 3c7fa347cf94..db1abe96495a 100644
--- a/packages/node-experimental/src/index.ts
+++ b/packages/node-experimental/src/index.ts
@@ -12,7 +12,9 @@ export { INTEGRATIONS as Integrations };
export { getAutoPerformanceIntegrations } from './integrations/getAutoPerformanceIntegrations';
export * as Handlers from './sdk/handlers';
export * from './sdk/trace';
+export { getActiveSpan } from './utils/getActiveSpan';
export { getCurrentHub, getHubFromCarrier } from './sdk/hub';
+export type { Span } from './types';
export {
makeNodeTransport,
@@ -39,7 +41,6 @@ export {
makeMain,
runWithAsyncContext,
Scope,
- startTransaction,
SDK_VERSION,
setContext,
setExtra,
@@ -67,10 +68,8 @@ export type {
Exception,
Session,
SeverityLevel,
- Span,
StackFrame,
Stacktrace,
Thread,
- Transaction,
User,
} from '@sentry/node';
diff --git a/packages/node-experimental/src/integrations/express.ts b/packages/node-experimental/src/integrations/express.ts
index 95b9527c8498..0bbe3a19a11d 100644
--- a/packages/node-experimental/src/integrations/express.ts
+++ b/packages/node-experimental/src/integrations/express.ts
@@ -2,7 +2,7 @@ import type { Instrumentation } from '@opentelemetry/instrumentation';
import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express';
import type { Integration } from '@sentry/types';
-import { addOriginToOtelSpan } from '../utils/addOriginToSpan';
+import { addOriginToSpan } from '../utils/addOriginToSpan';
import { NodePerformanceIntegration } from './NodePerformanceIntegration';
/**
@@ -31,7 +31,7 @@ export class Express extends NodePerformanceIntegration implements Integra
return [
new ExpressInstrumentation({
requestHook(span) {
- addOriginToOtelSpan(span, 'auto.http.otel.express');
+ addOriginToSpan(span, 'auto.http.otel.express');
},
}),
];
diff --git a/packages/node-experimental/src/integrations/fastify.ts b/packages/node-experimental/src/integrations/fastify.ts
index b84301967616..4d32037887b1 100644
--- a/packages/node-experimental/src/integrations/fastify.ts
+++ b/packages/node-experimental/src/integrations/fastify.ts
@@ -2,7 +2,7 @@ import type { Instrumentation } from '@opentelemetry/instrumentation';
import { FastifyInstrumentation } from '@opentelemetry/instrumentation-fastify';
import type { Integration } from '@sentry/types';
-import { addOriginToOtelSpan } from '../utils/addOriginToSpan';
+import { addOriginToSpan } from '../utils/addOriginToSpan';
import { NodePerformanceIntegration } from './NodePerformanceIntegration';
/**
@@ -31,7 +31,7 @@ export class Fastify extends NodePerformanceIntegration implements Integra
return [
new FastifyInstrumentation({
requestHook(span) {
- addOriginToOtelSpan(span, 'auto.http.otel.fastify');
+ addOriginToSpan(span, 'auto.http.otel.fastify');
},
}),
];
diff --git a/packages/node-experimental/src/integrations/graphql.ts b/packages/node-experimental/src/integrations/graphql.ts
index 87749a0f54a2..b4a529df713e 100644
--- a/packages/node-experimental/src/integrations/graphql.ts
+++ b/packages/node-experimental/src/integrations/graphql.ts
@@ -2,7 +2,7 @@ import type { Instrumentation } from '@opentelemetry/instrumentation';
import { GraphQLInstrumentation } from '@opentelemetry/instrumentation-graphql';
import type { Integration } from '@sentry/types';
-import { addOriginToOtelSpan } from '../utils/addOriginToSpan';
+import { addOriginToSpan } from '../utils/addOriginToSpan';
import { NodePerformanceIntegration } from './NodePerformanceIntegration';
/**
@@ -32,7 +32,7 @@ export class GraphQL extends NodePerformanceIntegration implements Integra
new GraphQLInstrumentation({
ignoreTrivialResolveSpans: true,
responseHook(span) {
- addOriginToOtelSpan(span, 'auto.graphql.otel.graphql');
+ addOriginToSpan(span, 'auto.graphql.otel.graphql');
},
}),
];
diff --git a/packages/node-experimental/src/integrations/http.ts b/packages/node-experimental/src/integrations/http.ts
index 6a4b8766a242..5b939e2ead20 100644
--- a/packages/node-experimental/src/integrations/http.ts
+++ b/packages/node-experimental/src/integrations/http.ts
@@ -1,25 +1,19 @@
-import type { Attributes } from '@opentelemetry/api';
+import type { Span } from '@opentelemetry/api';
import { SpanKind } from '@opentelemetry/api';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
-import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
-import { hasTracingEnabled, isSentryRequestUrl, Transaction } from '@sentry/core';
-import { getCurrentHub } from '@sentry/node';
-import { _INTERNAL_getSentrySpan } from '@sentry/opentelemetry-node';
+import { hasTracingEnabled, isSentryRequestUrl } from '@sentry/core';
import type { EventProcessor, Hub, Integration } from '@sentry/types';
+import { stringMatchesSomePattern } from '@sentry/utils';
import type { ClientRequest, IncomingMessage, ServerResponse } from 'http';
-import type { NodeExperimentalClient, OtelSpan } from '../types';
+import { OTEL_ATTR_ORIGIN } from '../constants';
+import { setSpanMetadata } from '../opentelemetry/spanData';
+import type { NodeExperimentalClient } from '../sdk/client';
+import { getCurrentHub } from '../sdk/hub';
import { getRequestSpanData } from '../utils/getRequestSpanData';
import { getRequestUrl } from '../utils/getRequestUrl';
-
-interface TracingOptions {
- /**
- * Function determining whether or not to create spans to track outgoing requests to the given URL.
- * By default, spans will be created for all outgoing requests.
- */
- shouldCreateSpanForRequest?: (url: string) => boolean;
-}
+import { getSpanKind } from '../utils/getSpanKind';
interface HttpOptions {
/**
@@ -32,7 +26,12 @@ interface HttpOptions {
* Whether tracing spans should be created for requests
* Defaults to false
*/
- tracing?: TracingOptions | boolean;
+ spans?: boolean;
+
+ /**
+ * Do not capture spans or breadcrumbs for outgoing HTTP requests to URLs matching the given patterns.
+ */
+ ignoreOutgoingRequests?: (string | RegExp)[];
}
/**
@@ -54,12 +53,16 @@ export class Http implements Integration {
*/
public name: string;
+ /**
+ * If spans for HTTP requests should be captured.
+ */
+ public shouldCreateSpansForRequests: boolean;
+
private _unload?: () => void;
private readonly _breadcrumbs: boolean;
- // undefined: default behavior based on tracing settings
- private readonly _tracing: boolean | undefined;
- private _shouldCreateSpans: boolean;
- private _shouldCreateSpanForRequest?: (url: string) => boolean;
+ // If this is undefined, use default behavior based on client settings
+ private readonly _spans: boolean | undefined;
+ private _ignoreOutgoingRequests: (string | RegExp)[];
/**
* @inheritDoc
@@ -67,12 +70,12 @@ export class Http implements Integration {
public constructor(options: HttpOptions = {}) {
this.name = Http.id;
this._breadcrumbs = typeof options.breadcrumbs === 'undefined' ? true : options.breadcrumbs;
- this._tracing = typeof options.tracing === 'undefined' ? undefined : !!options.tracing;
- this._shouldCreateSpans = false;
+ this._spans = typeof options.spans === 'undefined' ? undefined : options.spans;
- if (options.tracing && typeof options.tracing === 'object') {
- this._shouldCreateSpanForRequest = options.tracing.shouldCreateSpanForRequest;
- }
+ this._ignoreOutgoingRequests = options.ignoreOutgoingRequests || [];
+
+ // Properly set in setupOnce based on client settings
+ this.shouldCreateSpansForRequests = false;
}
/**
@@ -80,14 +83,16 @@ export class Http implements Integration {
*/
public setupOnce(_addGlobalEventProcessor: (callback: EventProcessor) => void, _getCurrentHub: () => Hub): void {
// No need to instrument if we don't want to track anything
- if (!this._breadcrumbs && this._tracing === false) {
+ if (!this._breadcrumbs && this._spans === false) {
return;
}
const client = getCurrentHub().getClient();
const clientOptions = client?.getOptions();
- this._shouldCreateSpans = typeof this._tracing === 'undefined' ? hasTracingEnabled(clientOptions) : this._tracing;
+ // This is used in the sampler function
+ this.shouldCreateSpansForRequests =
+ typeof this._spans === 'boolean' ? this._spans : hasTracingEnabled(clientOptions);
// Register instrumentations we care about
this._unload = registerInstrumentations({
@@ -95,7 +100,20 @@ export class Http implements Integration {
new HttpInstrumentation({
ignoreOutgoingRequestHook: request => {
const url = getRequestUrl(request);
- return url ? isSentryRequestUrl(url, getCurrentHub()) : false;
+
+ if (!url) {
+ return false;
+ }
+
+ if (isSentryRequestUrl(url, getCurrentHub())) {
+ return true;
+ }
+
+ if (this._ignoreOutgoingRequests.length && stringMatchesSomePattern(url, this._ignoreOutgoingRequests)) {
+ return true;
+ }
+
+ return false;
},
ignoreIncomingRequestHook: request => {
@@ -111,20 +129,14 @@ export class Http implements Integration {
requireParentforOutgoingSpans: true,
requireParentforIncomingSpans: false,
requestHook: (span, req) => {
- this._updateSentrySpan(span as unknown as OtelSpan, req);
+ this._updateSpan(span, req);
},
responseHook: (span, res) => {
- this._addRequestBreadcrumb(span as unknown as OtelSpan, res);
+ this._addRequestBreadcrumb(span, res);
},
}),
],
});
-
- this._shouldCreateSpanForRequest =
- // eslint-disable-next-line deprecation/deprecation
- this._shouldCreateSpanForRequest || clientOptions?.shouldCreateSpanForRequest;
-
- client?.on?.('otelSpanEnd', this._onSpanEnd);
}
/**
@@ -134,69 +146,18 @@ export class Http implements Integration {
this._unload?.();
}
- private _onSpanEnd: (otelSpan: unknown, mutableOptions: { drop: boolean }) => void = (
- otelSpan: unknown,
- mutableOptions: { drop: boolean },
- ) => {
- if (!this._shouldCreateSpans) {
- mutableOptions.drop = true;
- return;
- }
-
- if (this._shouldCreateSpanForRequest) {
- const url = getHttpUrl((otelSpan as OtelSpan).attributes);
- if (url && !this._shouldCreateSpanForRequest(url)) {
- mutableOptions.drop = true;
- return;
- }
- }
-
- return;
- };
+ /** Update the span with data we need. */
+ private _updateSpan(span: Span, request: ClientRequest | IncomingMessage): void {
+ span.setAttribute(OTEL_ATTR_ORIGIN, 'auto.http.otel.http');
- /** Update the Sentry span data based on the OTEL span. */
- private _updateSentrySpan(span: OtelSpan, request: ClientRequest | IncomingMessage): void {
- const data = getRequestSpanData(span);
- const { attributes } = span;
-
- const sentrySpan = _INTERNAL_getSentrySpan(span.spanContext().spanId);
- if (!sentrySpan) {
- return;
+ if (getSpanKind(span) === SpanKind.SERVER) {
+ setSpanMetadata(span, { request });
}
-
- sentrySpan.origin = 'auto.http.otel.http';
-
- const additionalData: Record = {
- url: data.url,
- };
-
- if (sentrySpan instanceof Transaction && span.kind === SpanKind.SERVER) {
- sentrySpan.setMetadata({ request });
- }
-
- if (attributes[SemanticAttributes.HTTP_STATUS_CODE]) {
- const statusCode = attributes[SemanticAttributes.HTTP_STATUS_CODE] as string;
- additionalData['http.response.status_code'] = statusCode;
-
- sentrySpan.setTag('http.status_code', statusCode);
- }
-
- if (data['http.query']) {
- additionalData['http.query'] = data['http.query'].slice(1);
- }
- if (data['http.fragment']) {
- additionalData['http.fragment'] = data['http.fragment'].slice(1);
- }
-
- Object.keys(additionalData).forEach(prop => {
- const value = additionalData[prop];
- sentrySpan.setData(prop, value);
- });
}
/** Add a breadcrumb for outgoing requests. */
- private _addRequestBreadcrumb(span: OtelSpan, response: IncomingMessage | ServerResponse): void {
- if (!this._breadcrumbs || span.kind !== SpanKind.CLIENT) {
+ private _addRequestBreadcrumb(span: Span, response: IncomingMessage | ServerResponse): void {
+ if (!this._breadcrumbs || getSpanKind(span) !== SpanKind.CLIENT) {
return;
}
@@ -220,8 +181,3 @@ export class Http implements Integration {
);
}
}
-
-function getHttpUrl(attributes: Attributes): string | undefined {
- const url = attributes[SemanticAttributes.HTTP_URL];
- return typeof url === 'string' ? url : undefined;
-}
diff --git a/packages/node-experimental/src/integrations/index.ts b/packages/node-experimental/src/integrations/index.ts
index efe485d6c1b6..35858c52c7a1 100644
--- a/packages/node-experimental/src/integrations/index.ts
+++ b/packages/node-experimental/src/integrations/index.ts
@@ -26,6 +26,7 @@ export {
export { Express } from './express';
export { Http } from './http';
+export { NodeFetch } from './node-fetch';
export { Fastify } from './fastify';
export { GraphQL } from './graphql';
export { Mongo } from './mongo';
diff --git a/packages/node-experimental/src/integrations/mongo.ts b/packages/node-experimental/src/integrations/mongo.ts
index aea5d0a7d3fb..f8be482be946 100644
--- a/packages/node-experimental/src/integrations/mongo.ts
+++ b/packages/node-experimental/src/integrations/mongo.ts
@@ -2,7 +2,7 @@ import type { Instrumentation } from '@opentelemetry/instrumentation';
import { MongoDBInstrumentation } from '@opentelemetry/instrumentation-mongodb';
import type { Integration } from '@sentry/types';
-import { addOriginToOtelSpan } from '../utils/addOriginToSpan';
+import { addOriginToSpan } from '../utils/addOriginToSpan';
import { NodePerformanceIntegration } from './NodePerformanceIntegration';
/**
@@ -31,7 +31,7 @@ export class Mongo extends NodePerformanceIntegration implements Integrati
return [
new MongoDBInstrumentation({
responseHook(span) {
- addOriginToOtelSpan(span, 'auto.db.otel.mongo');
+ addOriginToSpan(span, 'auto.db.otel.mongo');
},
}),
];
diff --git a/packages/node-experimental/src/integrations/mongoose.ts b/packages/node-experimental/src/integrations/mongoose.ts
index 8f6eb65adb8b..a5361a620bc2 100644
--- a/packages/node-experimental/src/integrations/mongoose.ts
+++ b/packages/node-experimental/src/integrations/mongoose.ts
@@ -2,7 +2,7 @@ import type { Instrumentation } from '@opentelemetry/instrumentation';
import { MongooseInstrumentation } from '@opentelemetry/instrumentation-mongoose';
import type { Integration } from '@sentry/types';
-import { addOriginToOtelSpan } from '../utils/addOriginToSpan';
+import { addOriginToSpan } from '../utils/addOriginToSpan';
import { NodePerformanceIntegration } from './NodePerformanceIntegration';
/**
@@ -31,7 +31,7 @@ export class Mongoose extends NodePerformanceIntegration implements Integr
return [
new MongooseInstrumentation({
responseHook(span) {
- addOriginToOtelSpan(span, 'auto.db.otel.mongoose');
+ addOriginToSpan(span, 'auto.db.otel.mongoose');
},
}),
];
diff --git a/packages/node-experimental/src/integrations/mysql2.ts b/packages/node-experimental/src/integrations/mysql2.ts
index b78b56bdd0ab..9a87de98fd66 100644
--- a/packages/node-experimental/src/integrations/mysql2.ts
+++ b/packages/node-experimental/src/integrations/mysql2.ts
@@ -2,7 +2,7 @@ import type { Instrumentation } from '@opentelemetry/instrumentation';
import { MySQL2Instrumentation } from '@opentelemetry/instrumentation-mysql2';
import type { Integration } from '@sentry/types';
-import { addOriginToOtelSpan } from '../utils/addOriginToSpan';
+import { addOriginToSpan } from '../utils/addOriginToSpan';
import { NodePerformanceIntegration } from './NodePerformanceIntegration';
/**
@@ -31,7 +31,7 @@ export class Mysql2 extends NodePerformanceIntegration implements Integrat
return [
new MySQL2Instrumentation({
responseHook(span) {
- addOriginToOtelSpan(span, 'auto.db.otel.mysql2');
+ addOriginToSpan(span, 'auto.db.otel.mysql2');
},
}),
];
diff --git a/packages/node-experimental/src/integrations/node-fetch.ts b/packages/node-experimental/src/integrations/node-fetch.ts
new file mode 100644
index 000000000000..73c1eaab27ae
--- /dev/null
+++ b/packages/node-experimental/src/integrations/node-fetch.ts
@@ -0,0 +1,123 @@
+import type { Span } from '@opentelemetry/api';
+import { SpanKind } from '@opentelemetry/api';
+import { registerInstrumentations } from '@opentelemetry/instrumentation';
+import { hasTracingEnabled } from '@sentry/core';
+import type { EventProcessor, Hub, Integration } from '@sentry/types';
+import { FetchInstrumentation } from 'opentelemetry-instrumentation-fetch-node';
+
+import { OTEL_ATTR_ORIGIN } from '../constants';
+import type { NodeExperimentalClient } from '../sdk/client';
+import { getCurrentHub } from '../sdk/hub';
+import { getRequestSpanData } from '../utils/getRequestSpanData';
+import { getSpanKind } from '../utils/getSpanKind';
+
+interface NodeFetchOptions {
+ /**
+ * Whether breadcrumbs should be recorded for requests
+ * Defaults to true
+ */
+ breadcrumbs?: boolean;
+
+ /**
+ * Whether tracing spans should be created for requests
+ * Defaults to false
+ */
+ spans?: boolean;
+}
+
+/**
+ * Fetch instrumentation based on opentelemetry-instrumentation-fetch.
+ * This instrumentation does two things:
+ * * Create breadcrumbs for outgoing requests
+ * * Create spans for outgoing requests
+ */
+export class NodeFetch implements Integration {
+ /**
+ * @inheritDoc
+ */
+ public static id: string = 'NodeFetch';
+
+ /**
+ * @inheritDoc
+ */
+ public name: string;
+
+ /**
+ * If spans for HTTP requests should be captured.
+ */
+ public shouldCreateSpansForRequests: boolean;
+
+ private _unload?: () => void;
+ private readonly _breadcrumbs: boolean;
+ // If this is undefined, use default behavior based on client settings
+ private readonly _spans: boolean | undefined;
+
+ /**
+ * @inheritDoc
+ */
+ public constructor(options: NodeFetchOptions = {}) {
+ this.name = NodeFetch.id;
+ this._breadcrumbs = typeof options.breadcrumbs === 'undefined' ? true : options.breadcrumbs;
+ this._spans = typeof options.spans === 'undefined' ? undefined : options.spans;
+
+ // Properly set in setupOnce based on client settings
+ this.shouldCreateSpansForRequests = false;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public setupOnce(_addGlobalEventProcessor: (callback: EventProcessor) => void, _getCurrentHub: () => Hub): void {
+ // No need to instrument if we don't want to track anything
+ if (!this._breadcrumbs && this._spans === false) {
+ return;
+ }
+
+ const client = getCurrentHub().getClient();
+ const clientOptions = client?.getOptions();
+
+ // This is used in the sampler function
+ this.shouldCreateSpansForRequests =
+ typeof this._spans === 'boolean' ? this._spans : hasTracingEnabled(clientOptions);
+
+ // Register instrumentations we care about
+ this._unload = registerInstrumentations({
+ instrumentations: [
+ new FetchInstrumentation({
+ onRequest: ({ span }: { span: Span }) => {
+ this._updateSpan(span);
+ this._addRequestBreadcrumb(span);
+ },
+ }),
+ ],
+ });
+ }
+
+ /**
+ * Unregister this integration.
+ */
+ public unregister(): void {
+ this._unload?.();
+ }
+
+ /** Update the span with data we need. */
+ private _updateSpan(span: Span): void {
+ span.setAttribute(OTEL_ATTR_ORIGIN, 'auto.http.otel.node_fetch');
+ }
+
+ /** Add a breadcrumb for outgoing requests. */
+ private _addRequestBreadcrumb(span: Span): void {
+ if (!this._breadcrumbs || getSpanKind(span) !== SpanKind.CLIENT) {
+ return;
+ }
+
+ const data = getRequestSpanData(span);
+ getCurrentHub().addBreadcrumb({
+ category: 'http',
+ data: {
+ ...data,
+ },
+ type: 'http',
+ });
+ }
+}
diff --git a/packages/node-experimental/src/integrations/postgres.ts b/packages/node-experimental/src/integrations/postgres.ts
index 4ecab8d685f2..85584f8a6507 100644
--- a/packages/node-experimental/src/integrations/postgres.ts
+++ b/packages/node-experimental/src/integrations/postgres.ts
@@ -2,7 +2,7 @@ import type { Instrumentation } from '@opentelemetry/instrumentation';
import { PgInstrumentation } from '@opentelemetry/instrumentation-pg';
import type { Integration } from '@sentry/types';
-import { addOriginToOtelSpan } from '../utils/addOriginToSpan';
+import { addOriginToSpan } from '../utils/addOriginToSpan';
import { NodePerformanceIntegration } from './NodePerformanceIntegration';
/**
@@ -32,7 +32,7 @@ export class Postgres extends NodePerformanceIntegration implements Integr
new PgInstrumentation({
requireParentSpan: true,
requestHook(span) {
- addOriginToOtelSpan(span, 'auto.db.otel.postgres');
+ addOriginToSpan(span, 'auto.db.otel.postgres');
},
}),
];
diff --git a/packages/node-experimental/src/opentelemetry/propagator.ts b/packages/node-experimental/src/opentelemetry/propagator.ts
new file mode 100644
index 000000000000..7aa43271b72c
--- /dev/null
+++ b/packages/node-experimental/src/opentelemetry/propagator.ts
@@ -0,0 +1,131 @@
+import type { Baggage, Context, SpanContext, TextMapGetter, TextMapSetter } from '@opentelemetry/api';
+import { propagation, trace, TraceFlags } from '@opentelemetry/api';
+import { isTracingSuppressed, W3CBaggagePropagator } from '@opentelemetry/core';
+import { getDynamicSamplingContextFromClient } from '@sentry/core';
+import type { DynamicSamplingContext, PropagationContext } from '@sentry/types';
+import { generateSentryTraceHeader, SENTRY_BAGGAGE_KEY_PREFIX, tracingContextFromHeaders } from '@sentry/utils';
+
+import { getCurrentHub } from '../sdk/hub';
+import { SENTRY_BAGGAGE_HEADER, SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY, SENTRY_TRACE_HEADER } from './../constants';
+import { getSpanScope } from './spanData';
+
+/**
+ * Injects and extracts `sentry-trace` and `baggage` headers from carriers.
+ */
+export class SentryPropagator extends W3CBaggagePropagator {
+ /**
+ * @inheritDoc
+ */
+ public inject(context: Context, carrier: unknown, setter: TextMapSetter): void {
+ if (isTracingSuppressed(context)) {
+ return;
+ }
+
+ let baggage = propagation.getBaggage(context) || propagation.createBaggage({});
+
+ const propagationContext = context.getValue(SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY) as
+ | PropagationContext
+ | undefined;
+
+ const { spanId, traceId, sampled } = getSentryTraceData(context, propagationContext);
+
+ const dynamicSamplingContext = propagationContext ? getDsc(context, propagationContext, traceId) : undefined;
+
+ if (dynamicSamplingContext) {
+ baggage = Object.entries(dynamicSamplingContext).reduce((b, [dscKey, dscValue]) => {
+ if (dscValue) {
+ return b.setEntry(`${SENTRY_BAGGAGE_KEY_PREFIX}${dscKey}`, { value: dscValue });
+ }
+ return b;
+ }, baggage);
+ }
+
+ setter.set(carrier, SENTRY_TRACE_HEADER, generateSentryTraceHeader(traceId, spanId, sampled));
+
+ super.inject(propagation.setBaggage(context, baggage), carrier, setter);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public extract(context: Context, carrier: unknown, getter: TextMapGetter): Context {
+ const maybeSentryTraceHeader: string | string[] | undefined = getter.get(carrier, SENTRY_TRACE_HEADER);
+ const maybeBaggageHeader = getter.get(carrier, SENTRY_BAGGAGE_HEADER);
+
+ const sentryTraceHeader = maybeSentryTraceHeader
+ ? Array.isArray(maybeSentryTraceHeader)
+ ? maybeSentryTraceHeader[0]
+ : maybeSentryTraceHeader
+ : undefined;
+
+ const { propagationContext } = tracingContextFromHeaders(sentryTraceHeader, maybeBaggageHeader);
+
+ // Add propagation context to context
+ const contextWithPropagationContext = context.setValue(SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY, propagationContext);
+
+ const spanContext: SpanContext = {
+ traceId: propagationContext.traceId,
+ spanId: propagationContext.parentSpanId || '',
+ isRemote: true,
+ traceFlags: propagationContext.sampled === true ? TraceFlags.SAMPLED : TraceFlags.NONE,
+ };
+
+ // Add remote parent span context
+ return trace.setSpanContext(contextWithPropagationContext, spanContext);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public fields(): string[] {
+ return [SENTRY_TRACE_HEADER, SENTRY_BAGGAGE_HEADER];
+ }
+}
+
+function getDsc(
+ context: Context,
+ propagationContext: PropagationContext,
+ traceId: string | undefined,
+): DynamicSamplingContext | undefined {
+ // If we have a DSC on the propagation context, we just use it
+ if (propagationContext.dsc) {
+ return propagationContext.dsc;
+ }
+
+ // Else, we try to generate a new one
+ const client = getCurrentHub().getClient();
+ const activeSpan = trace.getSpan(context);
+ const scope = activeSpan ? getSpanScope(activeSpan) : undefined;
+
+ if (client) {
+ return getDynamicSamplingContextFromClient(traceId || propagationContext.traceId, client, scope);
+ }
+
+ return undefined;
+}
+
+function getSentryTraceData(
+ context: Context,
+ propagationContext: PropagationContext | undefined,
+): {
+ spanId: string | undefined;
+ traceId: string | undefined;
+ sampled: boolean | undefined;
+} {
+ const span = trace.getSpan(context);
+ const spanContext = span && span.spanContext();
+
+ const traceId = spanContext ? spanContext.traceId : propagationContext?.traceId;
+
+ // We have a few scenarios here:
+ // If we have an active span, and it is _not_ remote, we just use the span's ID
+ // If we have an active span that is remote, we do not want to use the spanId, as we don't want to attach it to the parent span
+ // If `isRemote === true`, the span is bascially virtual
+ // If we don't have a local active span, we use the generated spanId from the propagationContext
+ const spanId = spanContext && !spanContext.isRemote ? spanContext.spanId : propagationContext?.spanId;
+
+ // eslint-disable-next-line no-bitwise
+ const sampled = spanContext ? Boolean(spanContext.traceFlags & TraceFlags.SAMPLED) : propagationContext?.sampled;
+
+ return { traceId, spanId, sampled };
+}
diff --git a/packages/node-experimental/src/opentelemetry/sampler.ts b/packages/node-experimental/src/opentelemetry/sampler.ts
new file mode 100644
index 000000000000..373c3b314b70
--- /dev/null
+++ b/packages/node-experimental/src/opentelemetry/sampler.ts
@@ -0,0 +1,193 @@
+/* eslint-disable no-bitwise */
+import type { Attributes, Context, SpanContext } from '@opentelemetry/api';
+import { isSpanContextValid, trace, TraceFlags } from '@opentelemetry/api';
+import type { Sampler, SamplingResult } from '@opentelemetry/sdk-trace-base';
+import { SamplingDecision } from '@opentelemetry/sdk-trace-base';
+import { hasTracingEnabled } from '@sentry/core';
+import type { Client, ClientOptions, PropagationContext, SamplingContext } from '@sentry/types';
+import { isNaN, logger } from '@sentry/utils';
+
+import {
+ OTEL_ATTR_PARENT_SAMPLED,
+ OTEL_ATTR_SENTRY_SAMPLE_RATE,
+ SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY,
+} from '../constants';
+
+/**
+ * A custom OTEL sampler that uses Sentry sampling rates to make it's decision
+ */
+export class SentrySampler implements Sampler {
+ private _client: Client;
+
+ public constructor(client: Client) {
+ this._client = client;
+ }
+
+ /** @inheritDoc */
+ public shouldSample(
+ context: Context,
+ traceId: string,
+ spanName: string,
+ _spanKind: unknown,
+ _attributes: unknown,
+ _links: unknown,
+ ): SamplingResult {
+ const options = this._client.getOptions();
+
+ if (!hasTracingEnabled(options)) {
+ return { decision: SamplingDecision.NOT_RECORD };
+ }
+
+ const parentContext = trace.getSpanContext(context);
+
+ let parentSampled: boolean | undefined = undefined;
+
+ // Only inherit sample rate if `traceId` is the same
+ // Note for testing: `isSpanContextValid()` checks the format of the traceId/spanId, so we need to pass valid ones
+ if (parentContext && isSpanContextValid(parentContext) && parentContext.traceId === traceId) {
+ if (parentContext.isRemote) {
+ parentSampled = getParentRemoteSampled(parentContext, context);
+ __DEBUG_BUILD__ &&
+ logger.log(`[Tracing] Inheriting remote parent's sampled decision for ${spanName}: ${parentSampled}`);
+ } else {
+ parentSampled = Boolean(parentContext.traceFlags & TraceFlags.SAMPLED);
+ __DEBUG_BUILD__ &&
+ logger.log(`[Tracing] Inheriting parent's sampled decision for ${spanName}: ${parentSampled}`);
+ }
+ }
+
+ const sampleRate = getSampleRate(options, {
+ transactionContext: {
+ name: spanName,
+ parentSampled,
+ },
+ parentSampled,
+ });
+
+ const attributes: Attributes = {
+ [OTEL_ATTR_SENTRY_SAMPLE_RATE]: Number(sampleRate),
+ };
+
+ if (typeof parentSampled === 'boolean') {
+ attributes[OTEL_ATTR_PARENT_SAMPLED] = parentSampled;
+ }
+
+ // Since this is coming from the user (or from a function provided by the user), who knows what we might get. (The
+ // only valid values are booleans or numbers between 0 and 1.)
+ if (!isValidSampleRate(sampleRate)) {
+ __DEBUG_BUILD__ && logger.warn('[Tracing] Discarding span because of invalid sample rate.');
+
+ return {
+ decision: SamplingDecision.NOT_RECORD,
+ attributes,
+ };
+ }
+
+ // if the function returned 0 (or false), or if `tracesSampleRate` is 0, it's a sign the transaction should be dropped
+ if (!sampleRate) {
+ __DEBUG_BUILD__ &&
+ logger.log(
+ `[Tracing] Discarding span because ${
+ typeof options.tracesSampler === 'function'
+ ? 'tracesSampler returned 0 or false'
+ : 'a negative sampling decision was inherited or tracesSampleRate is set to 0'
+ }`,
+ );
+
+ return {
+ decision: SamplingDecision.NOT_RECORD,
+ attributes,
+ };
+ }
+
+ // Now we roll the dice. Math.random is inclusive of 0, but not of 1, so strict < is safe here. In case sampleRate is
+ // a boolean, the < comparison will cause it to be automatically cast to 1 if it's true and 0 if it's false.
+ const isSampled = Math.random() < (sampleRate as number | boolean);
+
+ // if we're not going to keep it, we're done
+ if (!isSampled) {
+ __DEBUG_BUILD__ &&
+ logger.log(
+ `[Tracing] Discarding span because it's not included in the random sample (sampling rate = ${Number(
+ sampleRate,
+ )})`,
+ );
+
+ return {
+ decision: SamplingDecision.NOT_RECORD,
+ attributes,
+ };
+ }
+
+ return {
+ decision: SamplingDecision.RECORD_AND_SAMPLED,
+ attributes,
+ };
+ }
+
+ /** Returns the sampler name or short description with the configuration. */
+ public toString(): string {
+ return 'SentrySampler';
+ }
+}
+
+function getSampleRate(
+ options: Pick,
+ samplingContext: SamplingContext,
+): number | boolean {
+ if (typeof options.tracesSampler === 'function') {
+ return options.tracesSampler(samplingContext);
+ }
+
+ if (samplingContext.parentSampled !== undefined) {
+ return samplingContext.parentSampled;
+ }
+
+ if (typeof options.tracesSampleRate !== 'undefined') {
+ return options.tracesSampleRate;
+ }
+
+ // When `enableTracing === true`, we use a sample rate of 100%
+ if (options.enableTracing) {
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * Checks the given sample rate to make sure it is valid type and value (a boolean, or a number between 0 and 1).
+ */
+function isValidSampleRate(rate: unknown): boolean {
+ // we need to check NaN explicitly because it's of type 'number' and therefore wouldn't get caught by this typecheck
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ if (isNaN(rate) || !(typeof rate === 'number' || typeof rate === 'boolean')) {
+ __DEBUG_BUILD__ &&
+ logger.warn(
+ `[Tracing] Given sample rate is invalid. Sample rate must be a boolean or a number between 0 and 1. Got ${JSON.stringify(
+ rate,
+ )} of type ${JSON.stringify(typeof rate)}.`,
+ );
+ return false;
+ }
+
+ // in case sampleRate is a boolean, it will get automatically cast to 1 if it's true and 0 if it's false
+ if (rate < 0 || rate > 1) {
+ __DEBUG_BUILD__ &&
+ logger.warn(`[Tracing] Given sample rate is invalid. Sample rate must be between 0 and 1. Got ${rate}.`);
+ return false;
+ }
+ return true;
+}
+
+function getPropagationContext(parentContext: Context): PropagationContext | undefined {
+ return parentContext.getValue(SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY) as PropagationContext | undefined;
+}
+
+function getParentRemoteSampled(spanContext: SpanContext, context: Context): boolean | undefined {
+ const traceId = spanContext.traceId;
+ const traceparentData = getPropagationContext(context);
+
+ // Only inherit sample rate if `traceId` is the same
+ return traceparentData && traceId === traceparentData.traceId ? traceparentData.sampled : undefined;
+}
diff --git a/packages/node-experimental/src/opentelemetry/spanData.ts b/packages/node-experimental/src/opentelemetry/spanData.ts
new file mode 100644
index 000000000000..e8fe58506866
--- /dev/null
+++ b/packages/node-experimental/src/opentelemetry/spanData.ts
@@ -0,0 +1,52 @@
+import type { Span } from '@opentelemetry/api';
+import type { Hub, Scope, TransactionMetadata } from '@sentry/types';
+
+import type { AbstractSpan } from '../types';
+
+// We store the parent span, scope & metadata in separate weakmaps, so we can access them for a given span
+// This way we can enhance the data that an OTEL Span natively gives us
+// and since we are using weakmaps, we do not need to clean up after ourselves
+const SpanScope = new WeakMap();
+const SpanHub = new WeakMap();
+const SpanParent = new WeakMap();
+const SpanMetadata = new WeakMap>();
+
+/** Set the Sentry scope on an OTEL span. */
+export function setSpanScope(span: AbstractSpan, scope: Scope): void {
+ SpanScope.set(span, scope);
+}
+
+/** Get the Sentry scope of an OTEL span. */
+export function getSpanScope(span: AbstractSpan): Scope | undefined {
+ return SpanScope.get(span);
+}
+
+/** Set the Sentry hub on an OTEL span. */
+export function setSpanHub(span: AbstractSpan, hub: Hub): void {
+ SpanHub.set(span, hub);
+}
+
+/** Get the Sentry hub of an OTEL span. */
+export function getSpanHub(span: AbstractSpan): Hub | undefined {
+ return SpanHub.get(span);
+}
+
+/** Set the parent OTEL span on an OTEL span. */
+export function setSpanParent(span: AbstractSpan, parentSpan: Span): void {
+ SpanParent.set(span, parentSpan);
+}
+
+/** Get the parent OTEL span of an OTEL span. */
+export function getSpanParent(span: AbstractSpan): Span | undefined {
+ return SpanParent.get(span);
+}
+
+/** Set metadata for an OTEL span. */
+export function setSpanMetadata(span: AbstractSpan, metadata: Partial): void {
+ SpanMetadata.set(span, metadata);
+}
+
+/** Get metadata for an OTEL span. */
+export function getSpanMetadata(span: AbstractSpan): Partial | undefined {
+ return SpanMetadata.get(span);
+}
diff --git a/packages/node-experimental/src/opentelemetry/spanExporter.ts b/packages/node-experimental/src/opentelemetry/spanExporter.ts
new file mode 100644
index 000000000000..e242f74d6104
--- /dev/null
+++ b/packages/node-experimental/src/opentelemetry/spanExporter.ts
@@ -0,0 +1,325 @@
+import type { Span } from '@opentelemetry/api';
+import { SpanKind } from '@opentelemetry/api';
+import type { ExportResult } from '@opentelemetry/core';
+import { ExportResultCode } from '@opentelemetry/core';
+import type { ReadableSpan, Span as SdkTraceBaseSpan, SpanExporter } from '@opentelemetry/sdk-trace-base';
+import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
+import { flush } from '@sentry/core';
+import { mapOtelStatus, parseOtelSpanDescription } from '@sentry/opentelemetry-node';
+import type { DynamicSamplingContext, Span as SentrySpan, SpanOrigin, TransactionSource } from '@sentry/types';
+import { logger } from '@sentry/utils';
+
+import {
+ OTEL_ATTR_OP,
+ OTEL_ATTR_ORIGIN,
+ OTEL_ATTR_PARENT_SAMPLED,
+ OTEL_ATTR_SENTRY_SAMPLE_RATE,
+ OTEL_ATTR_SOURCE,
+} from '../constants';
+import { getCurrentHub } from '../sdk/hub';
+import { NodeExperimentalScope } from '../sdk/scope';
+import type { NodeExperimentalTransaction } from '../sdk/transaction';
+import { startTransaction } from '../sdk/transaction';
+import { convertOtelTimeToSeconds } from '../utils/convertOtelTimeToSeconds';
+import { getRequestSpanData } from '../utils/getRequestSpanData';
+import type { SpanNode } from '../utils/groupSpansWithParents';
+import { groupSpansWithParents } from '../utils/groupSpansWithParents';
+import { getSpanHub, getSpanMetadata, getSpanScope } from './spanData';
+
+type SpanNodeCompleted = SpanNode & { span: ReadableSpan };
+
+/**
+ * A Sentry-specific exporter that converts OpenTelemetry Spans to Sentry Spans & Transactions.
+ */
+export class SentrySpanExporter implements SpanExporter {
+ private _finishedSpans: ReadableSpan[];
+ private _stopped: boolean;
+
+ public constructor() {
+ this._stopped = false;
+ this._finishedSpans = [];
+ }
+
+ /** @inheritDoc */
+ public export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void {
+ if (this._stopped) {
+ return resultCallback({
+ code: ExportResultCode.FAILED,
+ error: new Error('Exporter has been stopped'),
+ });
+ }
+
+ const openSpanCount = this._finishedSpans.length;
+ const newSpanCount = spans.length;
+
+ this._finishedSpans.push(...spans);
+
+ const remainingSpans = maybeSend(this._finishedSpans);
+
+ const remainingOpenSpanCount = remainingSpans.length;
+ const sentSpanCount = openSpanCount + newSpanCount - remainingOpenSpanCount;
+
+ __DEBUG_BUILD__ &&
+ logger.log(`SpanExporter exported ${sentSpanCount} spans, ${remainingOpenSpanCount} unsent spans remaining`);
+
+ this._finishedSpans = remainingSpans.filter(span => {
+ const shouldDrop = shouldCleanupSpan(span, 5 * 60);
+ __DEBUG_BUILD__ &&
+ shouldDrop &&
+ logger.log(
+ `SpanExporter dropping span ${span.name} (${
+ span.spanContext().spanId
+ }) because it is pending for more than 5 minutes.`,
+ );
+ return !shouldDrop;
+ });
+
+ resultCallback({ code: ExportResultCode.SUCCESS });
+ }
+
+ /** @inheritDoc */
+ public shutdown(): Promise {
+ this._stopped = true;
+ this._finishedSpans = [];
+ return this.forceFlush();
+ }
+
+ /** @inheritDoc */
+ public async forceFlush(): Promise {
+ await flush();
+ }
+}
+
+/**
+ * Send the given spans, but only if they are part of a finished transaction.
+ *
+ * Returns the unsent spans.
+ * Spans remain unsent when their parent span is not yet finished.
+ * This will happen regularly, as child spans are generally finished before their parents.
+ * But it _could_ also happen because, for whatever reason, a parent span was lost.
+ * In this case, we'll eventually need to clean this up.
+ */
+function maybeSend(spans: ReadableSpan[]): ReadableSpan[] {
+ const grouped = groupSpansWithParents(spans);
+ const remaining = new Set(grouped);
+
+ const rootNodes = getCompletedRootNodes(grouped);
+
+ rootNodes.forEach(root => {
+ remaining.delete(root);
+ const span = root.span;
+ const transaction = createTransactionForOtelSpan(span);
+
+ root.children.forEach(child => {
+ createAndFinishSpanForOtelSpan(child, transaction, remaining);
+ });
+
+ // Now finish the transaction, which will send it together with all the spans
+ // We make sure to use the current span as the activeSpan for this transaction
+ const scope = getSpanScope(span);
+ const forkedScope = NodeExperimentalScope.clone(
+ scope as NodeExperimentalScope | undefined,
+ ) as NodeExperimentalScope;
+ forkedScope.activeSpan = span as unknown as Span;
+
+ transaction.finishWithScope(convertOtelTimeToSeconds(span.endTime), forkedScope);
+ });
+
+ return Array.from(remaining)
+ .map(node => node.span)
+ .filter((span): span is ReadableSpan => !!span);
+}
+
+function getCompletedRootNodes(nodes: SpanNode[]): SpanNodeCompleted[] {
+ return nodes.filter((node): node is SpanNodeCompleted => !!node.span && !node.parentNode);
+}
+
+function shouldCleanupSpan(span: ReadableSpan, maxStartTimeOffsetSeconds: number): boolean {
+ const cutoff = Date.now() / 1000 - maxStartTimeOffsetSeconds;
+ return convertOtelTimeToSeconds(span.startTime) < cutoff;
+}
+
+function parseSpan(span: ReadableSpan): { op?: string; origin?: SpanOrigin; source?: TransactionSource } {
+ const attributes = span.attributes;
+
+ const origin = attributes[OTEL_ATTR_ORIGIN] as SpanOrigin | undefined;
+ const op = attributes[OTEL_ATTR_OP] as string | undefined;
+ const source = attributes[OTEL_ATTR_SOURCE] as TransactionSource | undefined;
+
+ return { origin, op, source };
+}
+
+function createTransactionForOtelSpan(span: ReadableSpan): NodeExperimentalTransaction {
+ const scope = getSpanScope(span);
+ const hub = getSpanHub(span) || getCurrentHub();
+ const spanContext = span.spanContext();
+ const spanId = spanContext.spanId;
+ const traceId = spanContext.traceId;
+ const parentSpanId = span.parentSpanId;
+
+ const parentSampled = span.attributes[OTEL_ATTR_PARENT_SAMPLED] as boolean | undefined;
+ const dynamicSamplingContext: DynamicSamplingContext | undefined = scope
+ ? scope.getPropagationContext().dsc
+ : undefined;
+
+ const { op, description, tags, data, origin, source } = getSpanData(span as SdkTraceBaseSpan);
+ const metadata = getSpanMetadata(span);
+
+ const transaction = startTransaction(hub, {
+ spanId,
+ traceId,
+ parentSpanId,
+ parentSampled,
+ name: description,
+ op,
+ instrumenter: 'otel',
+ status: mapOtelStatus(span as SdkTraceBaseSpan),
+ startTimestamp: convertOtelTimeToSeconds(span.startTime),
+ metadata: {
+ dynamicSamplingContext,
+ source,
+ sampleRate: span.attributes[OTEL_ATTR_SENTRY_SAMPLE_RATE] as number | undefined,
+ ...metadata,
+ },
+ data: removeSentryAttributes(data),
+ origin,
+ tags,
+ sampled: true,
+ }) as NodeExperimentalTransaction;
+
+ transaction.setContext('otel', {
+ attributes: removeSentryAttributes(span.attributes),
+ resource: span.resource.attributes,
+ });
+
+ return transaction;
+}
+
+function createAndFinishSpanForOtelSpan(node: SpanNode, sentryParentSpan: SentrySpan, remaining: Set): void {
+ remaining.delete(node);
+ const span = node.span;
+
+ const shouldDrop = !span;
+
+ // If this span should be dropped, we still want to create spans for the children of this
+ if (shouldDrop) {
+ node.children.forEach(child => {
+ createAndFinishSpanForOtelSpan(child, sentryParentSpan, remaining);
+ });
+ return;
+ }
+
+ const spanId = span.spanContext().spanId;
+ const { attributes } = span;
+
+ const { op, description, tags, data, origin } = getSpanData(span as SdkTraceBaseSpan);
+ const allData = { ...removeSentryAttributes(attributes), ...data };
+
+ const sentrySpan = sentryParentSpan.startChild({
+ description,
+ op,
+ data: allData,
+ status: mapOtelStatus(span as SdkTraceBaseSpan),
+ instrumenter: 'otel',
+ startTimestamp: convertOtelTimeToSeconds(span.startTime),
+ spanId,
+ origin,
+ tags,
+ });
+
+ node.children.forEach(child => {
+ createAndFinishSpanForOtelSpan(child, sentrySpan, remaining);
+ });
+
+ sentrySpan.finish(convertOtelTimeToSeconds(span.endTime));
+}
+
+function getSpanData(span: ReadableSpan): {
+ tags: Record;
+ data: Record;
+ op?: string;
+ description: string;
+ source?: TransactionSource;
+ origin?: SpanOrigin;
+} {
+ const { op: definedOp, source: definedSource, origin } = parseSpan(span);
+ const {
+ op: inferredOp,
+ description,
+ source: inferredSource,
+ data: inferredData,
+ } = parseOtelSpanDescription(span as SdkTraceBaseSpan);
+
+ const op = definedOp || inferredOp;
+ const source = definedSource || inferredSource;
+
+ const tags = getTags(span);
+ const data = { ...inferredData, ...getData(span) };
+
+ return {
+ op,
+ description,
+ source,
+ origin,
+ tags,
+ data,
+ };
+}
+
+/**
+ * Remove custom `sentry.` attribtues we do not need to send.
+ * These are more carrier attributes we use inside of the SDK, we do not need to send them to the API.
+ */
+function removeSentryAttributes(data: Record): Record {
+ const cleanedData = { ...data };
+
+ /* eslint-disable @typescript-eslint/no-dynamic-delete */
+ delete cleanedData[OTEL_ATTR_PARENT_SAMPLED];
+ delete cleanedData[OTEL_ATTR_ORIGIN];
+ delete cleanedData[OTEL_ATTR_OP];
+ delete cleanedData[OTEL_ATTR_SOURCE];
+ delete cleanedData[OTEL_ATTR_SENTRY_SAMPLE_RATE];
+ /* eslint-enable @typescript-eslint/no-dynamic-delete */
+
+ return cleanedData;
+}
+
+function getTags(span: ReadableSpan): Record {
+ const attributes = span.attributes;
+ const tags: Record = {};
+
+ if (attributes[SemanticAttributes.HTTP_STATUS_CODE]) {
+ const statusCode = attributes[SemanticAttributes.HTTP_STATUS_CODE] as string;
+
+ tags['http.status_code'] = statusCode;
+ }
+
+ return tags;
+}
+
+function getData(span: ReadableSpan): Record {
+ const attributes = span.attributes;
+ const data: Record = {
+ 'otel.kind': SpanKind[span.kind],
+ };
+
+ if (attributes[SemanticAttributes.HTTP_STATUS_CODE]) {
+ const statusCode = attributes[SemanticAttributes.HTTP_STATUS_CODE] as string;
+ data['http.response.status_code'] = statusCode;
+ }
+
+ const requestData = getRequestSpanData(span);
+
+ if (requestData.url) {
+ data.url = requestData.url;
+ }
+
+ if (requestData['http.query']) {
+ data['http.query'] = requestData['http.query'].slice(1);
+ }
+ if (requestData['http.fragment']) {
+ data['http.fragment'] = requestData['http.fragment'].slice(1);
+ }
+
+ return data;
+}
diff --git a/packages/node-experimental/src/opentelemetry/spanProcessor.ts b/packages/node-experimental/src/opentelemetry/spanProcessor.ts
new file mode 100644
index 000000000000..c7e07d11aa8e
--- /dev/null
+++ b/packages/node-experimental/src/opentelemetry/spanProcessor.ts
@@ -0,0 +1,99 @@
+import type { Context } from '@opentelemetry/api';
+import { ROOT_CONTEXT, SpanKind, trace } from '@opentelemetry/api';
+import type { Span, SpanProcessor as SpanProcessorInterface } from '@opentelemetry/sdk-trace-base';
+import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
+import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
+import { maybeCaptureExceptionForTimedEvent } from '@sentry/opentelemetry-node';
+import type { Hub } from '@sentry/types';
+import { logger } from '@sentry/utils';
+
+import { OTEL_CONTEXT_HUB_KEY } from '../constants';
+import { Http } from '../integrations';
+import { NodeFetch } from '../integrations/node-fetch';
+import type { NodeExperimentalClient } from '../sdk/client';
+import { getCurrentHub } from '../sdk/hub';
+import { getSpanHub, setSpanHub, setSpanParent, setSpanScope } from './spanData';
+import { SentrySpanExporter } from './spanExporter';
+
+/**
+ * Converts OpenTelemetry Spans to Sentry Spans and sends them to Sentry via
+ * the Sentry SDK.
+ */
+export class SentrySpanProcessor extends BatchSpanProcessor implements SpanProcessorInterface {
+ public constructor() {
+ super(new SentrySpanExporter());
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public onStart(span: Span, parentContext: Context): void {
+ // This is a reliable way to get the parent span - because this is exactly how the parent is identified in the OTEL SDK
+ const parentSpan = trace.getSpan(parentContext);
+ const hub = parentContext.getValue(OTEL_CONTEXT_HUB_KEY) as Hub | undefined;
+
+ // We need access to the parent span in order to be able to move up the span tree for breadcrumbs
+ if (parentSpan) {
+ setSpanParent(span, parentSpan);
+ }
+
+ // The root context does not have a hub stored, so we check for this specifically
+ // We do this instead of just falling back to `getCurrentHub` to avoid attaching the wrong hub
+ let actualHub = hub;
+ if (parentContext === ROOT_CONTEXT) {
+ actualHub = getCurrentHub();
+ }
+
+ // We need the scope at time of span creation in order to apply it to the event when the span is finished
+ if (actualHub) {
+ setSpanScope(span, actualHub.getScope());
+ setSpanHub(span, actualHub);
+ }
+
+ __DEBUG_BUILD__ && logger.log(`[Tracing] Starting span "${span.name}" (${span.spanContext().spanId})`);
+
+ return super.onStart(span, parentContext);
+ }
+
+ /** @inheritDoc */
+ public onEnd(span: Span): void {
+ __DEBUG_BUILD__ && logger.log(`[Tracing] Finishing span "${span.name}" (${span.spanContext().spanId})`);
+
+ if (!shouldCaptureSentrySpan(span)) {
+ // Prevent this being called to super.onEnd(), which would pass this to the span exporter
+ return;
+ }
+
+ // Capture exceptions as events
+ const hub = getSpanHub(span) || getCurrentHub();
+ span.events.forEach(event => {
+ maybeCaptureExceptionForTimedEvent(hub, event, span);
+ });
+
+ return super.onEnd(span);
+ }
+}
+
+function shouldCaptureSentrySpan(span: Span): boolean {
+ const client = getCurrentHub().getClient();
+ const httpIntegration = client ? client.getIntegration(Http) : undefined;
+ const fetchIntegration = client ? client.getIntegration(NodeFetch) : undefined;
+
+ // If we encounter a client or server span with url & method, we assume this comes from the http instrumentation
+ // In this case, if `shouldCreateSpansForRequests` is false, we want to _record_ the span but not _sample_ it,
+ // So we can generate a breadcrumb for it but no span will be sent
+ if (
+ (span.kind === SpanKind.CLIENT || span.kind === SpanKind.SERVER) &&
+ span.attributes[SemanticAttributes.HTTP_URL] &&
+ span.attributes[SemanticAttributes.HTTP_METHOD]
+ ) {
+ const shouldCreateSpansForRequests =
+ span.attributes['http.client'] === 'fetch'
+ ? fetchIntegration?.shouldCreateSpansForRequests
+ : httpIntegration?.shouldCreateSpansForRequests;
+
+ return shouldCreateSpansForRequests !== false;
+ }
+
+ return true;
+}
diff --git a/packages/node-experimental/src/sdk/client.ts b/packages/node-experimental/src/sdk/client.ts
index 29f68980f008..a3145475e307 100644
--- a/packages/node-experimental/src/sdk/client.ts
+++ b/packages/node-experimental/src/sdk/client.ts
@@ -1,5 +1,6 @@
import type { Tracer } from '@opentelemetry/api';
import { trace } from '@opentelemetry/api';
+import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
import type { EventHint, Scope } from '@sentry/node';
import { NodeClient, SDK_VERSION } from '@sentry/node';
import type { Event } from '@sentry/types';
@@ -8,12 +9,13 @@ import type {
NodeExperimentalClient as NodeExperimentalClientInterface,
NodeExperimentalClientOptions,
} from '../types';
-import { OtelScope } from './scope';
+import { NodeExperimentalScope } from './scope';
/**
* A client built on top of the NodeClient, which provides some otel-specific things on top.
*/
export class NodeExperimentalClient extends NodeClient implements NodeExperimentalClientInterface {
+ public traceProvider: BasicTracerProvider | undefined;
private _tracer: Tracer | undefined;
public constructor(options: ConstructorParameters[0]) {
@@ -54,16 +56,30 @@ export class NodeExperimentalClient extends NodeClient implements NodeExperiment
return super.getOptions();
}
+ /**
+ * @inheritDoc
+ */
+ public async flush(timeout?: number): Promise {
+ const provider = this.traceProvider;
+ const spanProcessor = provider?.activeSpanProcessor;
+
+ if (spanProcessor) {
+ await spanProcessor.forceFlush();
+ }
+
+ return super.flush(timeout);
+ }
+
/**
* Extends the base `_prepareEvent` so that we can properly handle `captureContext`.
- * This uses `Scope.clone()`, which we need to replace with `OtelScope.clone()` for this client.
+ * This uses `Scope.clone()`, which we need to replace with `NodeExperimentalScope.clone()` for this client.
*/
protected _prepareEvent(event: Event, hint: EventHint, scope?: Scope): PromiseLike {
let actualScope = scope;
// Remove `captureContext` hint and instead clone already here
if (hint && hint.captureContext) {
- actualScope = OtelScope.clone(scope);
+ actualScope = NodeExperimentalScope.clone(scope);
delete hint.captureContext;
}
diff --git a/packages/node-experimental/src/sdk/hub.ts b/packages/node-experimental/src/sdk/hub.ts
index 8220265e600c..50958d13c84d 100644
--- a/packages/node-experimental/src/sdk/hub.ts
+++ b/packages/node-experimental/src/sdk/hub.ts
@@ -3,12 +3,14 @@ import { Hub } from '@sentry/core';
import type { Client } from '@sentry/types';
import { getGlobalSingleton, GLOBAL_OBJ } from '@sentry/utils';
-import { OtelScope } from './scope';
+import { NodeExperimentalScope } from './scope';
-/** A custom hub that ensures we always creat an OTEL scope. */
-
-class OtelHub extends Hub {
- public constructor(client?: Client, scope: Scope = new OtelScope()) {
+/**
+ * A custom hub that ensures we always creat an OTEL scope.
+ * Exported only for testing
+ */
+export class NodeExperimentalHub extends Hub {
+ public constructor(client?: Client, scope: Scope = new NodeExperimentalScope()) {
super(client, scope);
}
@@ -17,7 +19,7 @@ class OtelHub extends Hub {
*/
public pushScope(): Scope {
// We want to clone the content of prev scope
- const scope = OtelScope.clone(this.getScope());
+ const scope = NodeExperimentalScope.clone(this.getScope());
this.getStack().push({
client: this.getClient(),
scope,
@@ -29,11 +31,11 @@ class OtelHub extends Hub {
/**
* *******************************************************************************
* Everything below here is a copy of the stuff from core's hub.ts,
- * only that we make sure to create our custom OtelScope instead of the default Scope.
+ * only that we make sure to create our custom NodeExperimentalScope instead of the default Scope.
* This is necessary to get the correct breadcrumbs behavior.
*
- * Basically, this overwrites all places that do `new Scope()` with `new OtelScope()`.
- * Which in turn means overwriting all places that do `new Hub()` and make sure to pass in a OtelScope instead.
+ * Basically, this overwrites all places that do `new Scope()` with `new NodeExperimentalScope()`.
+ * Which in turn means overwriting all places that do `new Hub()` and make sure to pass in a NodeExperimentalScope instead.
* *******************************************************************************
*/
@@ -77,7 +79,7 @@ export function getCurrentHub(): Hub {
* @hidden
*/
export function getHubFromCarrier(carrier: Carrier): Hub {
- return getGlobalSingleton('hub', () => new OtelHub(), carrier);
+ return getGlobalSingleton('hub', () => new NodeExperimentalHub(), carrier);
}
/**
@@ -89,14 +91,17 @@ export function ensureHubOnCarrier(carrier: Carrier, parent: Hub = getGlobalHub(
// If there's no hub on current domain, or it's an old API, assign a new one
if (!hasHubOnCarrier(carrier) || getHubFromCarrier(carrier).isOlderThan(API_VERSION)) {
const globalHubTopStack = parent.getStackTop();
- setHubOnCarrier(carrier, new OtelHub(globalHubTopStack.client, OtelScope.clone(globalHubTopStack.scope)));
+ setHubOnCarrier(
+ carrier,
+ new NodeExperimentalHub(globalHubTopStack.client, NodeExperimentalScope.clone(globalHubTopStack.scope)),
+ );
}
}
function getGlobalHub(registry: Carrier = getMainCarrier()): Hub {
// If there's no hub, or its an old API, assign a new one
if (!hasHubOnCarrier(registry) || getHubFromCarrier(registry).isOlderThan(API_VERSION)) {
- setHubOnCarrier(registry, new OtelHub());
+ setHubOnCarrier(registry, new NodeExperimentalHub());
}
// Return hub that lives on a global object
diff --git a/packages/node-experimental/src/sdk/hubextensions.ts b/packages/node-experimental/src/sdk/hubextensions.ts
index 4971226fee01..07ee08c1f7f9 100644
--- a/packages/node-experimental/src/sdk/hubextensions.ts
+++ b/packages/node-experimental/src/sdk/hubextensions.ts
@@ -1,11 +1,5 @@
-import type { startTransaction } from '@sentry/core';
import { addTracingExtensions as _addTracingExtensions, getMainCarrier } from '@sentry/core';
-import type { Breadcrumb, Hub, Transaction } from '@sentry/types';
-import { dateTimestampInSeconds } from '@sentry/utils';
-
-import type { TransactionWithBreadcrumbs } from '../types';
-
-const DEFAULT_MAX_BREADCRUMBS = 100;
+import type { CustomSamplingContext, TransactionContext } from '@sentry/types';
/**
* Add tracing extensions, ensuring a patched `startTransaction` to work with OTEL.
@@ -19,62 +13,18 @@ export function addTracingExtensions(): void {
}
carrier.__SENTRY__.extensions = carrier.__SENTRY__.extensions || {};
- if (carrier.__SENTRY__.extensions.startTransaction) {
- carrier.__SENTRY__.extensions.startTransaction = getPatchedStartTransaction(
- carrier.__SENTRY__.extensions.startTransaction as typeof startTransaction,
- );
- }
-}
-
-/**
- * We patch the `startTransaction` function to ensure we create a `TransactionWithBreadcrumbs` instead of a regular `Transaction`.
- */
-function getPatchedStartTransaction(_startTransaction: typeof startTransaction): typeof startTransaction {
- return function (this: Hub, ...args) {
- const transaction = _startTransaction.apply(this, args);
-
- return patchTransaction(transaction);
- };
-}
-
-function patchTransaction(transaction: Transaction): TransactionWithBreadcrumbs {
- return new Proxy(transaction as TransactionWithBreadcrumbs, {
- get(target, prop, receiver) {
- if (prop === 'addBreadcrumb') {
- return addBreadcrumb;
- }
- if (prop === 'getBreadcrumbs') {
- return getBreadcrumbs;
- }
- if (prop === '_breadcrumbs') {
- const breadcrumbs = Reflect.get(target, prop, receiver);
- return breadcrumbs || [];
- }
- return Reflect.get(target, prop, receiver);
- },
- });
-}
-
-/** Add a breadcrumb to a transaction. */
-function addBreadcrumb(this: TransactionWithBreadcrumbs, breadcrumb: Breadcrumb, maxBreadcrumbs?: number): void {
- const maxCrumbs = typeof maxBreadcrumbs === 'number' ? maxBreadcrumbs : DEFAULT_MAX_BREADCRUMBS;
-
- // No data has been changed, so don't notify scope listeners
- if (maxCrumbs <= 0) {
- return;
+ if (carrier.__SENTRY__.extensions.startTransaction !== startTransactionNoop) {
+ carrier.__SENTRY__.extensions.startTransaction = startTransactionNoop;
}
-
- const mergedBreadcrumb = {
- timestamp: dateTimestampInSeconds(),
- ...breadcrumb,
- };
-
- const breadcrumbs = this._breadcrumbs;
- breadcrumbs.push(mergedBreadcrumb);
- this._breadcrumbs = breadcrumbs.length > maxCrumbs ? breadcrumbs.slice(-maxCrumbs) : breadcrumbs;
}
-/** Get all breadcrumbs from a transaction. */
-function getBreadcrumbs(this: TransactionWithBreadcrumbs): Breadcrumb[] {
- return this._breadcrumbs;
+function startTransactionNoop(
+ _transactionContext: TransactionContext,
+ _customSamplingContext?: CustomSamplingContext,
+): unknown {
+ // eslint-disable-next-line no-console
+ console.warn('startTransaction is a noop in @sentry/node-experimental. Use `startSpan` instead.');
+ // We return an object here as hub.ts checks for the result of this
+ // and renders a different warning if this is empty
+ return {};
}
diff --git a/packages/node-experimental/src/sdk/init.ts b/packages/node-experimental/src/sdk/init.ts
index 070728367925..c33a90f037d7 100644
--- a/packages/node-experimental/src/sdk/init.ts
+++ b/packages/node-experimental/src/sdk/init.ts
@@ -1,24 +1,38 @@
import { hasTracingEnabled } from '@sentry/core';
import { defaultIntegrations as defaultNodeIntegrations, init as initNode } from '@sentry/node';
+import type { Integration } from '@sentry/types';
+import { parseSemver } from '@sentry/utils';
import { getAutoPerformanceIntegrations } from '../integrations/getAutoPerformanceIntegrations';
import { Http } from '../integrations/http';
+import { NodeFetch } from '../integrations/node-fetch';
import type { NodeExperimentalOptions } from '../types';
import { NodeExperimentalClient } from './client';
+import { getCurrentHub } from './hub';
import { initOtel } from './initOtel';
import { setOtelContextAsyncContextStrategy } from './otelAsyncContextStrategy';
+const NODE_VERSION: ReturnType = parseSemver(process.versions.node);
const ignoredDefaultIntegrations = ['Http', 'Undici'];
-export const defaultIntegrations = [
+export const defaultIntegrations: Integration[] = [
...defaultNodeIntegrations.filter(i => !ignoredDefaultIntegrations.includes(i.name)),
new Http(),
];
+// Only add NodeFetch if Node >= 16, as previous versions do not support it
+if (NODE_VERSION.major && NODE_VERSION.major >= 16) {
+ defaultIntegrations.push(new NodeFetch());
+}
+
/**
* Initialize Sentry for Node.
*/
export function init(options: NodeExperimentalOptions | undefined = {}): void {
+ // Ensure we register our own global hub before something else does
+ // This will register the NodeExperimentalHub as the global hub
+ getCurrentHub();
+
const isTracingEnabled = hasTracingEnabled(options);
options.defaultIntegrations =
diff --git a/packages/node-experimental/src/sdk/initOtel.ts b/packages/node-experimental/src/sdk/initOtel.ts
index 3ed0e2ab2b2b..b60dac87aeda 100644
--- a/packages/node-experimental/src/sdk/initOtel.ts
+++ b/packages/node-experimental/src/sdk/initOtel.ts
@@ -1,22 +1,33 @@
import { diag, DiagLogLevel } from '@opentelemetry/api';
import { Resource } from '@opentelemetry/resources';
-import { AlwaysOnSampler, BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
+import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
-import { getCurrentHub, SDK_VERSION } from '@sentry/core';
-import { SentryPropagator, SentrySpanProcessor } from '@sentry/opentelemetry-node';
+import { SDK_VERSION } from '@sentry/core';
import { logger } from '@sentry/utils';
+import { SentryPropagator } from '../opentelemetry/propagator';
+import { SentrySampler } from '../opentelemetry/sampler';
+import { SentrySpanProcessor } from '../opentelemetry/spanProcessor';
import type { NodeExperimentalClient } from '../types';
+import { setupEventContextTrace } from '../utils/setupEventContextTrace';
import { SentryContextManager } from './../opentelemetry/contextManager';
+import { getCurrentHub } from './hub';
/**
* Initialize OpenTelemetry for Node.
- * We use the @sentry/opentelemetry-node package to communicate with OpenTelemetry.
*/
-export function initOtel(): () => void {
+export function initOtel(): void {
const client = getCurrentHub().getClient();
- if (client?.getOptions().debug) {
+ if (!client) {
+ __DEBUG_BUILD__ &&
+ logger.warn(
+ 'No client available, skipping OpenTelemetry setup. This probably means that `Sentry.init()` was not called before `initOtel()`.',
+ );
+ return;
+ }
+
+ if (client.getOptions().debug) {
const otelLogger = new Proxy(logger as typeof logger & { verbose: (typeof logger)['debug'] }, {
get(target, prop, receiver) {
const actualProp = prop === 'verbose' ? 'debug' : prop;
@@ -27,14 +38,23 @@ export function initOtel(): () => void {
diag.setLogger(otelLogger, DiagLogLevel.DEBUG);
}
+ setupEventContextTrace(client);
+
+ const provider = setupOtel(client);
+ client.traceProvider = provider;
+}
+
+/** Just exported for tests. */
+export function setupOtel(client: NodeExperimentalClient): BasicTracerProvider {
// Create and configure NodeTracerProvider
const provider = new BasicTracerProvider({
- sampler: new AlwaysOnSampler(),
+ sampler: new SentrySampler(client),
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'node-experimental',
[SemanticResourceAttributes.SERVICE_NAMESPACE]: 'sentry',
[SemanticResourceAttributes.SERVICE_VERSION]: SDK_VERSION,
}),
+ forceFlushTimeoutMillis: 500,
});
provider.addSpanProcessor(new SentrySpanProcessor());
@@ -47,9 +67,5 @@ export function initOtel(): () => void {
contextManager,
});
- // Cleanup function
- return () => {
- void provider.forceFlush();
- void provider.shutdown();
- };
+ return provider;
}
diff --git a/packages/node-experimental/src/sdk/scope.ts b/packages/node-experimental/src/sdk/scope.ts
index 12fcc6862904..39f931936ccf 100644
--- a/packages/node-experimental/src/sdk/scope.ts
+++ b/packages/node-experimental/src/sdk/scope.ts
@@ -1,16 +1,34 @@
+import type { Span } from '@opentelemetry/api';
+import type { TimedEvent } from '@opentelemetry/sdk-trace-base';
import { Scope } from '@sentry/core';
-import type { Breadcrumb } from '@sentry/types';
+import type { Breadcrumb, SeverityLevel, Span as SentrySpan } from '@sentry/types';
+import { dateTimestampInSeconds, dropUndefinedKeys, logger, normalize } from '@sentry/utils';
-import type { TransactionWithBreadcrumbs } from '../types';
-import { getActiveSpan } from './trace';
+import {
+ OTEL_ATTR_BREADCRUMB_CATEGORY,
+ OTEL_ATTR_BREADCRUMB_DATA,
+ OTEL_ATTR_BREADCRUMB_EVENT_ID,
+ OTEL_ATTR_BREADCRUMB_LEVEL,
+ OTEL_ATTR_BREADCRUMB_TYPE,
+} from '../constants';
+import { getSpanParent } from '../opentelemetry/spanData';
+import { convertOtelTimeToSeconds } from '../utils/convertOtelTimeToSeconds';
+import { getActiveSpan, getRootSpan } from '../utils/getActiveSpan';
+import { spanHasEvents } from '../utils/spanTypes';
/** A fork of the classic scope with some otel specific stuff. */
-export class OtelScope extends Scope {
+export class NodeExperimentalScope extends Scope {
+ /**
+ * This can be set to ensure the scope uses _this_ span as the active one,
+ * instead of using getActiveSpan().
+ */
+ public activeSpan: Span | undefined;
+
/**
* @inheritDoc
*/
public static clone(scope?: Scope): Scope {
- const newScope = new OtelScope();
+ const newScope = new NodeExperimentalScope();
if (scope) {
newScope._breadcrumbs = [...scope['_breadcrumbs']];
newScope._tags = { ...scope['_tags'] };
@@ -31,14 +49,42 @@ export class OtelScope extends Scope {
return newScope;
}
+ /**
+ * In node-experimental, scope.getSpan() always returns undefined.
+ * Instead, use the global `getActiveSpan()`.
+ */
+ public getSpan(): undefined {
+ __DEBUG_BUILD__ &&
+ logger.warn('Calling getSpan() is a noop in @sentry/node-experimental. Use `getActiveSpan()` instead.');
+
+ return undefined;
+ }
+
+ /**
+ * In node-experimental, scope.setSpan() is a noop.
+ * Instead, use the global `startSpan()` to define the active span.
+ */
+ public setSpan(_span: SentrySpan): this {
+ __DEBUG_BUILD__ &&
+ logger.warn('Calling setSpan() is a noop in @sentry/node-experimental. Use `startSpan()` instead.');
+
+ return this;
+ }
+
/**
* @inheritDoc
*/
public addBreadcrumb(breadcrumb: Breadcrumb, maxBreadcrumbs?: number): this {
- const transaction = getActiveTransaction();
+ const activeSpan = this.activeSpan || getActiveSpan();
+ const rootSpan = activeSpan ? getRootSpan(activeSpan) : undefined;
- if (transaction && transaction.addBreadcrumb) {
- transaction.addBreadcrumb(breadcrumb, maxBreadcrumbs);
+ if (rootSpan) {
+ const mergedBreadcrumb = {
+ timestamp: dateTimestampInSeconds(),
+ ...breadcrumb,
+ };
+
+ rootSpan.addEvent(...breadcrumbToOtelEvent(mergedBreadcrumb));
return this;
}
@@ -49,18 +95,94 @@ export class OtelScope extends Scope {
* @inheritDoc
*/
protected _getBreadcrumbs(): Breadcrumb[] {
- const transaction = getActiveTransaction();
- const transactionBreadcrumbs = transaction && transaction.getBreadcrumbs ? transaction.getBreadcrumbs() : [];
+ const span = this.activeSpan || getActiveSpan();
+
+ const spanBreadcrumbs = span ? getBreadcrumbsForSpan(span) : [];
- return this._breadcrumbs.concat(transactionBreadcrumbs);
+ return spanBreadcrumbs.length > 0 ? this._breadcrumbs.concat(spanBreadcrumbs) : this._breadcrumbs;
}
}
/**
- * This gets the currently active transaction,
- * and ensures to wrap it so that we can store breadcrumbs on it.
+ * Get all breadcrumbs for the given span as well as it's parents.
*/
-function getActiveTransaction(): TransactionWithBreadcrumbs | undefined {
- const activeSpan = getActiveSpan();
- return activeSpan && (activeSpan.transaction as TransactionWithBreadcrumbs | undefined);
+function getBreadcrumbsForSpan(span: Span): Breadcrumb[] {
+ const events = span ? getOtelEvents(span) : [];
+
+ return events.map(otelEventToBreadcrumb);
+}
+
+function breadcrumbToOtelEvent(breadcrumb: Breadcrumb): Parameters {
+ const name = breadcrumb.message || '';
+
+ const dataAttrs = serializeBreadcrumbData(breadcrumb.data);
+
+ return [
+ name,
+ dropUndefinedKeys({
+ [OTEL_ATTR_BREADCRUMB_TYPE]: breadcrumb.type,
+ [OTEL_ATTR_BREADCRUMB_LEVEL]: breadcrumb.level,
+ [OTEL_ATTR_BREADCRUMB_EVENT_ID]: breadcrumb.event_id,
+ [OTEL_ATTR_BREADCRUMB_CATEGORY]: breadcrumb.category,
+ ...dataAttrs,
+ }),
+ breadcrumb.timestamp ? new Date(breadcrumb.timestamp * 1000) : undefined,
+ ];
+}
+
+function serializeBreadcrumbData(data: Breadcrumb['data']): undefined | Record {
+ if (!data || Object.keys(data).length === 0) {
+ return undefined;
+ }
+
+ try {
+ const normalizedData = normalize(data);
+ return {
+ [OTEL_ATTR_BREADCRUMB_DATA]: JSON.stringify(normalizedData),
+ };
+ } catch (e) {
+ return undefined;
+ }
+}
+
+function otelEventToBreadcrumb(event: TimedEvent): Breadcrumb {
+ const attributes = event.attributes || {};
+
+ const type = attributes[OTEL_ATTR_BREADCRUMB_TYPE] as string | undefined;
+ const level = attributes[OTEL_ATTR_BREADCRUMB_LEVEL] as SeverityLevel | undefined;
+ const eventId = attributes[OTEL_ATTR_BREADCRUMB_EVENT_ID] as string | undefined;
+ const category = attributes[OTEL_ATTR_BREADCRUMB_CATEGORY] as string | undefined;
+ const dataStr = attributes[OTEL_ATTR_BREADCRUMB_DATA] as string | undefined;
+
+ const breadcrumb: Breadcrumb = dropUndefinedKeys({
+ timestamp: convertOtelTimeToSeconds(event.time),
+ message: event.name,
+ type,
+ level,
+ event_id: eventId,
+ category,
+ });
+
+ if (typeof dataStr === 'string') {
+ try {
+ const data = JSON.parse(dataStr);
+ breadcrumb.data = data;
+ } catch (e) {} // eslint-disable-line no-empty
+ }
+
+ return breadcrumb;
+}
+
+function getOtelEvents(span: Span, events: TimedEvent[] = []): TimedEvent[] {
+ if (spanHasEvents(span)) {
+ events.push(...span.events);
+ }
+
+ // Go up parent chain and collect events
+ const parent = getSpanParent(span);
+ if (parent) {
+ return getOtelEvents(parent, events);
+ }
+
+ return events;
}
diff --git a/packages/node-experimental/src/sdk/trace.ts b/packages/node-experimental/src/sdk/trace.ts
index 1faf780ec5c7..72047f4478a3 100644
--- a/packages/node-experimental/src/sdk/trace.ts
+++ b/packages/node-experimental/src/sdk/trace.ts
@@ -1,11 +1,14 @@
-import type { Span as OtelSpan, Tracer } from '@opentelemetry/api';
-import { trace } from '@opentelemetry/api';
-import { getCurrentHub, hasTracingEnabled, Transaction } from '@sentry/core';
-import { _INTERNAL_getSentrySpan } from '@sentry/opentelemetry-node';
-import type { Span, TransactionContext } from '@sentry/types';
+import type { Tracer } from '@opentelemetry/api';
+import { SpanStatusCode } from '@opentelemetry/api';
+import type { Span } from '@opentelemetry/sdk-trace-base';
+import { hasTracingEnabled } from '@sentry/core';
import { isThenable } from '@sentry/utils';
-import type { NodeExperimentalClient } from '../types';
+import { OTEL_ATTR_OP, OTEL_ATTR_ORIGIN, OTEL_ATTR_SOURCE } from '../constants';
+import { setSpanMetadata } from '../opentelemetry/spanData';
+import type { NodeExperimentalClient, NodeExperimentalSpanContext } from '../types';
+import { spanIsSdkTraceBaseSpan } from '../utils/spanTypes';
+import { getCurrentHub } from './hub';
/**
* Wraps a function with a transaction/span and finishes the span after the function is done.
@@ -18,32 +21,33 @@ import type { NodeExperimentalClient } from '../types';
* or you didn't set `tracesSampleRate`, this function will not generate spans
* and the `span` returned from the callback will be undefined.
*/
-export function startSpan(context: TransactionContext, callback: (span: Span | undefined) => T): T {
+export function startSpan(spanContext: NodeExperimentalSpanContext, callback: (span: Span | undefined) => T): T {
const tracer = getTracer();
if (!tracer) {
return callback(undefined);
}
- const name = context.name || context.description || context.op || '';
+ const { name } = spanContext;
- return tracer.startActiveSpan(name, (span: OtelSpan): T => {
- const otelSpanId = span.spanContext().spanId;
-
- const sentrySpan = _INTERNAL_getSentrySpan(otelSpanId);
-
- if (sentrySpan && isTransaction(sentrySpan) && context.metadata) {
- sentrySpan.setMetadata(context.metadata);
+ return tracer.startActiveSpan(name, (span): T => {
+ function finishSpan(): void {
+ span.end();
}
- function finishSpan(): void {
+ // This is just a sanity check - in reality, this should not happen as we control the tracer,
+ // but to ensure type saftey we rather bail out here than to pass an invalid type out
+ if (!spanIsSdkTraceBaseSpan(span)) {
span.end();
+ return callback(undefined);
}
+ _applySentryAttributesToSpan(span, spanContext);
+
let maybePromiseResult: T;
try {
- maybePromiseResult = callback(sentrySpan);
+ maybePromiseResult = callback(span);
} catch (e) {
- sentrySpan && sentrySpan.setStatus('internal_error');
+ span.setStatus({ code: SpanStatusCode.ERROR });
finishSpan();
throw e;
}
@@ -54,7 +58,7 @@ export function startSpan(context: TransactionContext, callback: (span: Span
finishSpan();
},
() => {
- sentrySpan && sentrySpan.setStatus('internal_error');
+ span.setStatus({ code: SpanStatusCode.ERROR });
finishSpan();
},
);
@@ -81,50 +85,26 @@ export const startActiveSpan = startSpan;
* or you didn't set `tracesSampleRate` or `tracesSampler`, this function will not generate spans
* and the `span` returned from the callback will be undefined.
*/
-export function startInactiveSpan(context: TransactionContext): Span | undefined {
+export function startInactiveSpan(spanContext: NodeExperimentalSpanContext): Span | undefined {
const tracer = getTracer();
if (!tracer) {
return undefined;
}
- const name = context.name || context.description || context.op || '';
- const otelSpan = tracer.startSpan(name);
+ const { name } = spanContext;
- const otelSpanId = otelSpan.spanContext().spanId;
+ const span = tracer.startSpan(name);
- const sentrySpan = _INTERNAL_getSentrySpan(otelSpanId);
-
- if (!sentrySpan) {
+ // This is just a sanity check - in reality, this should not happen as we control the tracer,
+ // but to ensure type saftey we rather bail out here than to pass an invalid type out
+ if (!spanIsSdkTraceBaseSpan(span)) {
+ span.end();
return undefined;
}
- if (isTransaction(sentrySpan) && context.metadata) {
- sentrySpan.setMetadata(context.metadata);
- }
-
- // Monkey-patch `finish()` to finish the OTEL span instead
- // This will also in turn finish the Sentry Span, so no need to call this ourselves
- const wrappedSentrySpan = new Proxy(sentrySpan, {
- get(target, prop, receiver) {
- if (prop === 'finish') {
- return () => {
- otelSpan.end();
- };
- }
- return Reflect.get(target, prop, receiver);
- },
- });
-
- return wrappedSentrySpan;
-}
+ _applySentryAttributesToSpan(span, spanContext);
-/**
- * Returns the currently active span.
- */
-export function getActiveSpan(): Span | undefined {
- const otelSpan = trace.getActiveSpan();
- const spanId = otelSpan && otelSpan.spanContext().spanId;
- return spanId ? _INTERNAL_getSentrySpan(spanId) : undefined;
+ return span;
}
function getTracer(): Tracer | undefined {
@@ -136,6 +116,22 @@ function getTracer(): Tracer | undefined {
return client && client.tracer;
}
-function isTransaction(span: Span): span is Transaction {
- return span instanceof Transaction;
+function _applySentryAttributesToSpan(span: Span, spanContext: NodeExperimentalSpanContext): void {
+ const { origin, op, source, metadata } = spanContext;
+
+ if (origin) {
+ span.setAttribute(OTEL_ATTR_ORIGIN, origin);
+ }
+
+ if (op) {
+ span.setAttribute(OTEL_ATTR_OP, op);
+ }
+
+ if (source) {
+ span.setAttribute(OTEL_ATTR_SOURCE, source);
+ }
+
+ if (metadata) {
+ setSpanMetadata(span, metadata);
+ }
}
diff --git a/packages/node-experimental/src/sdk/transaction.ts b/packages/node-experimental/src/sdk/transaction.ts
new file mode 100644
index 000000000000..382d9a926f5d
--- /dev/null
+++ b/packages/node-experimental/src/sdk/transaction.ts
@@ -0,0 +1,48 @@
+import type { Hub } from '@sentry/core';
+import { Transaction } from '@sentry/core';
+import type { ClientOptions, Hub as HubInterface, Scope, TransactionContext } from '@sentry/types';
+import { uuid4 } from '@sentry/utils';
+
+/**
+ * This is a fork of core's tracing/hubextensions.ts _startTransaction,
+ * with some OTEL specifics.
+ */
+export function startTransaction(hub: HubInterface, transactionContext: TransactionContext): Transaction {
+ const client = hub.getClient();
+ const options: Partial = (client && client.getOptions()) || {};
+
+ const transaction = new NodeExperimentalTransaction(transactionContext, hub as Hub);
+ // Since we do not do sampling here, we assume that this is _always_ sampled
+ // Any sampling decision happens in OpenTelemetry's sampler
+ transaction.initSpanRecorder(options._experiments && (options._experiments.maxSpans as number));
+
+ if (client && client.emit) {
+ client.emit('startTransaction', transaction);
+ }
+ return transaction;
+}
+
+/**
+ * This is a fork of the base Transaction with OTEL specific stuff added.
+ */
+export class NodeExperimentalTransaction extends Transaction {
+ /**
+ * Finish the transaction, but apply the given scope instead of the current one.
+ */
+ public finishWithScope(endTimestamp?: number, scope?: Scope): string | undefined {
+ const event = this._finishTransaction(endTimestamp);
+
+ if (!event) {
+ return undefined;
+ }
+
+ const client = this._hub.getClient();
+
+ if (!client) {
+ return undefined;
+ }
+
+ const eventId = uuid4();
+ return client.captureEvent(event, { event_id: eventId }, scope);
+ }
+}
diff --git a/packages/node-experimental/src/types.ts b/packages/node-experimental/src/types.ts
index 0fd9a6922a78..8878a5fd2a8c 100644
--- a/packages/node-experimental/src/types.ts
+++ b/packages/node-experimental/src/types.ts
@@ -1,29 +1,34 @@
-import type { Tracer } from '@opentelemetry/api';
-import type { Span as OtelSpan } from '@opentelemetry/sdk-trace-base';
+import type { Span as WriteableSpan, Tracer } from '@opentelemetry/api';
+import type { BasicTracerProvider, ReadableSpan, Span } from '@opentelemetry/sdk-trace-base';
import type { NodeClient, NodeOptions } from '@sentry/node';
-import type { Breadcrumb, Transaction } from '@sentry/types';
+import type { SpanOrigin, TransactionMetadata, TransactionSource } from '@sentry/types';
export type NodeExperimentalOptions = NodeOptions;
export type NodeExperimentalClientOptions = ConstructorParameters[0];
export interface NodeExperimentalClient extends NodeClient {
tracer: Tracer;
+ traceProvider: BasicTracerProvider | undefined;
getOptions(): NodeExperimentalClientOptions;
}
+export interface NodeExperimentalSpanContext {
+ name: string;
+ op?: string;
+ metadata?: Partial;
+ origin?: SpanOrigin;
+ source?: TransactionSource;
+}
+
/**
- * This is a fork of the base Transaction with OTEL specific stuff added.
- * Note that we do not solve this via an actual subclass, but by wrapping this in a proxy when we need it -
- * as we can't easily control all the places a transaction may be created.
+ * The base `Span` type is basically a `WriteableSpan`.
+ * There are places where we basically want to allow passing _any_ span,
+ * so in these cases we type this as `AbstractSpan` which could be either a regular `Span` or a `ReadableSpan`.
+ * You'll have to make sur to check revelant fields before accessing them.
+ *
+ * Note that technically, the `Span` exported from `@opentelemwetry/sdk-trace-base` matches this,
+ * but we cannot be 100% sure that we are actually getting such a span, so this type is more defensive.
*/
-export interface TransactionWithBreadcrumbs extends Transaction {
- _breadcrumbs: Breadcrumb[];
-
- /** Get all breadcrumbs added to this transaction. */
- getBreadcrumbs(): Breadcrumb[];
-
- /** Add a breadcrumb to this transaction. */
- addBreadcrumb(breadcrumb: Breadcrumb, maxBreadcrumbs?: number): void;
-}
+export type AbstractSpan = WriteableSpan | ReadableSpan;
-export type { OtelSpan };
+export type { Span };
diff --git a/packages/node-experimental/src/utils/addOriginToSpan.ts b/packages/node-experimental/src/utils/addOriginToSpan.ts
index 4320d31d7fce..007f55bb1e05 100644
--- a/packages/node-experimental/src/utils/addOriginToSpan.ts
+++ b/packages/node-experimental/src/utils/addOriginToSpan.ts
@@ -1,14 +1,9 @@
-// We are using the broader OtelSpan type from api here, as this is also what integrations etc. use
-import type { Span as OtelSpan } from '@opentelemetry/api';
-import { _INTERNAL_getSentrySpan } from '@sentry/opentelemetry-node';
+import type { Span } from '@opentelemetry/api';
import type { SpanOrigin } from '@sentry/types';
-/** Adds an origin to an OTEL Span. */
-export function addOriginToOtelSpan(otelSpan: OtelSpan, origin: SpanOrigin): void {
- const sentrySpan = _INTERNAL_getSentrySpan(otelSpan.spanContext().spanId);
- if (!sentrySpan) {
- return;
- }
+import { OTEL_ATTR_ORIGIN } from '../constants';
- sentrySpan.origin = origin;
+/** Adds an origin to an OTEL Span. */
+export function addOriginToSpan(span: Span, origin: SpanOrigin): void {
+ span.setAttribute(OTEL_ATTR_ORIGIN, origin);
}
diff --git a/packages/node-experimental/src/utils/convertOtelTimeToSeconds.ts b/packages/node-experimental/src/utils/convertOtelTimeToSeconds.ts
new file mode 100644
index 000000000000..64087aeffc4d
--- /dev/null
+++ b/packages/node-experimental/src/utils/convertOtelTimeToSeconds.ts
@@ -0,0 +1,4 @@
+/** Convert an OTEL time to seconds */
+export function convertOtelTimeToSeconds([seconds, nano]: [number, number]): number {
+ return seconds + nano / 1_000_000_000;
+}
diff --git a/packages/node-experimental/src/utils/getActiveSpan.ts b/packages/node-experimental/src/utils/getActiveSpan.ts
new file mode 100644
index 000000000000..240842770a68
--- /dev/null
+++ b/packages/node-experimental/src/utils/getActiveSpan.ts
@@ -0,0 +1,25 @@
+import type { Span } from '@opentelemetry/api';
+import { trace } from '@opentelemetry/api';
+
+import { getSpanParent } from '../opentelemetry/spanData';
+
+/**
+ * Returns the currently active span.
+ */
+export function getActiveSpan(): Span | undefined {
+ return trace.getActiveSpan();
+}
+
+/**
+ * Get the root span for the given span.
+ * The given span may be the root span itself.
+ */
+export function getRootSpan(span: Span): Span {
+ let parent: Span = span;
+
+ while (getSpanParent(parent)) {
+ parent = getSpanParent(parent) as Span;
+ }
+
+ return parent;
+}
diff --git a/packages/node-experimental/src/utils/getRequestSpanData.ts b/packages/node-experimental/src/utils/getRequestSpanData.ts
index ca89f5a2b976..0154f8e4cd3e 100644
--- a/packages/node-experimental/src/utils/getRequestSpanData.ts
+++ b/packages/node-experimental/src/utils/getRequestSpanData.ts
@@ -1,18 +1,30 @@
+import type { Span } from '@opentelemetry/api';
+import type { ReadableSpan } from '@opentelemetry/sdk-trace-base';
import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
import type { SanitizedRequestData } from '@sentry/types';
import { getSanitizedUrlString, parseUrl } from '@sentry/utils';
-import type { OtelSpan } from '../types';
+import { spanHasAttributes } from './spanTypes';
/**
* Get sanitizied request data from an OTEL span.
*/
-export function getRequestSpanData(span: OtelSpan): SanitizedRequestData {
- const data: SanitizedRequestData = {
- url: span.attributes[SemanticAttributes.HTTP_URL] as string,
- 'http.method': (span.attributes[SemanticAttributes.HTTP_METHOD] as string) || 'GET',
+export function getRequestSpanData(span: Span | ReadableSpan): Partial {
+ // The base `Span` type has no `attributes`, so we need to guard here against that
+ if (!spanHasAttributes(span)) {
+ return {};
+ }
+
+ const data: Partial = {
+ url: span.attributes[SemanticAttributes.HTTP_URL] as string | undefined,
+ 'http.method': span.attributes[SemanticAttributes.HTTP_METHOD] as string | undefined,
};
+ // Default to GET if URL is set but method is not
+ if (!data['http.method'] && data.url) {
+ data['http.method'] = 'GET';
+ }
+
try {
const urlStr = span.attributes[SemanticAttributes.HTTP_URL];
if (typeof urlStr === 'string') {
diff --git a/packages/node-experimental/src/utils/getSpanKind.ts b/packages/node-experimental/src/utils/getSpanKind.ts
new file mode 100644
index 000000000000..7769a1cd3290
--- /dev/null
+++ b/packages/node-experimental/src/utils/getSpanKind.ts
@@ -0,0 +1,18 @@
+import type { Span } from '@opentelemetry/api';
+import { SpanKind } from '@opentelemetry/api';
+
+import { spanHasKind } from './spanTypes';
+
+/**
+ * Get the span kind from a span.
+ * For whatever reason, this is not public API on the generic "Span" type,
+ * so we need to check if we actually have a `SDKTraceBaseSpan` where we can fetch this from.
+ * Otherwise, we fall back to `SpanKind.INTERNAL`.
+ */
+export function getSpanKind(span: Span): SpanKind {
+ if (spanHasKind(span)) {
+ return span.kind;
+ }
+
+ return SpanKind.INTERNAL;
+}
diff --git a/packages/node-experimental/src/utils/groupSpansWithParents.ts b/packages/node-experimental/src/utils/groupSpansWithParents.ts
new file mode 100644
index 000000000000..2af278d0bce2
--- /dev/null
+++ b/packages/node-experimental/src/utils/groupSpansWithParents.ts
@@ -0,0 +1,80 @@
+import type { ReadableSpan } from '@opentelemetry/sdk-trace-base';
+
+import { getSpanParent } from '../opentelemetry/spanData';
+
+export interface SpanNode {
+ id: string;
+ span?: ReadableSpan;
+ parentNode?: SpanNode | undefined;
+ children: SpanNode[];
+}
+
+type SpanMap = Map;
+
+/**
+ * This function runs through a list of OTEL Spans, and wraps them in an `SpanNode`
+ * where each node holds a reference to their parent node.
+ */
+export function groupSpansWithParents(spans: ReadableSpan[]): SpanNode[] {
+ const nodeMap: SpanMap = new Map();
+
+ for (const span of spans) {
+ createOrUpdateSpanNodeAndRefs(nodeMap, span);
+ }
+
+ return Array.from(nodeMap, function ([_id, spanNode]) {
+ return spanNode;
+ });
+}
+
+function createOrUpdateSpanNodeAndRefs(nodeMap: SpanMap, span: ReadableSpan): void {
+ const parentSpan = getSpanParent(span);
+ const parentIsRemote = parentSpan ? !!parentSpan.spanContext().isRemote : false;
+
+ const id = span.spanContext().spanId;
+
+ // If the parentId is the trace parent ID, we pretend it's undefined
+ // As this means the parent exists somewhere else
+ const parentId = !parentIsRemote ? span.parentSpanId : undefined;
+
+ if (!parentId) {
+ createOrUpdateNode(nodeMap, { id, span, children: [] });
+ return;
+ }
+
+ // Else make sure to create parent node as well
+ // Note that the parent may not know it's parent _yet_, this may be updated in a later pass
+ const parentNode = createOrGetParentNode(nodeMap, parentId);
+ const node = createOrUpdateNode(nodeMap, { id, span, parentNode, children: [] });
+ parentNode.children.push(node);
+}
+
+function createOrGetParentNode(nodeMap: SpanMap, id: string): SpanNode {
+ const existing = nodeMap.get(id);
+
+ if (existing) {
+ return existing;
+ }
+
+ return createOrUpdateNode(nodeMap, { id, children: [] });
+}
+
+function createOrUpdateNode(nodeMap: SpanMap, spanNode: SpanNode): SpanNode {
+ const existing = nodeMap.get(spanNode.id);
+
+ // If span is already set, nothing to do here
+ if (existing && existing.span) {
+ return existing;
+ }
+
+ // If it exists but span is not set yet, we update it
+ if (existing && !existing.span) {
+ existing.span = spanNode.span;
+ existing.parentNode = spanNode.parentNode;
+ return existing;
+ }
+
+ // Else, we create a new one...
+ nodeMap.set(spanNode.id, spanNode);
+ return spanNode;
+}
diff --git a/packages/node-experimental/src/utils/setupEventContextTrace.ts b/packages/node-experimental/src/utils/setupEventContextTrace.ts
new file mode 100644
index 000000000000..0e8dc7c23d7b
--- /dev/null
+++ b/packages/node-experimental/src/utils/setupEventContextTrace.ts
@@ -0,0 +1,32 @@
+import type { Client } from '@sentry/types';
+
+import { getActiveSpan } from './getActiveSpan';
+import { spanHasParentId } from './spanTypes';
+
+/** Ensure the `trace` context is set on all events. */
+export function setupEventContextTrace(client: Client): void {
+ if (!client.addEventProcessor) {
+ return;
+ }
+
+ client.addEventProcessor(event => {
+ const span = getActiveSpan();
+ if (!span) {
+ return event;
+ }
+
+ const spanContext = span.spanContext();
+
+ // If event has already set `trace` context, use that one.
+ event.contexts = {
+ trace: {
+ trace_id: spanContext.traceId,
+ span_id: spanContext.spanId,
+ parent_span_id: spanHasParentId(span) ? span.parentSpanId : undefined,
+ },
+ ...event.contexts,
+ };
+
+ return event;
+ });
+}
diff --git a/packages/node-experimental/src/utils/spanTypes.ts b/packages/node-experimental/src/utils/spanTypes.ts
new file mode 100644
index 000000000000..3883a97f8004
--- /dev/null
+++ b/packages/node-experimental/src/utils/spanTypes.ts
@@ -0,0 +1,58 @@
+import type { SpanKind } from '@opentelemetry/api';
+import type { ReadableSpan, TimedEvent } from '@opentelemetry/sdk-trace-base';
+import { Span as SdkTraceBaseSpan } from '@opentelemetry/sdk-trace-base';
+
+import type { AbstractSpan } from '../types';
+
+/**
+ * Check if a given span has attributes.
+ * This is necessary because the base `Span` type does not have attributes,
+ * so in places where we are passed a generic span, we need to check if we want to access them.
+ */
+export function spanHasAttributes(
+ span: SpanType,
+): span is SpanType & { attributes: ReadableSpan['attributes'] } {
+ const castSpan = span as ReadableSpan;
+ return !!castSpan.attributes && typeof castSpan.attributes === 'object';
+}
+
+/**
+ * Check if a given span has a kind.
+ * This is necessary because the base `Span` type does not have a kind,
+ * so in places where we are passed a generic span, we need to check if we want to access it.
+ */
+export function spanHasKind(span: SpanType): span is SpanType & { kind: SpanKind } {
+ const castSpan = span as ReadableSpan;
+ return !!castSpan.kind;
+}
+
+/**
+ * Check if a given span has a kind.
+ * This is necessary because the base `Span` type does not have a kind,
+ * so in places where we are passed a generic span, we need to check if we want to access it.
+ */
+export function spanHasParentId(
+ span: SpanType,
+): span is SpanType & { parentSpanId: string } {
+ const castSpan = span as ReadableSpan;
+ return !!castSpan.parentSpanId;
+}
+
+/**
+ * Check if a given span has events.
+ * This is necessary because the base `Span` type does not have events,
+ * so in places where we are passed a generic span, we need to check if we want to access it.
+ */
+export function spanHasEvents(
+ span: SpanType,
+): span is SpanType & { events: TimedEvent[] } {
+ const castSpan = span as ReadableSpan;
+ return Array.isArray(castSpan.events);
+}
+
+/**
+ * If the span is a SDK trace base span, which has some additional fields.
+ */
+export function spanIsSdkTraceBaseSpan(span: AbstractSpan): span is SdkTraceBaseSpan {
+ return span instanceof SdkTraceBaseSpan;
+}
diff --git a/packages/node-experimental/test/helpers/createSpan.ts b/packages/node-experimental/test/helpers/createSpan.ts
new file mode 100644
index 000000000000..38c4ed96f3a8
--- /dev/null
+++ b/packages/node-experimental/test/helpers/createSpan.ts
@@ -0,0 +1,30 @@
+import type { Context, SpanContext } from '@opentelemetry/api';
+import { SpanKind } from '@opentelemetry/api';
+import type { Tracer } from '@opentelemetry/sdk-trace-base';
+import { Span } from '@opentelemetry/sdk-trace-base';
+import { uuid4 } from '@sentry/utils';
+
+export function createSpan(
+ name?: string,
+ { spanId, parentSpanId }: { spanId?: string; parentSpanId?: string } = {},
+): Span {
+ const spanProcessor = {
+ onStart: () => {},
+ onEnd: () => {},
+ };
+ const tracer = {
+ resource: 'test-resource',
+ instrumentationLibrary: 'test-instrumentation-library',
+ getSpanLimits: () => ({}),
+ getActiveSpanProcessor: () => spanProcessor,
+ } as unknown as Tracer;
+
+ const spanContext: SpanContext = {
+ spanId: spanId || uuid4(),
+ traceId: uuid4(),
+ traceFlags: 0,
+ };
+
+ // eslint-disable-next-line deprecation/deprecation
+ return new Span(tracer, {} as Context, name || 'test', spanContext, SpanKind.INTERNAL, parentSpanId);
+}
diff --git a/packages/node-experimental/test/helpers/getDefaultNodePreviewClientOptions.ts b/packages/node-experimental/test/helpers/getDefaultNodePreviewClientOptions.ts
index c5f2b8b5cf82..00778e78582a 100644
--- a/packages/node-experimental/test/helpers/getDefaultNodePreviewClientOptions.ts
+++ b/packages/node-experimental/test/helpers/getDefaultNodePreviewClientOptions.ts
@@ -7,6 +7,7 @@ export function getDefaultNodeExperimentalClientOptions(
options: Partial = {},
): NodeExperimentalClientOptions {
return {
+ tracesSampleRate: 1,
integrations: [],
transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => resolvedSyncPromise({})),
stackParser: () => [],
diff --git a/packages/node-experimental/test/helpers/mockSdkInit.ts b/packages/node-experimental/test/helpers/mockSdkInit.ts
index f7bfb68f6bf6..3443f0608806 100644
--- a/packages/node-experimental/test/helpers/mockSdkInit.ts
+++ b/packages/node-experimental/test/helpers/mockSdkInit.ts
@@ -1,13 +1,49 @@
+import { context, propagation, ProxyTracerProvider, trace } from '@opentelemetry/api';
+import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
+import { GLOBAL_OBJ } from '@sentry/utils';
+
import { init } from '../../src/sdk/init';
import type { NodeExperimentalClientOptions } from '../../src/types';
-// eslint-disable-next-line no-var
-declare var global: any;
-
const PUBLIC_DSN = 'https://username@domain/123';
export function mockSdkInit(options?: Partial) {
- global.__SENTRY__ = {};
+ GLOBAL_OBJ.__SENTRY__ = {
+ extensions: {},
+ hub: undefined,
+ globalEventProcessors: [],
+ logger: undefined,
+ };
init({ dsn: PUBLIC_DSN, defaultIntegrations: false, ...options });
}
+
+export function cleanupOtel(_provider?: BasicTracerProvider): void {
+ const provider = getProvider(_provider);
+
+ if (!provider) {
+ return;
+ }
+
+ void provider.forceFlush();
+ void provider.shutdown();
+
+ // Disable all globally registered APIs
+ trace.disable();
+ context.disable();
+ propagation.disable();
+}
+
+export function getProvider(_provider?: BasicTracerProvider): BasicTracerProvider | undefined {
+ let provider = _provider || trace.getTracerProvider();
+
+ if (provider instanceof ProxyTracerProvider) {
+ provider = provider.getDelegate();
+ }
+
+ if (!(provider instanceof BasicTracerProvider)) {
+ return undefined;
+ }
+
+ return provider;
+}
diff --git a/packages/node-experimental/test/integration/breadcrumbs.test.ts b/packages/node-experimental/test/integration/breadcrumbs.test.ts
new file mode 100644
index 000000000000..fbd46a6bd466
--- /dev/null
+++ b/packages/node-experimental/test/integration/breadcrumbs.test.ts
@@ -0,0 +1,362 @@
+import { withScope } from '../../src/';
+import { NodeExperimentalClient } from '../../src/sdk/client';
+import { getCurrentHub, NodeExperimentalHub } from '../../src/sdk/hub';
+import { startSpan } from '../../src/sdk/trace';
+import { cleanupOtel, mockSdkInit } from '../helpers/mockSdkInit';
+
+describe('Integration | breadcrumbs', () => {
+ const beforeSendTransaction = jest.fn(() => null);
+
+ afterEach(() => {
+ cleanupOtel();
+ });
+
+ describe('without tracing', () => {
+ it('correctly adds & retrieves breadcrumbs', async () => {
+ const beforeSend = jest.fn(() => null);
+ const beforeBreadcrumb = jest.fn(breadcrumb => breadcrumb);
+
+ mockSdkInit({ beforeSend, beforeBreadcrumb });
+
+ const hub = getCurrentHub();
+ const client = hub.getClient() as NodeExperimentalClient;
+
+ expect(hub).toBeInstanceOf(NodeExperimentalHub);
+ expect(client).toBeInstanceOf(NodeExperimentalClient);
+
+ hub.addBreadcrumb({ timestamp: 123456, message: 'test1' });
+ hub.addBreadcrumb({ timestamp: 123457, message: 'test2', data: { nested: 'yes' } });
+ hub.addBreadcrumb({ timestamp: 123455, message: 'test3' });
+
+ const error = new Error('test');
+ hub.captureException(error);
+
+ await client.flush();
+
+ expect(beforeSend).toHaveBeenCalledTimes(1);
+ expect(beforeBreadcrumb).toHaveBeenCalledTimes(3);
+
+ expect(beforeSend).toHaveBeenCalledWith(
+ expect.objectContaining({
+ breadcrumbs: [
+ { message: 'test1', timestamp: 123456 },
+ { data: { nested: 'yes' }, message: 'test2', timestamp: 123457 },
+ { message: 'test3', timestamp: 123455 },
+ ],
+ }),
+ {
+ event_id: expect.any(String),
+ originalException: error,
+ syntheticException: expect.any(Error),
+ },
+ );
+ });
+
+ it('handles parallel scopes', async () => {
+ const beforeSend = jest.fn(() => null);
+ const beforeBreadcrumb = jest.fn(breadcrumb => breadcrumb);
+
+ mockSdkInit({ beforeSend, beforeBreadcrumb });
+
+ const hub = getCurrentHub();
+ const client = hub.getClient() as NodeExperimentalClient;
+
+ expect(hub).toBeInstanceOf(NodeExperimentalHub);
+ expect(client).toBeInstanceOf(NodeExperimentalClient);
+
+ const error = new Error('test');
+
+ hub.addBreadcrumb({ timestamp: 123456, message: 'test0' });
+
+ withScope(() => {
+ hub.addBreadcrumb({ timestamp: 123456, message: 'test1' });
+ });
+
+ withScope(() => {
+ hub.addBreadcrumb({ timestamp: 123456, message: 'test2' });
+ hub.captureException(error);
+ });
+
+ withScope(() => {
+ hub.addBreadcrumb({ timestamp: 123456, message: 'test3' });
+ });
+
+ await client.flush();
+
+ expect(beforeSend).toHaveBeenCalledTimes(1);
+ expect(beforeBreadcrumb).toHaveBeenCalledTimes(4);
+
+ expect(beforeSend).toHaveBeenCalledWith(
+ expect.objectContaining({
+ breadcrumbs: [
+ { message: 'test0', timestamp: 123456 },
+ { message: 'test2', timestamp: 123456 },
+ ],
+ }),
+ {
+ event_id: expect.any(String),
+ originalException: error,
+ syntheticException: expect.any(Error),
+ },
+ );
+ });
+ });
+
+ it('correctly adds & retrieves breadcrumbs', async () => {
+ const beforeSend = jest.fn(() => null);
+ const beforeBreadcrumb = jest.fn(breadcrumb => breadcrumb);
+
+ mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, enableTracing: true });
+
+ const hub = getCurrentHub();
+ const client = hub.getClient() as NodeExperimentalClient;
+
+ const error = new Error('test');
+
+ startSpan({ name: 'test' }, () => {
+ hub.addBreadcrumb({ timestamp: 123456, message: 'test1' });
+
+ startSpan({ name: 'inner1' }, () => {
+ hub.addBreadcrumb({ timestamp: 123457, message: 'test2', data: { nested: 'yes' } });
+ });
+
+ startSpan({ name: 'inner2' }, () => {
+ hub.addBreadcrumb({ timestamp: 123455, message: 'test3' });
+ });
+
+ hub.captureException(error);
+ });
+
+ await client.flush();
+
+ expect(beforeSend).toHaveBeenCalledTimes(1);
+ expect(beforeBreadcrumb).toHaveBeenCalledTimes(3);
+
+ expect(beforeSend).toHaveBeenCalledWith(
+ expect.objectContaining({
+ breadcrumbs: [
+ { message: 'test1', timestamp: 123456 },
+ { data: { nested: 'yes' }, message: 'test2', timestamp: 123457 },
+ { message: 'test3', timestamp: 123455 },
+ ],
+ }),
+ {
+ event_id: expect.any(String),
+ originalException: error,
+ syntheticException: expect.any(Error),
+ },
+ );
+ });
+
+ it('correctly adds & retrieves breadcrumbs for the current root span only', async () => {
+ const beforeSend = jest.fn(() => null);
+ const beforeBreadcrumb = jest.fn(breadcrumb => breadcrumb);
+
+ mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, enableTracing: true });
+
+ const hub = getCurrentHub();
+ const client = hub.getClient() as NodeExperimentalClient;
+
+ const error = new Error('test');
+
+ startSpan({ name: 'test1' }, () => {
+ hub.addBreadcrumb({ timestamp: 123456, message: 'test1-a' });
+
+ startSpan({ name: 'inner1' }, () => {
+ hub.addBreadcrumb({ timestamp: 123457, message: 'test1-b' });
+ });
+ });
+
+ startSpan({ name: 'test2' }, () => {
+ hub.addBreadcrumb({ timestamp: 123456, message: 'test2-a' });
+
+ startSpan({ name: 'inner2' }, () => {
+ hub.addBreadcrumb({ timestamp: 123457, message: 'test2-b' });
+ });
+
+ hub.captureException(error);
+ });
+
+ await client.flush();
+
+ expect(beforeSend).toHaveBeenCalledTimes(1);
+ expect(beforeBreadcrumb).toHaveBeenCalledTimes(4);
+
+ expect(beforeSend).toHaveBeenCalledWith(
+ expect.objectContaining({
+ breadcrumbs: [
+ { message: 'test2-a', timestamp: 123456 },
+ { message: 'test2-b', timestamp: 123457 },
+ ],
+ }),
+ {
+ event_id: expect.any(String),
+ originalException: error,
+ syntheticException: expect.any(Error),
+ },
+ );
+ });
+
+ it('ignores scopes inside of root span', async () => {
+ const beforeSend = jest.fn(() => null);
+ const beforeBreadcrumb = jest.fn(breadcrumb => breadcrumb);
+
+ mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, enableTracing: true });
+
+ const hub = getCurrentHub();
+ const client = hub.getClient() as NodeExperimentalClient;
+
+ const error = new Error('test');
+
+ startSpan({ name: 'test1' }, () => {
+ withScope(() => {
+ hub.addBreadcrumb({ timestamp: 123456, message: 'test1' });
+ });
+ startSpan({ name: 'inner1' }, () => {
+ hub.addBreadcrumb({ timestamp: 123457, message: 'test2' });
+ });
+
+ hub.captureException(error);
+ });
+
+ await client.flush();
+
+ expect(beforeSend).toHaveBeenCalledTimes(1);
+ expect(beforeBreadcrumb).toHaveBeenCalledTimes(2);
+
+ expect(beforeSend).toHaveBeenCalledWith(
+ expect.objectContaining({
+ breadcrumbs: [
+ { message: 'test1', timestamp: 123456 },
+ { message: 'test2', timestamp: 123457 },
+ ],
+ }),
+ {
+ event_id: expect.any(String),
+ originalException: error,
+ syntheticException: expect.any(Error),
+ },
+ );
+ });
+
+ it('handles deep nesting of scopes', async () => {
+ const beforeSend = jest.fn(() => null);
+ const beforeBreadcrumb = jest.fn(breadcrumb => breadcrumb);
+
+ mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, enableTracing: true });
+
+ const hub = getCurrentHub();
+ const client = hub.getClient() as NodeExperimentalClient;
+
+ const error = new Error('test');
+
+ startSpan({ name: 'test1' }, () => {
+ withScope(() => {
+ hub.addBreadcrumb({ timestamp: 123456, message: 'test1' });
+ });
+ startSpan({ name: 'inner1' }, () => {
+ hub.addBreadcrumb({ timestamp: 123457, message: 'test2' });
+
+ startSpan({ name: 'inner2' }, () => {
+ hub.addBreadcrumb({ timestamp: 123457, message: 'test3' });
+
+ startSpan({ name: 'inner3' }, () => {
+ hub.addBreadcrumb({ timestamp: 123457, message: 'test4' });
+
+ hub.captureException(error);
+
+ startSpan({ name: 'inner4' }, () => {
+ hub.addBreadcrumb({ timestamp: 123457, message: 'test5' });
+ });
+
+ hub.addBreadcrumb({ timestamp: 123457, message: 'test6' });
+ });
+ });
+ });
+
+ hub.addBreadcrumb({ timestamp: 123456, message: 'test99' });
+ });
+
+ await client.flush();
+
+ expect(beforeSend).toHaveBeenCalledTimes(1);
+
+ expect(beforeSend).toHaveBeenCalledWith(
+ expect.objectContaining({
+ breadcrumbs: [
+ { message: 'test1', timestamp: 123456 },
+ { message: 'test2', timestamp: 123457 },
+ { message: 'test3', timestamp: 123457 },
+ { message: 'test4', timestamp: 123457 },
+ ],
+ }),
+ {
+ event_id: expect.any(String),
+ originalException: error,
+ syntheticException: expect.any(Error),
+ },
+ );
+ });
+
+ it('correctly adds & retrieves breadcrumbs in async spans', async () => {
+ const beforeSend = jest.fn(() => null);
+ const beforeBreadcrumb = jest.fn(breadcrumb => breadcrumb);
+
+ mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, enableTracing: true });
+
+ const hub = getCurrentHub();
+ const client = hub.getClient() as NodeExperimentalClient;
+
+ const error = new Error('test');
+
+ const promise1 = startSpan({ name: 'test' }, async () => {
+ hub.addBreadcrumb({ timestamp: 123456, message: 'test1' });
+
+ await startSpan({ name: 'inner1' }, async () => {
+ hub.addBreadcrumb({ timestamp: 123457, message: 'test2' });
+ });
+
+ await startSpan({ name: 'inner2' }, async () => {
+ hub.addBreadcrumb({ timestamp: 123455, message: 'test3' });
+ });
+
+ await new Promise(resolve => setTimeout(resolve, 10));
+
+ hub.captureException(error);
+ });
+
+ const promise2 = startSpan({ name: 'test-b' }, async () => {
+ hub.addBreadcrumb({ timestamp: 123456, message: 'test1-b' });
+
+ await startSpan({ name: 'inner1' }, async () => {
+ hub.addBreadcrumb({ timestamp: 123457, message: 'test2-b' });
+ });
+
+ await startSpan({ name: 'inner2' }, async () => {
+ hub.addBreadcrumb({ timestamp: 123455, message: 'test3-b' });
+ });
+ });
+
+ await Promise.all([promise1, promise2]);
+
+ await client.flush();
+
+ expect(beforeSend).toHaveBeenCalledTimes(1);
+ expect(beforeBreadcrumb).toHaveBeenCalledTimes(6);
+
+ expect(beforeSend).toHaveBeenCalledWith(
+ expect.objectContaining({
+ breadcrumbs: [
+ { message: 'test1', timestamp: 123456 },
+ { message: 'test2', timestamp: 123457 },
+ { message: 'test3', timestamp: 123455 },
+ ],
+ }),
+ {
+ event_id: expect.any(String),
+ originalException: error,
+ syntheticException: expect.any(Error),
+ },
+ );
+ });
+});
diff --git a/packages/node-experimental/test/integration/otelTimedEvents.test.ts b/packages/node-experimental/test/integration/otelTimedEvents.test.ts
new file mode 100644
index 000000000000..8bdaec750a15
--- /dev/null
+++ b/packages/node-experimental/test/integration/otelTimedEvents.test.ts
@@ -0,0 +1,57 @@
+import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
+
+import type { NodeExperimentalClient } from '../../src/sdk/client';
+import { getCurrentHub } from '../../src/sdk/hub';
+import { startSpan } from '../../src/sdk/trace';
+import { cleanupOtel, mockSdkInit } from '../helpers/mockSdkInit';
+
+describe('Integration | OTEL TimedEvents', () => {
+ afterEach(() => {
+ cleanupOtel();
+ });
+
+ it('captures TimedEvents with name `exception` as exceptions', async () => {
+ const beforeSend = jest.fn(() => null);
+ const beforeSendTransaction = jest.fn(() => null);
+
+ mockSdkInit({ beforeSend, beforeSendTransaction, enableTracing: true });
+
+ const hub = getCurrentHub();
+ const client = hub.getClient() as NodeExperimentalClient;
+
+ startSpan({ name: 'test' }, span => {
+ span?.addEvent('exception', {
+ [SemanticAttributes.EXCEPTION_MESSAGE]: 'test-message',
+ 'test-span-event-attr': 'test-span-event-attr-value',
+ });
+
+ span?.addEvent('other', {
+ [SemanticAttributes.EXCEPTION_MESSAGE]: 'test-message-2',
+ 'test-span-event-attr': 'test-span-event-attr-value',
+ });
+ });
+
+ await client.flush();
+
+ expect(beforeSend).toHaveBeenCalledTimes(1);
+ expect(beforeSend).toHaveBeenCalledWith(
+ expect.objectContaining({
+ exception: {
+ values: [
+ {
+ mechanism: { handled: true, type: 'generic' },
+ stacktrace: expect.any(Object),
+ type: 'Error',
+ value: 'test-message',
+ },
+ ],
+ },
+ }),
+ {
+ event_id: expect.any(String),
+ originalException: expect.any(Error),
+ syntheticException: expect.any(Error),
+ },
+ );
+ });
+});
diff --git a/packages/node-experimental/test/integration/scope.test.ts b/packages/node-experimental/test/integration/scope.test.ts
new file mode 100644
index 000000000000..925047583f2e
--- /dev/null
+++ b/packages/node-experimental/test/integration/scope.test.ts
@@ -0,0 +1,235 @@
+import * as Sentry from '../../src/';
+import { NodeExperimentalClient } from '../../src/sdk/client';
+import { getCurrentHub, NodeExperimentalHub } from '../../src/sdk/hub';
+import { NodeExperimentalScope } from '../../src/sdk/scope';
+import { cleanupOtel, mockSdkInit } from '../helpers/mockSdkInit';
+
+describe('Integration | Scope', () => {
+ afterEach(() => {
+ cleanupOtel();
+ });
+
+ describe.each([
+ ['with tracing', true],
+ ['without tracing', false],
+ ])('%s', (_name, enableTracing) => {
+ it('correctly syncs OTEL context & Sentry hub/scope', async () => {
+ const beforeSend = jest.fn(() => null);
+ const beforeSendTransaction = jest.fn(() => null);
+
+ mockSdkInit({ enableTracing, beforeSend, beforeSendTransaction });
+
+ const hub = getCurrentHub();
+ const client = hub.getClient() as NodeExperimentalClient;
+
+ const rootScope = hub.getScope();
+
+ expect(hub).toBeInstanceOf(NodeExperimentalHub);
+ expect(rootScope).toBeInstanceOf(NodeExperimentalScope);
+ expect(client).toBeInstanceOf(NodeExperimentalClient);
+
+ const error = new Error('test error');
+ let spanId: string | undefined;
+ let traceId: string | undefined;
+
+ rootScope.setTag('tag1', 'val1');
+
+ Sentry.withScope(scope1 => {
+ scope1.setTag('tag2', 'val2');
+
+ Sentry.withScope(scope2b => {
+ scope2b.setTag('tag3-b', 'val3-b');
+ });
+
+ Sentry.withScope(scope2 => {
+ scope2.setTag('tag3', 'val3');
+
+ Sentry.startSpan({ name: 'outer' }, span => {
+ spanId = span?.spanContext().spanId;
+ traceId = span?.spanContext().traceId;
+
+ Sentry.setTag('tag4', 'val4');
+
+ Sentry.captureException(error);
+ });
+ });
+ });
+
+ await client.flush();
+
+ expect(beforeSend).toHaveBeenCalledTimes(1);
+ expect(beforeSend).toHaveBeenCalledWith(
+ expect.objectContaining({
+ contexts: expect.objectContaining({
+ trace: spanId
+ ? {
+ span_id: spanId,
+ trace_id: traceId,
+ parent_span_id: undefined,
+ }
+ : expect.any(Object),
+ }),
+ tags: {
+ tag1: 'val1',
+ tag2: 'val2',
+ tag3: 'val3',
+ tag4: 'val4',
+ },
+ }),
+ {
+ event_id: expect.any(String),
+ originalException: error,
+ syntheticException: expect.any(Error),
+ },
+ );
+
+ if (enableTracing) {
+ expect(beforeSendTransaction).toHaveBeenCalledTimes(1);
+ // Note: Scope for transaction is taken at `start` time, not `finish` time
+ expect(beforeSendTransaction).toHaveBeenCalledWith(
+ expect.objectContaining({
+ contexts: expect.objectContaining({
+ trace: {
+ data: { 'otel.kind': 'INTERNAL' },
+ span_id: spanId,
+ status: 'ok',
+ trace_id: traceId,
+ },
+ }),
+
+ spans: [],
+ start_timestamp: expect.any(Number),
+ tags: {
+ tag1: 'val1',
+ tag2: 'val2',
+ tag3: 'val3',
+ },
+ timestamp: expect.any(Number),
+ transaction: 'outer',
+ transaction_info: { source: 'custom' },
+ type: 'transaction',
+ }),
+ {
+ event_id: expect.any(String),
+ },
+ );
+ }
+ });
+
+ it('isolates parallel root scopes', async () => {
+ const beforeSend = jest.fn(() => null);
+ const beforeSendTransaction = jest.fn(() => null);
+
+ mockSdkInit({ enableTracing, beforeSend, beforeSendTransaction });
+
+ const hub = getCurrentHub();
+ const client = hub.getClient() as NodeExperimentalClient;
+
+ const rootScope = hub.getScope();
+
+ expect(hub).toBeInstanceOf(NodeExperimentalHub);
+ expect(rootScope).toBeInstanceOf(NodeExperimentalScope);
+ expect(client).toBeInstanceOf(NodeExperimentalClient);
+
+ const error1 = new Error('test error 1');
+ const error2 = new Error('test error 2');
+ let spanId1: string | undefined;
+ let spanId2: string | undefined;
+ let traceId1: string | undefined;
+ let traceId2: string | undefined;
+
+ rootScope.setTag('tag1', 'val1');
+
+ Sentry.withScope(scope1 => {
+ scope1.setTag('tag2', 'val2a');
+
+ Sentry.withScope(scope2 => {
+ scope2.setTag('tag3', 'val3a');
+
+ Sentry.startSpan({ name: 'outer' }, span => {
+ spanId1 = span?.spanContext().spanId;
+ traceId1 = span?.spanContext().traceId;
+
+ Sentry.setTag('tag4', 'val4a');
+
+ Sentry.captureException(error1);
+ });
+ });
+ });
+
+ Sentry.withScope(scope1 => {
+ scope1.setTag('tag2', 'val2b');
+
+ Sentry.withScope(scope2 => {
+ scope2.setTag('tag3', 'val3b');
+
+ Sentry.startSpan({ name: 'outer' }, span => {
+ spanId2 = span?.spanContext().spanId;
+ traceId2 = span?.spanContext().traceId;
+
+ Sentry.setTag('tag4', 'val4b');
+
+ Sentry.captureException(error2);
+ });
+ });
+ });
+
+ await client.flush();
+
+ expect(beforeSend).toHaveBeenCalledTimes(2);
+ expect(beforeSend).toHaveBeenCalledWith(
+ expect.objectContaining({
+ contexts: expect.objectContaining({
+ trace: spanId1
+ ? {
+ span_id: spanId1,
+ trace_id: traceId1,
+ parent_span_id: undefined,
+ }
+ : expect.any(Object),
+ }),
+ tags: {
+ tag1: 'val1',
+ tag2: 'val2a',
+ tag3: 'val3a',
+ tag4: 'val4a',
+ },
+ }),
+ {
+ event_id: expect.any(String),
+ originalException: error1,
+ syntheticException: expect.any(Error),
+ },
+ );
+
+ expect(beforeSend).toHaveBeenCalledWith(
+ expect.objectContaining({
+ contexts: expect.objectContaining({
+ trace: spanId2
+ ? {
+ span_id: spanId2,
+ trace_id: traceId2,
+ parent_span_id: undefined,
+ }
+ : expect.any(Object),
+ }),
+ tags: {
+ tag1: 'val1',
+ tag2: 'val2b',
+ tag3: 'val3b',
+ tag4: 'val4b',
+ },
+ }),
+ {
+ event_id: expect.any(String),
+ originalException: error2,
+ syntheticException: expect.any(Error),
+ },
+ );
+
+ if (enableTracing) {
+ expect(beforeSendTransaction).toHaveBeenCalledTimes(2);
+ }
+ });
+ });
+});
diff --git a/packages/node-experimental/test/integration/transactions.test.ts b/packages/node-experimental/test/integration/transactions.test.ts
new file mode 100644
index 000000000000..4d657fc4cbf5
--- /dev/null
+++ b/packages/node-experimental/test/integration/transactions.test.ts
@@ -0,0 +1,675 @@
+import { context, SpanKind, trace, TraceFlags } from '@opentelemetry/api';
+import type { SpanProcessor } from '@opentelemetry/sdk-trace-base';
+import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
+import type { Integration, PropagationContext, TransactionEvent } from '@sentry/types';
+import { logger } from '@sentry/utils';
+
+import * as Sentry from '../../src';
+import { startSpan } from '../../src';
+import { SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY } from '../../src/constants';
+import type { Http, NodeFetch } from '../../src/integrations';
+import { SentrySpanProcessor } from '../../src/opentelemetry/spanProcessor';
+import type { NodeExperimentalClient } from '../../src/sdk/client';
+import { getCurrentHub } from '../../src/sdk/hub';
+import { cleanupOtel, getProvider, mockSdkInit } from '../helpers/mockSdkInit';
+
+describe('Integration | Transactions', () => {
+ afterEach(() => {
+ jest.restoreAllMocks();
+ cleanupOtel();
+ });
+
+ it('correctly creates transaction & spans', async () => {
+ const beforeSendTransaction = jest.fn(() => null);
+
+ mockSdkInit({ enableTracing: true, beforeSendTransaction });
+
+ const hub = getCurrentHub();
+ const client = hub.getClient() as NodeExperimentalClient;
+
+ Sentry.addBreadcrumb({ message: 'test breadcrumb 1', timestamp: 123456 });
+ Sentry.setTag('outer.tag', 'test value');
+
+ Sentry.startSpan(
+ {
+ op: 'test op',
+ name: 'test name',
+ source: 'task',
+ origin: 'auto.test',
+ metadata: { requestPath: 'test-path' },
+ },
+ span => {
+ if (!span) {
+ return;
+ }
+
+ Sentry.addBreadcrumb({ message: 'test breadcrumb 2', timestamp: 123456 });
+
+ span.setAttributes({
+ 'test.outer': 'test value',
+ });
+
+ const subSpan = Sentry.startInactiveSpan({ name: 'inner span 1' });
+ subSpan?.end();
+
+ Sentry.setTag('test.tag', 'test value');
+
+ Sentry.startSpan({ name: 'inner span 2' }, innerSpan => {
+ if (!innerSpan) {
+ return;
+ }
+
+ Sentry.addBreadcrumb({ message: 'test breadcrumb 3', timestamp: 123456 });
+
+ innerSpan.setAttributes({
+ 'test.inner': 'test value',
+ });
+ });
+ },
+ );
+
+ await client.flush();
+
+ expect(beforeSendTransaction).toHaveBeenCalledTimes(1);
+ expect(beforeSendTransaction).toHaveBeenLastCalledWith(
+ expect.objectContaining({
+ breadcrumbs: [
+ { message: 'test breadcrumb 1', timestamp: 123456 },
+ { message: 'test breadcrumb 2', timestamp: 123456 },
+ { message: 'test breadcrumb 3', timestamp: 123456 },
+ ],
+ contexts: {
+ otel: {
+ attributes: {
+ 'test.outer': 'test value',
+ },
+ resource: {
+ 'service.name': 'node-experimental',
+ 'service.namespace': 'sentry',
+ 'service.version': expect.any(String),
+ 'telemetry.sdk.language': 'nodejs',
+ 'telemetry.sdk.name': 'opentelemetry',
+ 'telemetry.sdk.version': expect.any(String),
+ },
+ },
+ runtime: { name: 'node', version: expect.any(String) },
+ trace: {
+ data: { 'otel.kind': 'INTERNAL' },
+ op: 'test op',
+ span_id: expect.any(String),
+ status: 'ok',
+ trace_id: expect.any(String),
+ },
+ },
+ environment: 'production',
+ event_id: expect.any(String),
+ platform: 'node',
+ sdkProcessingMetadata: {
+ dynamicSamplingContext: expect.objectContaining({
+ environment: 'production',
+ public_key: expect.any(String),
+ sample_rate: '1',
+ sampled: 'true',
+ trace_id: expect.any(String),
+ transaction: 'test name',
+ }),
+ propagationContext: {
+ sampled: undefined,
+ spanId: expect.any(String),
+ traceId: expect.any(String),
+ },
+ sampleRate: 1,
+ source: 'task',
+ spanMetadata: expect.any(Object),
+ requestPath: 'test-path',
+ },
+ server_name: expect.any(String),
+ // spans are circular (they have a reference to the transaction), which leads to jest choking on this
+ // instead we compare them in detail below
+ spans: [
+ expect.objectContaining({
+ description: 'inner span 1',
+ }),
+ expect.objectContaining({
+ description: 'inner span 2',
+ }),
+ ],
+ start_timestamp: expect.any(Number),
+ tags: {
+ 'outer.tag': 'test value',
+ },
+ timestamp: expect.any(Number),
+ transaction: 'test name',
+ transaction_info: { source: 'task' },
+ type: 'transaction',
+ }),
+ {
+ event_id: expect.any(String),
+ },
+ );
+
+ // Checking the spans here, as they are circular to the transaction...
+ const runArgs = beforeSendTransaction.mock.calls[0] as unknown as [TransactionEvent, unknown];
+ const spans = runArgs[0].spans || [];
+
+ // note: Currently, spans do not have any context/span added to them
+ // This is the same behavior as for the "regular" SDKs
+ expect(spans.map(span => span.toJSON())).toEqual([
+ {
+ data: { 'otel.kind': 'INTERNAL' },
+ description: 'inner span 1',
+ origin: 'manual',
+ parent_span_id: expect.any(String),
+ span_id: expect.any(String),
+ start_timestamp: expect.any(Number),
+ status: 'ok',
+ timestamp: expect.any(Number),
+ trace_id: expect.any(String),
+ },
+ {
+ data: { 'otel.kind': 'INTERNAL', 'test.inner': 'test value' },
+ description: 'inner span 2',
+ origin: 'manual',
+ parent_span_id: expect.any(String),
+ span_id: expect.any(String),
+ start_timestamp: expect.any(Number),
+ status: 'ok',
+ timestamp: expect.any(Number),
+ trace_id: expect.any(String),
+ },
+ ]);
+ });
+
+ it('correctly creates concurrent transaction & spans', async () => {
+ const beforeSendTransaction = jest.fn(() => null);
+
+ mockSdkInit({ enableTracing: true, beforeSendTransaction });
+
+ const hub = getCurrentHub();
+ const client = hub.getClient() as NodeExperimentalClient;
+
+ Sentry.addBreadcrumb({ message: 'test breadcrumb 1', timestamp: 123456 });
+
+ Sentry.startSpan({ op: 'test op', name: 'test name', source: 'task', origin: 'auto.test' }, span => {
+ if (!span) {
+ return;
+ }
+
+ Sentry.addBreadcrumb({ message: 'test breadcrumb 2', timestamp: 123456 });
+
+ span.setAttributes({
+ 'test.outer': 'test value',
+ });
+
+ const subSpan = Sentry.startInactiveSpan({ name: 'inner span 1' });
+ subSpan?.end();
+
+ Sentry.setTag('test.tag', 'test value');
+
+ Sentry.startSpan({ name: 'inner span 2' }, innerSpan => {
+ if (!innerSpan) {
+ return;
+ }
+
+ Sentry.addBreadcrumb({ message: 'test breadcrumb 3', timestamp: 123456 });
+
+ innerSpan.setAttributes({
+ 'test.inner': 'test value',
+ });
+ });
+ });
+
+ Sentry.startSpan({ op: 'test op b', name: 'test name b' }, span => {
+ if (!span) {
+ return;
+ }
+
+ Sentry.addBreadcrumb({ message: 'test breadcrumb 2b', timestamp: 123456 });
+
+ span.setAttributes({
+ 'test.outer': 'test value b',
+ });
+
+ const subSpan = Sentry.startInactiveSpan({ name: 'inner span 1b' });
+ subSpan?.end();
+
+ Sentry.setTag('test.tag', 'test value b');
+
+ Sentry.startSpan({ name: 'inner span 2b' }, innerSpan => {
+ if (!innerSpan) {
+ return;
+ }
+
+ Sentry.addBreadcrumb({ message: 'test breadcrumb 3b', timestamp: 123456 });
+
+ innerSpan.setAttributes({
+ 'test.inner': 'test value b',
+ });
+ });
+ });
+
+ await client.flush();
+
+ expect(beforeSendTransaction).toHaveBeenCalledTimes(2);
+ expect(beforeSendTransaction).toHaveBeenCalledWith(
+ expect.objectContaining({
+ breadcrumbs: [
+ { message: 'test breadcrumb 1', timestamp: 123456 },
+ { message: 'test breadcrumb 2', timestamp: 123456 },
+ { message: 'test breadcrumb 3', timestamp: 123456 },
+ ],
+ contexts: expect.objectContaining({
+ otel: expect.objectContaining({
+ attributes: {
+ 'test.outer': 'test value',
+ },
+ }),
+ trace: {
+ data: { 'otel.kind': 'INTERNAL' },
+ op: 'test op',
+ span_id: expect.any(String),
+ status: 'ok',
+ trace_id: expect.any(String),
+ },
+ }),
+ spans: [
+ expect.objectContaining({
+ description: 'inner span 1',
+ }),
+ expect.objectContaining({
+ description: 'inner span 2',
+ }),
+ ],
+ start_timestamp: expect.any(Number),
+ tags: {},
+ timestamp: expect.any(Number),
+ transaction: 'test name',
+ transaction_info: { source: 'task' },
+ type: 'transaction',
+ }),
+ {
+ event_id: expect.any(String),
+ },
+ );
+
+ expect(beforeSendTransaction).toHaveBeenCalledWith(
+ expect.objectContaining({
+ breadcrumbs: [
+ { message: 'test breadcrumb 1', timestamp: 123456 },
+ { message: 'test breadcrumb 2b', timestamp: 123456 },
+ { message: 'test breadcrumb 3b', timestamp: 123456 },
+ ],
+ contexts: expect.objectContaining({
+ otel: expect.objectContaining({
+ attributes: {
+ 'test.outer': 'test value b',
+ },
+ }),
+ trace: {
+ data: { 'otel.kind': 'INTERNAL' },
+ op: 'test op b',
+ span_id: expect.any(String),
+ status: 'ok',
+ trace_id: expect.any(String),
+ },
+ }),
+ spans: [
+ expect.objectContaining({
+ description: 'inner span 1b',
+ }),
+ expect.objectContaining({
+ description: 'inner span 2b',
+ }),
+ ],
+ start_timestamp: expect.any(Number),
+ tags: {},
+ timestamp: expect.any(Number),
+ transaction: 'test name b',
+ transaction_info: { source: 'custom' },
+ type: 'transaction',
+ }),
+ {
+ event_id: expect.any(String),
+ },
+ );
+ });
+
+ it('correctly creates transaction & spans with a trace header data', async () => {
+ const beforeSendTransaction = jest.fn(() => null);
+
+ const traceId = 'd4cda95b652f4a1592b449d5929fda1b';
+ const parentSpanId = '6e0c63257de34c92';
+
+ const spanContext = {
+ traceId,
+ spanId: parentSpanId,
+ sampled: true,
+ isRemote: true,
+ traceFlags: TraceFlags.SAMPLED,
+ };
+
+ const propagationContext: PropagationContext = {
+ traceId,
+ parentSpanId,
+ spanId: '6e0c63257de34c93',
+ sampled: true,
+ };
+
+ mockSdkInit({ enableTracing: true, beforeSendTransaction });
+
+ const hub = getCurrentHub();
+ const client = hub.getClient() as NodeExperimentalClient;
+
+ // We simulate the correct context we'd normally get from the SentryPropagator
+ context.with(
+ trace.setSpanContext(
+ context.active().setValue(SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY, propagationContext),
+ spanContext,
+ ),
+ () => {
+ Sentry.startSpan({ op: 'test op', name: 'test name', source: 'task', origin: 'auto.test' }, span => {
+ if (!span) {
+ return;
+ }
+
+ const subSpan = Sentry.startInactiveSpan({ name: 'inner span 1' });
+ subSpan?.end();
+
+ Sentry.startSpan({ name: 'inner span 2' }, innerSpan => {
+ if (!innerSpan) {
+ return;
+ }
+ });
+ });
+ },
+ );
+
+ await client.flush();
+
+ expect(beforeSendTransaction).toHaveBeenCalledTimes(1);
+ expect(beforeSendTransaction).toHaveBeenLastCalledWith(
+ expect.objectContaining({
+ contexts: expect.objectContaining({
+ otel: expect.objectContaining({
+ attributes: {},
+ }),
+ trace: {
+ data: { 'otel.kind': 'INTERNAL' },
+ op: 'test op',
+ span_id: expect.any(String),
+ parent_span_id: parentSpanId,
+ status: 'ok',
+ trace_id: traceId,
+ },
+ }),
+ // spans are circular (they have a reference to the transaction), which leads to jest choking on this
+ // instead we compare them in detail below
+ spans: [
+ expect.objectContaining({
+ description: 'inner span 1',
+ }),
+ expect.objectContaining({
+ description: 'inner span 2',
+ }),
+ ],
+ start_timestamp: expect.any(Number),
+ timestamp: expect.any(Number),
+ transaction: 'test name',
+ transaction_info: { source: 'task' },
+ type: 'transaction',
+ }),
+ {
+ event_id: expect.any(String),
+ },
+ );
+
+ // Checking the spans here, as they are circular to the transaction...
+ const runArgs = beforeSendTransaction.mock.calls[0] as unknown as [TransactionEvent, unknown];
+ const spans = runArgs[0].spans || [];
+
+ // note: Currently, spans do not have any context/span added to them
+ // This is the same behavior as for the "regular" SDKs
+ expect(spans.map(span => span.toJSON())).toEqual([
+ {
+ data: { 'otel.kind': 'INTERNAL' },
+ description: 'inner span 1',
+ origin: 'manual',
+ parent_span_id: expect.any(String),
+ span_id: expect.any(String),
+ start_timestamp: expect.any(Number),
+ status: 'ok',
+ timestamp: expect.any(Number),
+ trace_id: traceId,
+ },
+ {
+ data: { 'otel.kind': 'INTERNAL' },
+ description: 'inner span 2',
+ origin: 'manual',
+ parent_span_id: expect.any(String),
+ span_id: expect.any(String),
+ start_timestamp: expect.any(Number),
+ status: 'ok',
+ timestamp: expect.any(Number),
+ trace_id: traceId,
+ },
+ ]);
+ });
+
+ it('cleans up spans that are not flushed for over 5 mins', async () => {
+ const beforeSendTransaction = jest.fn(() => null);
+
+ const now = Date.now();
+ jest.useFakeTimers();
+ jest.setSystemTime(now);
+
+ const logs: unknown[] = [];
+ jest.spyOn(logger, 'log').mockImplementation(msg => logs.push(msg));
+
+ mockSdkInit({ enableTracing: true, beforeSendTransaction });
+
+ const hub = getCurrentHub();
+ const client = hub.getClient() as NodeExperimentalClient;
+ const provider = getProvider();
+ const multiSpanProcessor = provider?.activeSpanProcessor as
+ | (SpanProcessor & { _spanProcessors?: SpanProcessor[] })
+ | undefined;
+ const spanProcessor = multiSpanProcessor?.['_spanProcessors']?.find(
+ spanProcessor => spanProcessor instanceof SentrySpanProcessor,
+ ) as SentrySpanProcessor | undefined;
+
+ const exporter = spanProcessor ? spanProcessor['_exporter'] : undefined;
+
+ if (!exporter) {
+ throw new Error('No exporter found, aborting test...');
+ }
+
+ let innerSpan1Id: string | undefined;
+ let innerSpan2Id: string | undefined;
+
+ void Sentry.startSpan({ name: 'test name' }, async span => {
+ if (!span) {
+ return;
+ }
+
+ const subSpan = Sentry.startInactiveSpan({ name: 'inner span 1' });
+ innerSpan1Id = subSpan?.spanContext().spanId;
+ subSpan?.end();
+
+ Sentry.startSpan({ name: 'inner span 2' }, innerSpan => {
+ if (!innerSpan) {
+ return;
+ }
+
+ innerSpan2Id = innerSpan.spanContext().spanId;
+ });
+
+ // Pretend this is pending for 10 minutes
+ await new Promise(resolve => setTimeout(resolve, 10 * 60 * 1000));
+ });
+
+ // Nothing added to exporter yet
+ expect(exporter['_finishedSpans'].length).toBe(0);
+
+ void client.flush(5_000);
+ jest.advanceTimersByTime(5_000);
+
+ // Now the child-spans have been added to the exporter, but they are pending since they are waiting for their parant
+ expect(exporter['_finishedSpans'].length).toBe(2);
+ expect(beforeSendTransaction).toHaveBeenCalledTimes(0);
+
+ // Now wait for 5 mins
+ jest.advanceTimersByTime(5 * 60 * 1_000);
+
+ // Adding another span will trigger the cleanup
+ Sentry.startSpan({ name: 'other span' }, () => {});
+
+ void client.flush(5_000);
+ jest.advanceTimersByTime(5_000);
+
+ // Old spans have been cleared away
+ expect(exporter['_finishedSpans'].length).toBe(0);
+
+ // Called once for the 'other span'
+ expect(beforeSendTransaction).toHaveBeenCalledTimes(1);
+
+ expect(logs).toEqual(
+ expect.arrayContaining([
+ 'SpanExporter exported 0 spans, 2 unsent spans remaining',
+ 'SpanExporter exported 1 spans, 2 unsent spans remaining',
+ `SpanExporter dropping span inner span 1 (${innerSpan1Id}) because it is pending for more than 5 minutes.`,
+ `SpanExporter dropping span inner span 2 (${innerSpan2Id}) because it is pending for more than 5 minutes.`,
+ ]),
+ );
+ });
+
+ it('does not create spans for http requests if disabled in http integration', async () => {
+ const beforeSendTransaction = jest.fn(() => null);
+
+ mockSdkInit({ enableTracing: true, beforeSendTransaction });
+
+ jest.useFakeTimers();
+
+ const hub = getCurrentHub();
+ const client = hub.getClient() as NodeExperimentalClient;
+
+ jest.spyOn(client, 'getIntegration').mockImplementation(integrationClass => {
+ if (integrationClass.name === 'Http') {
+ return {
+ shouldCreateSpansForRequests: false,
+ } as Http;
+ }
+
+ return {} as Integration;
+ });
+
+ client.tracer.startActiveSpan(
+ 'test op',
+ {
+ kind: SpanKind.CLIENT,
+ attributes: {
+ [SemanticAttributes.HTTP_METHOD]: 'GET',
+ [SemanticAttributes.HTTP_URL]: 'https://example.com',
+ },
+ },
+ span => {
+ startSpan({ name: 'inner 1' }, () => {
+ startSpan({ name: 'inner 2' }, () => {});
+ });
+
+ span.end();
+ },
+ );
+
+ void client.flush();
+ jest.advanceTimersByTime(5_000);
+
+ expect(beforeSendTransaction).toHaveBeenCalledTimes(0);
+
+ // Now try a non-HTTP span
+ client.tracer.startActiveSpan(
+ 'test op 2',
+ {
+ kind: SpanKind.CLIENT,
+ attributes: {},
+ },
+ span => {
+ startSpan({ name: 'inner 1' }, () => {
+ startSpan({ name: 'inner 2' }, () => {});
+ });
+
+ span.end();
+ },
+ );
+
+ void client.flush();
+ jest.advanceTimersByTime(5_000);
+
+ expect(beforeSendTransaction).toHaveBeenCalledTimes(1);
+ });
+
+ it('does not create spans for fetch requests if disabled in fetch integration', async () => {
+ const beforeSendTransaction = jest.fn(() => null);
+
+ mockSdkInit({ enableTracing: true, beforeSendTransaction });
+
+ jest.useFakeTimers();
+
+ const hub = getCurrentHub();
+ const client = hub.getClient() as NodeExperimentalClient;
+
+ jest.spyOn(client, 'getIntegration').mockImplementation(integrationClass => {
+ if (integrationClass.name === 'NodeFetch') {
+ return {
+ shouldCreateSpansForRequests: false,
+ } as NodeFetch;
+ }
+
+ return {} as Integration;
+ });
+
+ client.tracer.startActiveSpan(
+ 'test op',
+ {
+ kind: SpanKind.CLIENT,
+ attributes: {
+ [SemanticAttributes.HTTP_METHOD]: 'GET',
+ [SemanticAttributes.HTTP_URL]: 'https://example.com',
+ 'http.client': 'fetch',
+ },
+ },
+ span => {
+ startSpan({ name: 'inner 1' }, () => {
+ startSpan({ name: 'inner 2' }, () => {});
+ });
+
+ span.end();
+ },
+ );
+
+ void client.flush();
+ jest.advanceTimersByTime(5_000);
+
+ expect(beforeSendTransaction).toHaveBeenCalledTimes(0);
+
+ // Now try a non-HTTP span
+ client.tracer.startActiveSpan(
+ 'test op 2',
+ {
+ kind: SpanKind.CLIENT,
+ attributes: {},
+ },
+ span => {
+ startSpan({ name: 'inner 1' }, () => {
+ startSpan({ name: 'inner 2' }, () => {});
+ });
+
+ span.end();
+ },
+ );
+
+ void client.flush();
+ jest.advanceTimersByTime(5_000);
+
+ expect(beforeSendTransaction).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/packages/node-experimental/test/opentelemetry/propagator.test.ts b/packages/node-experimental/test/opentelemetry/propagator.test.ts
new file mode 100644
index 000000000000..80b027496428
--- /dev/null
+++ b/packages/node-experimental/test/opentelemetry/propagator.test.ts
@@ -0,0 +1,375 @@
+import type { Context } from '@opentelemetry/api';
+import {
+ defaultTextMapGetter,
+ defaultTextMapSetter,
+ propagation,
+ ROOT_CONTEXT,
+ trace,
+ TraceFlags,
+} from '@opentelemetry/api';
+import { suppressTracing } from '@opentelemetry/core';
+import { addTracingExtensions, Hub, makeMain } from '@sentry/core';
+import type { PropagationContext } from '@sentry/types';
+
+import {
+ SENTRY_BAGGAGE_HEADER,
+ SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY,
+ SENTRY_TRACE_HEADER,
+} from '../../src/constants';
+import { SentryPropagator } from '../../src/opentelemetry/propagator';
+
+beforeAll(() => {
+ addTracingExtensions();
+});
+
+describe('SentryPropagator', () => {
+ const propagator = new SentryPropagator();
+ let carrier: { [key: string]: unknown };
+
+ beforeEach(() => {
+ carrier = {};
+ });
+
+ it('returns fields set', () => {
+ expect(propagator.fields()).toEqual([SENTRY_TRACE_HEADER, SENTRY_BAGGAGE_HEADER]);
+ });
+
+ describe('inject', () => {
+ const client = {
+ getOptions: () => ({
+ environment: 'production',
+ release: '1.0.0',
+ }),
+ getDsn: () => ({
+ publicKey: 'abc',
+ }),
+ };
+ // @ts-expect-error Use mock client for unit tests
+ const hub: Hub = new Hub(client);
+ makeMain(hub);
+
+ describe('with active span', () => {
+ it.each([
+ [
+ 'works with a sampled propagation context',
+ {
+ traceId: 'd4cda95b652f4a1592b449d5929fda1b',
+ spanId: '6e0c63257de34c92',
+ traceFlags: TraceFlags.SAMPLED,
+ },
+ {
+ traceId: 'd4cda95b652f4a1592b449d5929fda1b',
+ spanId: '6e0c63257de34c94',
+ parentSpanId: '6e0c63257de34c93',
+ sampled: true,
+ dsc: {
+ transaction: 'sampled-transaction',
+ trace_id: 'd4cda95b652f4a1592b449d5929fda1b',
+ sampled: 'true',
+ public_key: 'abc',
+ environment: 'production',
+ release: '1.0.0',
+ },
+ },
+ [
+ 'sentry-environment=production',
+ 'sentry-release=1.0.0',
+ 'sentry-public_key=abc',
+ 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b',
+ 'sentry-transaction=sampled-transaction',
+ 'sentry-sampled=true',
+ ],
+ 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1',
+ ],
+ [
+ 'works with an unsampled propagation context',
+ {
+ traceId: 'd4cda95b652f4a1592b449d5929fda1b',
+ spanId: '6e0c63257de34c92',
+ traceFlags: TraceFlags.NONE,
+ },
+ {
+ traceId: 'd4cda95b652f4a1592b449d5929fda1b',
+ spanId: '6e0c63257de34c94',
+ parentSpanId: '6e0c63257de34c93',
+ sampled: false,
+ dsc: {
+ transaction: 'not-sampled-transaction',
+ trace_id: 'd4cda95b652f4a1592b449d5929fda1b',
+ sampled: 'false',
+ public_key: 'abc',
+ environment: 'production',
+ release: '1.0.0',
+ },
+ },
+ [
+ 'sentry-environment=production',
+ 'sentry-release=1.0.0',
+ 'sentry-public_key=abc',
+ 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b',
+ 'sentry-transaction=not-sampled-transaction',
+ 'sentry-sampled=false',
+ ],
+ 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-0',
+ ],
+ [
+ 'creates a new DSC if none exists yet',
+ {
+ traceId: 'd4cda95b652f4a1592b449d5929fda1b',
+ spanId: '6e0c63257de34c92',
+ traceFlags: TraceFlags.SAMPLED,
+ },
+ {
+ traceId: 'd4cda95b652f4a1592b449d5929fda1b',
+ spanId: '6e0c63257de34c94',
+ parentSpanId: '6e0c63257de34c93',
+ sampled: true,
+ dsc: undefined,
+ },
+ [
+ 'sentry-environment=production',
+ 'sentry-public_key=abc',
+ 'sentry-release=1.0.0',
+ 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b',
+ ],
+ 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1',
+ ],
+ [
+ 'works with a remote parent span',
+ {
+ traceId: 'd4cda95b652f4a1592b449d5929fda1b',
+ spanId: '6e0c63257de34c92',
+ traceFlags: TraceFlags.SAMPLED,
+ isRemote: true,
+ },
+ {
+ traceId: 'd4cda95b652f4a1592b449d5929fda1b',
+ spanId: '6e0c63257de34c94',
+ parentSpanId: '6e0c63257de34c93',
+ sampled: true,
+ dsc: {
+ transaction: 'sampled-transaction',
+ trace_id: 'd4cda95b652f4a1592b449d5929fda1b',
+ sampled: 'true',
+ public_key: 'abc',
+ environment: 'production',
+ release: '1.0.0',
+ },
+ },
+ [
+ 'sentry-environment=production',
+ 'sentry-release=1.0.0',
+ 'sentry-public_key=abc',
+ 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b',
+ 'sentry-transaction=sampled-transaction',
+ 'sentry-sampled=true',
+ ],
+ 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c94-1',
+ ],
+ ])('%s', (_name, spanContext, propagationContext, baggage, sentryTrace) => {
+ const context = trace.setSpanContext(setPropagationContext(ROOT_CONTEXT, propagationContext), spanContext);
+ propagator.inject(context, carrier, defaultTextMapSetter);
+ expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual(baggage.sort());
+ expect(carrier[SENTRY_TRACE_HEADER]).toBe(sentryTrace);
+ });
+
+ it('should include existing baggage', () => {
+ const propagationContext: PropagationContext = {
+ traceId: 'd4cda95b652f4a1592b449d5929fda1b',
+ spanId: '6e0c63257de34c92',
+ parentSpanId: '6e0c63257de34c93',
+ sampled: true,
+ dsc: {
+ transaction: 'sampled-transaction',
+ trace_id: 'd4cda95b652f4a1592b449d5929fda1b',
+ sampled: 'true',
+ public_key: 'abc',
+ environment: 'production',
+ release: '1.0.0',
+ },
+ };
+
+ const spanContext = {
+ traceId: 'd4cda95b652f4a1592b449d5929fda1b',
+ spanId: '6e0c63257de34c92',
+ traceFlags: TraceFlags.SAMPLED,
+ };
+ const context = trace.setSpanContext(setPropagationContext(ROOT_CONTEXT, propagationContext), spanContext);
+ const baggage = propagation.createBaggage({ foo: { value: 'bar' } });
+ propagator.inject(propagation.setBaggage(context, baggage), carrier, defaultTextMapSetter);
+ expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual(
+ [
+ 'foo=bar',
+ 'sentry-transaction=sampled-transaction',
+ 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b',
+ 'sentry-sampled=true',
+ 'sentry-public_key=abc',
+ 'sentry-environment=production',
+ 'sentry-release=1.0.0',
+ ].sort(),
+ );
+ });
+
+ it('should create baggage without propagation context', () => {
+ const spanContext = {
+ traceId: 'd4cda95b652f4a1592b449d5929fda1b',
+ spanId: '6e0c63257de34c92',
+ traceFlags: TraceFlags.SAMPLED,
+ };
+ const context = trace.setSpanContext(ROOT_CONTEXT, spanContext);
+ const baggage = propagation.createBaggage({ foo: { value: 'bar' } });
+ propagator.inject(propagation.setBaggage(context, baggage), carrier, defaultTextMapSetter);
+ expect(carrier[SENTRY_BAGGAGE_HEADER]).toBe('foo=bar');
+ });
+
+ it('should NOT set baggage and sentry-trace header if instrumentation is supressed', () => {
+ const spanContext = {
+ traceId: 'd4cda95b652f4a1592b449d5929fda1b',
+ spanId: '6e0c63257de34c92',
+ traceFlags: TraceFlags.SAMPLED,
+ };
+ const propagationContext: PropagationContext = {
+ traceId: 'd4cda95b652f4a1592b449d5929fda1b',
+ spanId: '6e0c63257de34c92',
+ parentSpanId: '6e0c63257de34c93',
+ sampled: true,
+ dsc: {
+ transaction: 'sampled-transaction',
+ trace_id: 'd4cda95b652f4a1592b449d5929fda1b',
+ sampled: 'true',
+ public_key: 'abc',
+ environment: 'production',
+ release: '1.0.0',
+ },
+ };
+ const context = suppressTracing(
+ trace.setSpanContext(setPropagationContext(ROOT_CONTEXT, propagationContext), spanContext),
+ );
+ propagator.inject(context, carrier, defaultTextMapSetter);
+ expect(carrier[SENTRY_TRACE_HEADER]).toBe(undefined);
+ expect(carrier[SENTRY_BAGGAGE_HEADER]).toBe(undefined);
+ });
+ });
+
+ it('should take span from propagationContext id if no active span is found', () => {
+ const propagationContext: PropagationContext = {
+ traceId: 'd4cda95b652f4a1592b449d5929fda1b',
+ parentSpanId: '6e0c63257de34c93',
+ spanId: '6e0c63257de34c92',
+ sampled: true,
+ dsc: {
+ transaction: 'sampled-transaction',
+ trace_id: 'd4cda95b652f4a1592b449d5929fda1b',
+ sampled: 'true',
+ public_key: 'abc',
+ environment: 'production',
+ release: '1.0.0',
+ },
+ };
+
+ const context = setPropagationContext(ROOT_CONTEXT, propagationContext);
+ propagator.inject(context, carrier, defaultTextMapSetter);
+ expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual(
+ [
+ 'sentry-transaction=sampled-transaction',
+ 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b',
+ 'sentry-sampled=true',
+ 'sentry-public_key=abc',
+ 'sentry-environment=production',
+ 'sentry-release=1.0.0',
+ ].sort(),
+ );
+ expect(carrier[SENTRY_TRACE_HEADER]).toBe('d4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1');
+ });
+ });
+
+ describe('extract', () => {
+ it('sets sentry span context on the context', () => {
+ const sentryTraceHeader = 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1';
+ carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader;
+ const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter);
+ expect(trace.getSpanContext(context)).toEqual({
+ isRemote: true,
+ spanId: '6e0c63257de34c92',
+ traceFlags: TraceFlags.SAMPLED,
+ traceId: 'd4cda95b652f4a1592b449d5929fda1b',
+ });
+ });
+
+ it('sets defined sentry trace header on context', () => {
+ const sentryTraceHeader = 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1';
+ carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader;
+ const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter);
+
+ const propagationContext = context.getValue(SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY) as PropagationContext;
+ expect(propagationContext).toEqual({
+ sampled: true,
+ parentSpanId: '6e0c63257de34c92',
+ spanId: expect.any(String),
+ traceId: 'd4cda95b652f4a1592b449d5929fda1b',
+ });
+
+ // Ensure spanId !== parentSpanId - it should be a new random ID
+ expect(propagationContext.spanId).not.toBe('6e0c63257de34c92');
+ });
+
+ it('sets undefined sentry trace header on context', () => {
+ const sentryTraceHeader = undefined;
+ carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader;
+ const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter);
+ expect(context.getValue(SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY)).toEqual({
+ sampled: undefined,
+ spanId: expect.any(String),
+ traceId: expect.any(String),
+ });
+ });
+
+ it('sets defined dynamic sampling context on context', () => {
+ const baggage =
+ 'sentry-environment=production,sentry-release=1.0.0,sentry-public_key=abc,sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b,sentry-transaction=dsc-transaction';
+ carrier[SENTRY_BAGGAGE_HEADER] = baggage;
+ const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter);
+ expect(context.getValue(SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY)).toEqual({
+ sampled: undefined,
+ spanId: expect.any(String),
+ traceId: expect.any(String), // Note: This is not automatically taken from the DSC (in reality, this should be aligned)
+ dsc: {
+ environment: 'production',
+ public_key: 'abc',
+ release: '1.0.0',
+ trace_id: 'd4cda95b652f4a1592b449d5929fda1b',
+ transaction: 'dsc-transaction',
+ },
+ });
+ });
+
+ it('sets undefined dynamic sampling context on context', () => {
+ const baggage = '';
+ carrier[SENTRY_BAGGAGE_HEADER] = baggage;
+ const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter);
+ expect(context.getValue(SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY)).toEqual({
+ sampled: undefined,
+ spanId: expect.any(String),
+ traceId: expect.any(String),
+ });
+ });
+
+ it('handles when sentry-trace is an empty array', () => {
+ carrier[SENTRY_TRACE_HEADER] = [];
+ const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter);
+ expect(context.getValue(SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY)).toEqual({
+ sampled: undefined,
+ spanId: expect.any(String),
+ traceId: expect.any(String),
+ });
+ });
+ });
+});
+
+function setPropagationContext(context: Context, propagationContext: PropagationContext): Context {
+ return context.setValue(SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY, propagationContext);
+}
+
+function baggageToArray(baggage: unknown): string[] {
+ return typeof baggage === 'string' ? baggage.split(',').sort() : [];
+}
diff --git a/packages/node-experimental/test/sdk/client.test.ts b/packages/node-experimental/test/sdk/client.test.ts
new file mode 100644
index 000000000000..b7db215a4cd8
--- /dev/null
+++ b/packages/node-experimental/test/sdk/client.test.ts
@@ -0,0 +1,48 @@
+import { ProxyTracer } from '@opentelemetry/api';
+import { SDK_VERSION } from '@sentry/core';
+
+import { NodeExperimentalClient } from '../../src/sdk/client';
+import { getDefaultNodeExperimentalClientOptions } from '../helpers/getDefaultNodePreviewClientOptions';
+
+describe('NodeExperimentalClient', () => {
+ it('sets correct metadata', () => {
+ const options = getDefaultNodeExperimentalClientOptions();
+ const client = new NodeExperimentalClient(options);
+
+ expect(client.getOptions()).toEqual({
+ integrations: [],
+ transport: options.transport,
+ stackParser: options.stackParser,
+ _metadata: {
+ sdk: {
+ name: 'sentry.javascript.node-experimental',
+ packages: [
+ {
+ name: 'npm:@sentry/node-experimental',
+ version: SDK_VERSION,
+ },
+ ],
+ version: SDK_VERSION,
+ },
+ },
+ transportOptions: { textEncoder: expect.any(Object) },
+ platform: 'node',
+ runtime: { name: 'node', version: expect.any(String) },
+ serverName: expect.any(String),
+ tracesSampleRate: 1,
+ });
+ });
+
+ it('exposes a tracer', () => {
+ const client = new NodeExperimentalClient(getDefaultNodeExperimentalClientOptions());
+
+ const tracer = client.tracer;
+ expect(tracer).toBeDefined();
+ expect(tracer).toBeInstanceOf(ProxyTracer);
+
+ // Ensure we always get the same tracer instance
+ const tracer2 = client.tracer;
+
+ expect(tracer2).toBe(tracer);
+ });
+});
diff --git a/packages/node-experimental/test/sdk/hub.test.ts b/packages/node-experimental/test/sdk/hub.test.ts
new file mode 100644
index 000000000000..a25de1565ad8
--- /dev/null
+++ b/packages/node-experimental/test/sdk/hub.test.ts
@@ -0,0 +1,43 @@
+import { getCurrentHub, NodeExperimentalHub } from '../../src/sdk/hub';
+import { NodeExperimentalScope } from '../../src/sdk/scope';
+
+describe('NodeExperimentalHub', () => {
+ it('getCurrentHub() returns the correct hub', () => {
+ const hub = getCurrentHub();
+ expect(hub).toBeDefined();
+ expect(hub).toBeInstanceOf(NodeExperimentalHub);
+
+ const hub2 = getCurrentHub();
+ expect(hub2).toBe(hub);
+
+ const scope = hub.getScope();
+ expect(scope).toBeDefined();
+ expect(scope).toBeInstanceOf(NodeExperimentalScope);
+ });
+
+ it('hub gets correct scope on initialization', () => {
+ const hub = new NodeExperimentalHub();
+
+ const scope = hub.getScope();
+ expect(scope).toBeDefined();
+ expect(scope).toBeInstanceOf(NodeExperimentalScope);
+ });
+
+ it('pushScope() creates correct scope', () => {
+ const hub = new NodeExperimentalHub();
+
+ const scope = hub.pushScope();
+ expect(scope).toBeInstanceOf(NodeExperimentalScope);
+
+ const scope2 = hub.getScope();
+ expect(scope2).toBe(scope);
+ });
+
+ it('withScope() creates correct scope', () => {
+ const hub = new NodeExperimentalHub();
+
+ hub.withScope(scope => {
+ expect(scope).toBeInstanceOf(NodeExperimentalScope);
+ });
+ });
+});
diff --git a/packages/node-experimental/test/sdk/hubextensions.test.ts b/packages/node-experimental/test/sdk/hubextensions.test.ts
new file mode 100644
index 000000000000..c2fee6baabde
--- /dev/null
+++ b/packages/node-experimental/test/sdk/hubextensions.test.ts
@@ -0,0 +1,26 @@
+import { NodeExperimentalClient } from '../../src/sdk/client';
+import { getCurrentHub } from '../../src/sdk/hub';
+import { addTracingExtensions } from '../../src/sdk/hubextensions';
+import { getDefaultNodeExperimentalClientOptions } from '../helpers/getDefaultNodePreviewClientOptions';
+
+describe('hubextensions', () => {
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+
+ it('startTransaction is noop', () => {
+ const client = new NodeExperimentalClient(getDefaultNodeExperimentalClientOptions());
+ getCurrentHub().bindClient(client);
+ addTracingExtensions();
+
+ const mockConsole = jest.spyOn(console, 'warn').mockImplementation(() => {});
+
+ const transaction = getCurrentHub().startTransaction({ name: 'test' });
+ expect(transaction).toEqual({});
+
+ expect(mockConsole).toHaveBeenCalledTimes(1);
+ expect(mockConsole).toHaveBeenCalledWith(
+ 'startTransaction is a noop in @sentry/node-experimental. Use `startSpan` instead.',
+ );
+ });
+});
diff --git a/packages/node-experimental/test/sdk/init.test.ts b/packages/node-experimental/test/sdk/init.test.ts
index a150d61f3bf5..e220bf7e6ecd 100644
--- a/packages/node-experimental/test/sdk/init.test.ts
+++ b/packages/node-experimental/test/sdk/init.test.ts
@@ -3,6 +3,7 @@ import type { Integration } from '@sentry/types';
import * as auto from '../../src/integrations/getAutoPerformanceIntegrations';
import * as sdk from '../../src/sdk/init';
import { init } from '../../src/sdk/init';
+import { cleanupOtel } from '../helpers/mockSdkInit';
// eslint-disable-next-line no-var
declare var global: any;
@@ -31,6 +32,8 @@ describe('init()', () => {
afterEach(() => {
// @ts-expect-error - Reset the default integrations of node sdk to original
sdk.defaultIntegrations = defaultIntegrationsBackup;
+
+ cleanupOtel();
});
it("doesn't install default integrations if told not to", () => {
diff --git a/packages/node-experimental/test/sdk/otelAsyncContextStrategy.test.ts b/packages/node-experimental/test/sdk/otelAsyncContextStrategy.test.ts
new file mode 100644
index 000000000000..518d61000fee
--- /dev/null
+++ b/packages/node-experimental/test/sdk/otelAsyncContextStrategy.test.ts
@@ -0,0 +1,144 @@
+import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
+import type { Hub } from '@sentry/core';
+import { runWithAsyncContext, setAsyncContextStrategy } from '@sentry/core';
+
+import { NodeExperimentalClient } from '../../src/sdk/client';
+import { getCurrentHub } from '../../src/sdk/hub';
+import { setupOtel } from '../../src/sdk/initOtel';
+import { setOtelContextAsyncContextStrategy } from '../../src/sdk/otelAsyncContextStrategy';
+import { getDefaultNodeExperimentalClientOptions } from '../helpers/getDefaultNodePreviewClientOptions';
+import { cleanupOtel } from '../helpers/mockSdkInit';
+
+describe('otelAsyncContextStrategy', () => {
+ let provider: BasicTracerProvider | undefined;
+
+ beforeEach(() => {
+ const options = getDefaultNodeExperimentalClientOptions();
+ const client = new NodeExperimentalClient(options);
+ provider = setupOtel(client);
+ setOtelContextAsyncContextStrategy();
+ });
+
+ afterEach(() => {
+ cleanupOtel(provider);
+ });
+
+ afterAll(() => {
+ // clear the strategy
+ setAsyncContextStrategy(undefined);
+ });
+
+ test('hub scope inheritance', () => {
+ const globalHub = getCurrentHub();
+ globalHub.setExtra('a', 'b');
+
+ runWithAsyncContext(() => {
+ const hub1 = getCurrentHub();
+ expect(hub1).toEqual(globalHub);
+
+ hub1.setExtra('c', 'd');
+ expect(hub1).not.toEqual(globalHub);
+
+ runWithAsyncContext(() => {
+ const hub2 = getCurrentHub();
+ expect(hub2).toEqual(hub1);
+ expect(hub2).not.toEqual(globalHub);
+
+ hub2.setExtra('e', 'f');
+ expect(hub2).not.toEqual(hub1);
+ });
+ });
+ });
+
+ test('async hub scope inheritance', async () => {
+ async function addRandomExtra(hub: Hub, key: string): Promise {
+ return new Promise(resolve => {
+ setTimeout(() => {
+ hub.setExtra(key, Math.random());
+ resolve();
+ }, 100);
+ });
+ }
+
+ const globalHub = getCurrentHub();
+ await addRandomExtra(globalHub, 'a');
+
+ await runWithAsyncContext(async () => {
+ const hub1 = getCurrentHub();
+ expect(hub1).toEqual(globalHub);
+
+ await addRandomExtra(hub1, 'b');
+ expect(hub1).not.toEqual(globalHub);
+
+ await runWithAsyncContext(async () => {
+ const hub2 = getCurrentHub();
+ expect(hub2).toEqual(hub1);
+ expect(hub2).not.toEqual(globalHub);
+
+ await addRandomExtra(hub1, 'c');
+ expect(hub2).not.toEqual(hub1);
+ });
+ });
+ });
+
+ test('context single instance', () => {
+ const globalHub = getCurrentHub();
+ runWithAsyncContext(() => {
+ expect(globalHub).not.toBe(getCurrentHub());
+ });
+ });
+
+ test('context within a context not reused', () => {
+ runWithAsyncContext(() => {
+ const hub1 = getCurrentHub();
+ runWithAsyncContext(() => {
+ const hub2 = getCurrentHub();
+ expect(hub1).not.toBe(hub2);
+ });
+ });
+ });
+
+ test('context within a context reused when requested', () => {
+ runWithAsyncContext(() => {
+ const hub1 = getCurrentHub();
+ runWithAsyncContext(
+ () => {
+ const hub2 = getCurrentHub();
+ expect(hub1).toBe(hub2);
+ },
+ { reuseExisting: true },
+ );
+ });
+ });
+
+ test('concurrent hub contexts', done => {
+ let d1done = false;
+ let d2done = false;
+
+ runWithAsyncContext(() => {
+ const hub = getCurrentHub();
+ hub.getStack().push({ client: 'process' } as any);
+ expect(hub.getStack()[1]).toEqual({ client: 'process' });
+ // Just in case so we don't have to worry which one finishes first
+ // (although it always should be d2)
+ setTimeout(() => {
+ d1done = true;
+ if (d2done) {
+ done();
+ }
+ });
+ });
+
+ runWithAsyncContext(() => {
+ const hub = getCurrentHub();
+ hub.getStack().push({ client: 'local' } as any);
+ expect(hub.getStack()[1]).toEqual({ client: 'local' });
+ setTimeout(() => {
+ d2done = true;
+ if (d1done) {
+ done();
+ }
+ });
+ });
+ });
+});
diff --git a/packages/node-experimental/test/sdk/scope.test.ts b/packages/node-experimental/test/sdk/scope.test.ts
new file mode 100644
index 000000000000..7d8d772abd8c
--- /dev/null
+++ b/packages/node-experimental/test/sdk/scope.test.ts
@@ -0,0 +1,438 @@
+import { makeSession } from '@sentry/core';
+import type { Breadcrumb } from '@sentry/types';
+
+import {
+ OTEL_ATTR_BREADCRUMB_CATEGORY,
+ OTEL_ATTR_BREADCRUMB_DATA,
+ OTEL_ATTR_BREADCRUMB_EVENT_ID,
+ OTEL_ATTR_BREADCRUMB_LEVEL,
+ OTEL_ATTR_BREADCRUMB_TYPE,
+} from '../../src/constants';
+import { setSpanParent } from '../../src/opentelemetry/spanData';
+import { NodeExperimentalScope } from '../../src/sdk/scope';
+import { createSpan } from '../helpers/createSpan';
+import * as GetActiveSpan from './../../src/utils/getActiveSpan';
+
+describe('NodeExperimentalScope', () => {
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+
+ it('clone() correctly clones the scope', () => {
+ const scope = new NodeExperimentalScope();
+
+ scope['_breadcrumbs'] = [{ message: 'test' }];
+ scope['_tags'] = { tag: 'bar' };
+ scope['_extra'] = { extra: 'bar' };
+ scope['_contexts'] = { os: { name: 'Linux' } };
+ scope['_user'] = { id: '123' };
+ scope['_level'] = 'warning';
+ // we don't care about _span
+ scope['_session'] = makeSession({ sid: '123' });
+ // we don't care about transactionName
+ scope['_fingerprint'] = ['foo'];
+ scope['_eventProcessors'] = [() => ({})];
+ scope['_requestSession'] = { status: 'ok' };
+ scope['_attachments'] = [{ data: '123', filename: 'test.txt' }];
+ scope['_sdkProcessingMetadata'] = { sdk: 'bar' };
+
+ const scope2 = NodeExperimentalScope.clone(scope);
+
+ expect(scope2).toBeInstanceOf(NodeExperimentalScope);
+ expect(scope2).not.toBe(scope);
+
+ // Ensure everything is correctly cloned
+ expect(scope2['_breadcrumbs']).toEqual(scope['_breadcrumbs']);
+ expect(scope2['_tags']).toEqual(scope['_tags']);
+ expect(scope2['_extra']).toEqual(scope['_extra']);
+ expect(scope2['_contexts']).toEqual(scope['_contexts']);
+ expect(scope2['_user']).toEqual(scope['_user']);
+ expect(scope2['_level']).toEqual(scope['_level']);
+ expect(scope2['_session']).toEqual(scope['_session']);
+ expect(scope2['_fingerprint']).toEqual(scope['_fingerprint']);
+ expect(scope2['_eventProcessors']).toEqual(scope['_eventProcessors']);
+ expect(scope2['_requestSession']).toEqual(scope['_requestSession']);
+ expect(scope2['_attachments']).toEqual(scope['_attachments']);
+ expect(scope2['_sdkProcessingMetadata']).toEqual(scope['_sdkProcessingMetadata']);
+ expect(scope2['_propagationContext']).toEqual(scope['_propagationContext']);
+
+ // Ensure things are not copied by reference
+ expect(scope2['_breadcrumbs']).not.toBe(scope['_breadcrumbs']);
+ expect(scope2['_tags']).not.toBe(scope['_tags']);
+ expect(scope2['_extra']).not.toBe(scope['_extra']);
+ expect(scope2['_contexts']).not.toBe(scope['_contexts']);
+ expect(scope2['_eventProcessors']).not.toBe(scope['_eventProcessors']);
+ expect(scope2['_attachments']).not.toBe(scope['_attachments']);
+ expect(scope2['_sdkProcessingMetadata']).not.toBe(scope['_sdkProcessingMetadata']);
+ expect(scope2['_propagationContext']).not.toBe(scope['_propagationContext']);
+
+ // These are actually copied by reference
+ expect(scope2['_user']).toBe(scope['_user']);
+ expect(scope2['_session']).toBe(scope['_session']);
+ expect(scope2['_requestSession']).toBe(scope['_requestSession']);
+ expect(scope2['_fingerprint']).toBe(scope['_fingerprint']);
+ });
+
+ it('clone() works without existing scope', () => {
+ const scope = NodeExperimentalScope.clone(undefined);
+
+ expect(scope).toBeInstanceOf(NodeExperimentalScope);
+ });
+
+ it('getSpan returns undefined', () => {
+ const scope = new NodeExperimentalScope();
+
+ // Pretend we have a _span set
+ scope['_span'] = {} as any;
+
+ expect(scope.getSpan()).toBeUndefined();
+ });
+
+ it('setSpan is a noop', () => {
+ const scope = new NodeExperimentalScope();
+
+ scope.setSpan({} as any);
+
+ expect(scope['_span']).toBeUndefined();
+ });
+
+ describe('addBreadcrumb', () => {
+ it('adds to scope if no root span is found', () => {
+ jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(undefined);
+
+ const scope = new NodeExperimentalScope();
+ const breadcrumb: Breadcrumb = { message: 'test' };
+
+ const now = Date.now();
+ jest.useFakeTimers();
+ jest.setSystemTime(now);
+
+ scope.addBreadcrumb(breadcrumb);
+
+ expect(scope['_breadcrumbs']).toEqual([{ message: 'test', timestamp: now / 1000 }]);
+ });
+
+ it('adds to scope if no root span is found & uses given timestamp', () => {
+ jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(undefined);
+
+ const scope = new NodeExperimentalScope();
+ const breadcrumb: Breadcrumb = { message: 'test', timestamp: 1234 };
+
+ scope.addBreadcrumb(breadcrumb);
+
+ expect(scope['_breadcrumbs']).toEqual([breadcrumb]);
+ });
+
+ it('adds to root span if found', () => {
+ const span = createSpan();
+ jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(span);
+
+ const scope = new NodeExperimentalScope();
+ const breadcrumb: Breadcrumb = { message: 'test' };
+
+ const now = Date.now();
+ jest.useFakeTimers();
+ jest.setSystemTime(now);
+
+ scope.addBreadcrumb(breadcrumb);
+
+ expect(scope['_breadcrumbs']).toEqual([]);
+ expect(span.events).toEqual([
+ expect.objectContaining({
+ name: 'test',
+ time: [Math.floor(now / 1000), (now % 1000) * 1_000_000],
+ attributes: {},
+ }),
+ ]);
+ });
+
+ it('adds to root span if found & uses given timestamp', () => {
+ const span = createSpan();
+ jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(span);
+
+ const scope = new NodeExperimentalScope();
+ const breadcrumb: Breadcrumb = { timestamp: 12345, message: 'test' };
+
+ scope.addBreadcrumb(breadcrumb);
+
+ expect(scope['_breadcrumbs']).toEqual([]);
+ expect(span.events).toEqual([
+ expect.objectContaining({
+ name: 'test',
+ time: [12345, 0],
+ attributes: {},
+ }),
+ ]);
+ });
+
+ it('adds many breadcrumbs to root span if found', () => {
+ const span = createSpan();
+ jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(span);
+
+ const scope = new NodeExperimentalScope();
+ const breadcrumb1: Breadcrumb = { timestamp: 12345, message: 'test1' };
+ const breadcrumb2: Breadcrumb = { timestamp: 5678, message: 'test2' };
+ const breadcrumb3: Breadcrumb = { timestamp: 9101112, message: 'test3' };
+
+ scope.addBreadcrumb(breadcrumb1);
+ scope.addBreadcrumb(breadcrumb2);
+ scope.addBreadcrumb(breadcrumb3);
+
+ expect(scope['_breadcrumbs']).toEqual([]);
+ expect(span.events).toEqual([
+ expect.objectContaining({
+ name: 'test1',
+ time: [12345, 0],
+ attributes: {},
+ }),
+ expect.objectContaining({
+ name: 'test2',
+ time: [5678, 0],
+ attributes: {},
+ }),
+ expect.objectContaining({
+ name: 'test3',
+ time: [9101112, 0],
+ attributes: {},
+ }),
+ ]);
+ });
+
+ it('adds to root span if found & no message is given', () => {
+ const span = createSpan();
+ jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(span);
+
+ const scope = new NodeExperimentalScope();
+ const breadcrumb: Breadcrumb = { timestamp: 12345 };
+
+ scope.addBreadcrumb(breadcrumb);
+
+ expect(scope['_breadcrumbs']).toEqual([]);
+ expect(span.events).toEqual([
+ expect.objectContaining({
+ name: '',
+ time: [12345, 0],
+ attributes: {},
+ }),
+ ]);
+ });
+
+ it('adds to root span with full attributes', () => {
+ const span = createSpan();
+ jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(span);
+
+ const scope = new NodeExperimentalScope();
+ const breadcrumb: Breadcrumb = {
+ timestamp: 12345,
+ message: 'test',
+ data: { nested: { indeed: true } },
+ level: 'info',
+ category: 'test-category',
+ type: 'test-type',
+ event_id: 'test-event-id',
+ };
+
+ scope.addBreadcrumb(breadcrumb);
+
+ expect(scope['_breadcrumbs']).toEqual([]);
+ expect(span.events).toEqual([
+ expect.objectContaining({
+ name: 'test',
+ time: [12345, 0],
+ attributes: {
+ [OTEL_ATTR_BREADCRUMB_DATA]: JSON.stringify({ nested: { indeed: true } }),
+ [OTEL_ATTR_BREADCRUMB_TYPE]: 'test-type',
+ [OTEL_ATTR_BREADCRUMB_LEVEL]: 'info',
+ [OTEL_ATTR_BREADCRUMB_EVENT_ID]: 'test-event-id',
+ [OTEL_ATTR_BREADCRUMB_CATEGORY]: 'test-category',
+ },
+ }),
+ ]);
+ });
+
+ it('adds to root span with empty data', () => {
+ const span = createSpan();
+ jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(span);
+
+ const scope = new NodeExperimentalScope();
+ const breadcrumb: Breadcrumb = { timestamp: 12345, message: 'test', data: {} };
+
+ scope.addBreadcrumb(breadcrumb);
+
+ expect(scope['_breadcrumbs']).toEqual([]);
+ expect(span.events).toEqual([
+ expect.objectContaining({
+ name: 'test',
+ time: [12345, 0],
+ attributes: {},
+ }),
+ ]);
+ });
+ });
+
+ describe('_getBreadcrumbs', () => {
+ it('gets from scope if no root span is found', () => {
+ jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(undefined);
+
+ const scope = new NodeExperimentalScope();
+ const breadcrumbs: Breadcrumb[] = [
+ { message: 'test1', timestamp: 1234 },
+ { message: 'test2', timestamp: 12345 },
+ { message: 'test3', timestamp: 12346 },
+ ];
+ scope['_breadcrumbs'] = breadcrumbs;
+
+ expect(scope['_getBreadcrumbs']()).toEqual(breadcrumbs);
+ });
+
+ it('gets from root span if found', () => {
+ const span = createSpan();
+ jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(span);
+
+ const scope = new NodeExperimentalScope();
+
+ const now = Date.now();
+
+ span.addEvent('basic event', now);
+ span.addEvent('breadcrumb event', {}, now + 1000);
+ span.addEvent(
+ 'breadcrumb event 2',
+ {
+ [OTEL_ATTR_BREADCRUMB_DATA]: JSON.stringify({ nested: { indeed: true } }),
+ [OTEL_ATTR_BREADCRUMB_TYPE]: 'test-type',
+ [OTEL_ATTR_BREADCRUMB_LEVEL]: 'info',
+ [OTEL_ATTR_BREADCRUMB_EVENT_ID]: 'test-event-id',
+ [OTEL_ATTR_BREADCRUMB_CATEGORY]: 'test-category',
+ },
+ now + 3000,
+ );
+ span.addEvent(
+ 'breadcrumb event invalid JSON data',
+ {
+ [OTEL_ATTR_BREADCRUMB_DATA]: 'this is not JSON...',
+ },
+ now + 2000,
+ );
+
+ expect(scope['_getBreadcrumbs']()).toEqual([
+ { message: 'basic event', timestamp: now / 1000 },
+ { message: 'breadcrumb event', timestamp: now / 1000 + 1 },
+ {
+ message: 'breadcrumb event 2',
+ timestamp: now / 1000 + 3,
+ data: { nested: { indeed: true } },
+ level: 'info',
+ event_id: 'test-event-id',
+ category: 'test-category',
+ type: 'test-type',
+ },
+ { message: 'breadcrumb event invalid JSON data', timestamp: now / 1000 + 2 },
+ ]);
+ });
+
+ it('gets from spans up the parent chain if found', () => {
+ const span = createSpan();
+ const parentSpan = createSpan();
+ const rootSpan = createSpan();
+ jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(span);
+
+ setSpanParent(span, parentSpan);
+ setSpanParent(parentSpan, rootSpan);
+
+ const scope = new NodeExperimentalScope();
+
+ const now = Date.now();
+
+ span.addEvent('basic event', now);
+ parentSpan.addEvent('parent breadcrumb event', {}, now + 1000);
+ span.addEvent(
+ 'breadcrumb event 2',
+ {
+ [OTEL_ATTR_BREADCRUMB_DATA]: JSON.stringify({ nested: true }),
+ },
+ now + 3000,
+ );
+ rootSpan.addEvent(
+ 'breadcrumb event invalid JSON data',
+ {
+ [OTEL_ATTR_BREADCRUMB_DATA]: 'this is not JSON...',
+ },
+ now + 2000,
+ );
+
+ expect(scope['_getBreadcrumbs']()).toEqual([
+ { message: 'basic event', timestamp: now / 1000 },
+ { message: 'breadcrumb event 2', timestamp: now / 1000 + 3, data: { nested: true } },
+ { message: 'parent breadcrumb event', timestamp: now / 1000 + 1 },
+ { message: 'breadcrumb event invalid JSON data', timestamp: now / 1000 + 2 },
+ ]);
+ });
+
+ it('combines scope & span breadcrumbs if both exist', () => {
+ const span = createSpan();
+ jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(span);
+
+ const scope = new NodeExperimentalScope();
+
+ const breadcrumbs: Breadcrumb[] = [
+ { message: 'test1', timestamp: 1234 },
+ { message: 'test2', timestamp: 12345 },
+ { message: 'test3', timestamp: 12346 },
+ ];
+ scope['_breadcrumbs'] = breadcrumbs;
+
+ const now = Date.now();
+
+ span.addEvent('basic event', now);
+ span.addEvent('breadcrumb event', {}, now + 1000);
+
+ expect(scope['_getBreadcrumbs']()).toEqual([
+ { message: 'test1', timestamp: 1234 },
+ { message: 'test2', timestamp: 12345 },
+ { message: 'test3', timestamp: 12346 },
+ { message: 'basic event', timestamp: now / 1000 },
+ { message: 'breadcrumb event', timestamp: now / 1000 + 1 },
+ ]);
+ });
+
+ it('gets from activeSpan if defined', () => {
+ const span = createSpan();
+ jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(span);
+
+ const scope = new NodeExperimentalScope();
+
+ const now = Date.now();
+
+ span.addEvent('basic event', now);
+ span.addEvent('breadcrumb event', {}, now + 1000);
+ span.addEvent(
+ 'breadcrumb event 2',
+ {
+ [OTEL_ATTR_BREADCRUMB_DATA]: JSON.stringify({ nested: { indeed: true } }),
+ [OTEL_ATTR_BREADCRUMB_TYPE]: 'test-type',
+ [OTEL_ATTR_BREADCRUMB_LEVEL]: 'info',
+ [OTEL_ATTR_BREADCRUMB_EVENT_ID]: 'test-event-id',
+ [OTEL_ATTR_BREADCRUMB_CATEGORY]: 'test-category',
+ },
+ now + 3000,
+ );
+ span.addEvent(
+ 'breadcrumb event invalid JSON data',
+ {
+ [OTEL_ATTR_BREADCRUMB_DATA]: 'this is not JSON...',
+ },
+ now + 2000,
+ );
+
+ const activeSpan = createSpan();
+ activeSpan.addEvent('event 1', now);
+ activeSpan.addEvent('event 2', {}, now + 1000);
+ scope.activeSpan = activeSpan;
+
+ expect(scope['_getBreadcrumbs']()).toEqual([
+ { message: 'event 1', timestamp: now / 1000 },
+ { message: 'event 2', timestamp: now / 1000 + 1 },
+ ]);
+ });
+ });
+});
diff --git a/packages/node-experimental/test/sdk/trace.test.ts b/packages/node-experimental/test/sdk/trace.test.ts
index c53606140fa1..e141372552a6 100644
--- a/packages/node-experimental/test/sdk/trace.test.ts
+++ b/packages/node-experimental/test/sdk/trace.test.ts
@@ -1,63 +1,78 @@
-import { Span, Transaction } from '@sentry/core';
+import { context, trace, TraceFlags } from '@opentelemetry/api';
+import type { Span } from '@opentelemetry/sdk-trace-base';
+import type { PropagationContext } from '@sentry/types';
import * as Sentry from '../../src';
-import { mockSdkInit } from '../helpers/mockSdkInit';
+import {
+ OTEL_ATTR_OP,
+ OTEL_ATTR_ORIGIN,
+ OTEL_ATTR_SENTRY_SAMPLE_RATE,
+ OTEL_ATTR_SOURCE,
+ SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY,
+} from '../../src/constants';
+import { getSpanMetadata } from '../../src/opentelemetry/spanData';
+import { getActiveSpan } from '../../src/utils/getActiveSpan';
+import { cleanupOtel, mockSdkInit } from '../helpers/mockSdkInit';
describe('trace', () => {
beforeEach(() => {
mockSdkInit({ enableTracing: true });
});
+ afterEach(() => {
+ cleanupOtel();
+ });
+
describe('startSpan', () => {
it('works with a sync callback', () => {
const spans: Span[] = [];
- expect(Sentry.getActiveSpan()).toEqual(undefined);
+ expect(getActiveSpan()).toEqual(undefined);
- Sentry.startSpan({ name: 'outer' }, outerSpan => {
+ const res = Sentry.startSpan({ name: 'outer' }, outerSpan => {
expect(outerSpan).toBeDefined();
spans.push(outerSpan!);
expect(outerSpan?.name).toEqual('outer');
- expect(outerSpan).toBeInstanceOf(Transaction);
- expect(Sentry.getActiveSpan()).toEqual(outerSpan);
+ expect(getActiveSpan()).toEqual(outerSpan);
Sentry.startSpan({ name: 'inner' }, innerSpan => {
expect(innerSpan).toBeDefined();
spans.push(innerSpan!);
- expect(innerSpan?.description).toEqual('inner');
- expect(innerSpan).toBeInstanceOf(Span);
- expect(innerSpan).not.toBeInstanceOf(Transaction);
- expect(Sentry.getActiveSpan()).toEqual(innerSpan);
+ expect(innerSpan?.name).toEqual('inner');
+ expect(getActiveSpan()).toEqual(innerSpan);
});
+
+ return 'test value';
});
- expect(Sentry.getActiveSpan()).toEqual(undefined);
+ expect(res).toEqual('test value');
+
+ expect(getActiveSpan()).toEqual(undefined);
expect(spans).toHaveLength(2);
const [outerSpan, innerSpan] = spans;
- expect((outerSpan as Transaction).name).toEqual('outer');
- expect(innerSpan.description).toEqual('inner');
+ expect(outerSpan.name).toEqual('outer');
+ expect(innerSpan.name).toEqual('inner');
- expect(outerSpan.endTimestamp).toEqual(expect.any(Number));
- expect(innerSpan.endTimestamp).toEqual(expect.any(Number));
+ expect(outerSpan.endTime).not.toEqual([0, 0]);
+ expect(innerSpan.endTime).not.toEqual([0, 0]);
});
it('works with an async callback', async () => {
const spans: Span[] = [];
- expect(Sentry.getActiveSpan()).toEqual(undefined);
+ expect(getActiveSpan()).toEqual(undefined);
- await Sentry.startSpan({ name: 'outer' }, async outerSpan => {
+ const res = await Sentry.startSpan({ name: 'outer' }, async outerSpan => {
expect(outerSpan).toBeDefined();
spans.push(outerSpan!);
await new Promise(resolve => setTimeout(resolve, 10));
expect(outerSpan?.name).toEqual('outer');
- expect(outerSpan).toBeInstanceOf(Transaction);
- expect(Sentry.getActiveSpan()).toEqual(outerSpan);
+ expect(getActiveSpan()).toEqual(outerSpan);
await Sentry.startSpan({ name: 'inner' }, async innerSpan => {
expect(innerSpan).toBeDefined();
@@ -65,46 +80,45 @@ describe('trace', () => {
await new Promise(resolve => setTimeout(resolve, 10));
- expect(innerSpan?.description).toEqual('inner');
- expect(innerSpan).toBeInstanceOf(Span);
- expect(innerSpan).not.toBeInstanceOf(Transaction);
- expect(Sentry.getActiveSpan()).toEqual(innerSpan);
+ expect(innerSpan?.name).toEqual('inner');
+ expect(getActiveSpan()).toEqual(innerSpan);
});
+
+ return 'test value';
});
- expect(Sentry.getActiveSpan()).toEqual(undefined);
+ expect(res).toEqual('test value');
+
+ expect(getActiveSpan()).toEqual(undefined);
expect(spans).toHaveLength(2);
const [outerSpan, innerSpan] = spans;
- expect((outerSpan as Transaction).name).toEqual('outer');
- expect(innerSpan.description).toEqual('inner');
+ expect(outerSpan.name).toEqual('outer');
+ expect(innerSpan.name).toEqual('inner');
- expect(outerSpan.endTimestamp).toEqual(expect.any(Number));
- expect(innerSpan.endTimestamp).toEqual(expect.any(Number));
+ expect(outerSpan.endTime).not.toEqual([0, 0]);
+ expect(innerSpan.endTime).not.toEqual([0, 0]);
});
it('works with multiple parallel calls', () => {
const spans1: Span[] = [];
const spans2: Span[] = [];
- expect(Sentry.getActiveSpan()).toEqual(undefined);
+ expect(getActiveSpan()).toEqual(undefined);
Sentry.startSpan({ name: 'outer' }, outerSpan => {
expect(outerSpan).toBeDefined();
spans1.push(outerSpan!);
expect(outerSpan?.name).toEqual('outer');
- expect(outerSpan).toBeInstanceOf(Transaction);
- expect(Sentry.getActiveSpan()).toEqual(outerSpan);
+ expect(getActiveSpan()).toEqual(outerSpan);
Sentry.startSpan({ name: 'inner' }, innerSpan => {
expect(innerSpan).toBeDefined();
spans1.push(innerSpan!);
- expect(innerSpan?.description).toEqual('inner');
- expect(innerSpan).toBeInstanceOf(Span);
- expect(innerSpan).not.toBeInstanceOf(Transaction);
- expect(Sentry.getActiveSpan()).toEqual(innerSpan);
+ expect(innerSpan?.name).toEqual('inner');
+ expect(getActiveSpan()).toEqual(innerSpan);
});
});
@@ -113,24 +127,58 @@ describe('trace', () => {
spans2.push(outerSpan!);
expect(outerSpan?.name).toEqual('outer2');
- expect(outerSpan).toBeInstanceOf(Transaction);
- expect(Sentry.getActiveSpan()).toEqual(outerSpan);
+ expect(getActiveSpan()).toEqual(outerSpan);
Sentry.startSpan({ name: 'inner2' }, innerSpan => {
expect(innerSpan).toBeDefined();
spans2.push(innerSpan!);
- expect(innerSpan?.description).toEqual('inner2');
- expect(innerSpan).toBeInstanceOf(Span);
- expect(innerSpan).not.toBeInstanceOf(Transaction);
- expect(Sentry.getActiveSpan()).toEqual(innerSpan);
+ expect(innerSpan?.name).toEqual('inner2');
+ expect(getActiveSpan()).toEqual(innerSpan);
});
});
- expect(Sentry.getActiveSpan()).toEqual(undefined);
+ expect(getActiveSpan()).toEqual(undefined);
expect(spans1).toHaveLength(2);
expect(spans2).toHaveLength(2);
});
+
+ it('allows to pass context arguments', () => {
+ Sentry.startSpan(
+ {
+ name: 'outer',
+ },
+ span => {
+ expect(span).toBeDefined();
+ expect(span?.attributes).toEqual({
+ [OTEL_ATTR_SENTRY_SAMPLE_RATE]: 1,
+ });
+
+ expect(getSpanMetadata(span!)).toEqual(undefined);
+ },
+ );
+
+ Sentry.startSpan(
+ {
+ name: 'outer',
+ op: 'my-op',
+ origin: 'auto.test.origin',
+ source: 'task',
+ metadata: { requestPath: 'test-path' },
+ },
+ span => {
+ expect(span).toBeDefined();
+ expect(span?.attributes).toEqual({
+ [OTEL_ATTR_SOURCE]: 'task',
+ [OTEL_ATTR_ORIGIN]: 'auto.test.origin',
+ [OTEL_ATTR_OP]: 'my-op',
+ [OTEL_ATTR_SENTRY_SAMPLE_RATE]: 1,
+ });
+
+ expect(getSpanMetadata(span!)).toEqual({ requestPath: 'test-path' });
+ },
+ );
+ });
});
describe('startInactiveSpan', () => {
@@ -138,36 +186,389 @@ describe('trace', () => {
const span = Sentry.startInactiveSpan({ name: 'test' });
expect(span).toBeDefined();
- expect(span).toBeInstanceOf(Transaction);
expect(span?.name).toEqual('test');
- expect(span?.endTimestamp).toBeUndefined();
- expect(Sentry.getActiveSpan()).toBeUndefined();
+ expect(span?.endTime).toEqual([0, 0]);
+ expect(getActiveSpan()).toBeUndefined();
- span?.finish();
+ span?.end();
- expect(span?.endTimestamp).toEqual(expect.any(Number));
- expect(Sentry.getActiveSpan()).toBeUndefined();
+ expect(span?.endTime).not.toEqual([0, 0]);
+ expect(getActiveSpan()).toBeUndefined();
});
it('works as a child span', () => {
Sentry.startSpan({ name: 'outer' }, outerSpan => {
expect(outerSpan).toBeDefined();
- expect(Sentry.getActiveSpan()).toEqual(outerSpan);
+ expect(getActiveSpan()).toEqual(outerSpan);
const innerSpan = Sentry.startInactiveSpan({ name: 'test' });
expect(innerSpan).toBeDefined();
- expect(innerSpan).toBeInstanceOf(Span);
- expect(innerSpan).not.toBeInstanceOf(Transaction);
- expect(innerSpan?.description).toEqual('test');
- expect(innerSpan?.endTimestamp).toBeUndefined();
- expect(Sentry.getActiveSpan()).toEqual(outerSpan);
+ expect(innerSpan?.name).toEqual('test');
+ expect(innerSpan?.endTime).toEqual([0, 0]);
+ expect(getActiveSpan()).toEqual(outerSpan);
+
+ innerSpan?.end();
+
+ expect(innerSpan?.endTime).not.toEqual([0, 0]);
+ expect(getActiveSpan()).toEqual(outerSpan);
+ });
+ });
+
+ it('allows to pass context arguments', () => {
+ const span = Sentry.startInactiveSpan({
+ name: 'outer',
+ });
+
+ expect(span).toBeDefined();
+ expect(span?.attributes).toEqual({
+ [OTEL_ATTR_SENTRY_SAMPLE_RATE]: 1,
+ });
+
+ expect(getSpanMetadata(span!)).toEqual(undefined);
+
+ const span2 = Sentry.startInactiveSpan({
+ name: 'outer',
+ op: 'my-op',
+ origin: 'auto.test.origin',
+ source: 'task',
+ metadata: { requestPath: 'test-path' },
+ });
+
+ expect(span2).toBeDefined();
+ expect(span2?.attributes).toEqual({
+ [OTEL_ATTR_SENTRY_SAMPLE_RATE]: 1,
+ [OTEL_ATTR_SOURCE]: 'task',
+ [OTEL_ATTR_ORIGIN]: 'auto.test.origin',
+ [OTEL_ATTR_OP]: 'my-op',
+ });
+
+ expect(getSpanMetadata(span2!)).toEqual({ requestPath: 'test-path' });
+ });
+ });
+});
+
+describe('trace (tracing disabled)', () => {
+ beforeEach(() => {
+ mockSdkInit({ enableTracing: false });
+ });
+
+ afterEach(() => {
+ cleanupOtel();
+ });
+
+ it('startSpan calls callback without span', () => {
+ const val = Sentry.startSpan({ name: 'outer' }, outerSpan => {
+ expect(outerSpan).toBeUndefined();
+
+ return 'test value';
+ });
+
+ expect(val).toEqual('test value');
+ });
+
+ it('startInactiveSpan returns undefined', () => {
+ const span = Sentry.startInactiveSpan({ name: 'test' });
+
+ expect(span).toBeUndefined();
+ });
+});
+
+describe('trace (sampling)', () => {
+ afterEach(() => {
+ cleanupOtel();
+ jest.clearAllMocks();
+ });
+
+ it('samples with a tracesSampleRate, when Math.random() > tracesSampleRate', () => {
+ jest.spyOn(Math, 'random').mockImplementation(() => 0.6);
+
+ mockSdkInit({ tracesSampleRate: 0.5 });
+
+ Sentry.startSpan({ name: 'outer' }, outerSpan => {
+ expect(outerSpan).toBeUndefined();
+
+ Sentry.startSpan({ name: 'inner' }, innerSpan => {
+ expect(innerSpan).toBeUndefined();
+ });
+ });
+ });
+
+ it('samples with a tracesSampleRate, when Math.random() < tracesSampleRate', () => {
+ jest.spyOn(Math, 'random').mockImplementation(() => 0.4);
+
+ mockSdkInit({ tracesSampleRate: 0.5 });
+
+ Sentry.startSpan({ name: 'outer' }, outerSpan => {
+ expect(outerSpan).toBeDefined();
+ expect(outerSpan?.isRecording()).toBe(true);
+ // All fields are empty for NonRecordingSpan
+ expect(outerSpan?.name).toBe('outer');
+
+ Sentry.startSpan({ name: 'inner' }, innerSpan => {
+ expect(innerSpan).toBeDefined();
+ expect(innerSpan?.isRecording()).toBe(true);
+ expect(innerSpan?.name).toBe('inner');
+ });
+ });
+ });
+
+ it('positive parent sampling takes precedence over tracesSampleRate', () => {
+ jest.spyOn(Math, 'random').mockImplementation(() => 0.6);
+
+ mockSdkInit({ tracesSampleRate: 1 });
+
+ // This will def. be sampled because of the tracesSampleRate
+ Sentry.startSpan({ name: 'outer' }, outerSpan => {
+ expect(outerSpan).toBeDefined();
+ expect(outerSpan?.isRecording()).toBe(true);
+ expect(outerSpan?.name).toBe('outer');
+
+ // Now let's mutate the tracesSampleRate so that the next entry _should_ not be sampled
+ // but it will because of parent sampling
+ const client = Sentry.getCurrentHub().getClient();
+ client!.getOptions().tracesSampleRate = 0.5;
+
+ Sentry.startSpan({ name: 'inner' }, innerSpan => {
+ expect(innerSpan).toBeDefined();
+ expect(innerSpan?.isRecording()).toBe(true);
+ expect(innerSpan?.name).toBe('inner');
+ });
+ });
+ });
+
+ it('negative parent sampling takes precedence over tracesSampleRate', () => {
+ jest.spyOn(Math, 'random').mockImplementation(() => 0.6);
+
+ mockSdkInit({ tracesSampleRate: 0.5 });
+
+ // This will def. be sampled because of the tracesSampleRate
+ Sentry.startSpan({ name: 'outer' }, outerSpan => {
+ expect(outerSpan).toBeUndefined();
+
+ // Now let's mutate the tracesSampleRate so that the next entry _should_ not be sampled
+ // but it will because of parent sampling
+ const client = Sentry.getCurrentHub().getClient();
+ client!.getOptions().tracesSampleRate = 1;
+
+ Sentry.startSpan({ name: 'inner' }, innerSpan => {
+ expect(innerSpan).toBeUndefined();
+ });
+ });
+ });
+
+ it('positive remote parent sampling takes precedence over tracesSampleRate', () => {
+ jest.spyOn(Math, 'random').mockImplementation(() => 0.6);
+
+ mockSdkInit({ tracesSampleRate: 0.5 });
+
+ const traceId = 'd4cda95b652f4a1592b449d5929fda1b';
+ const parentSpanId = '6e0c63257de34c92';
+
+ const spanContext = {
+ traceId,
+ spanId: parentSpanId,
+ sampled: true,
+ isRemote: true,
+ traceFlags: TraceFlags.SAMPLED,
+ };
+
+ const propagationContext: PropagationContext = {
+ traceId,
+ sampled: true,
+ parentSpanId,
+ spanId: '6e0c63257de34c93',
+ };
+
+ // We simulate the correct context we'd normally get from the SentryPropagator
+ context.with(
+ trace.setSpanContext(
+ context.active().setValue(SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY, propagationContext),
+ spanContext,
+ ),
+ () => {
+ // This will def. be sampled because of the tracesSampleRate
+ Sentry.startSpan({ name: 'outer' }, outerSpan => {
+ expect(outerSpan).toBeDefined();
+ expect(outerSpan?.isRecording()).toBe(true);
+ expect(outerSpan?.name).toBe('outer');
+ });
+ },
+ );
+ });
+
+ it('negative remote parent sampling takes precedence over tracesSampleRate', () => {
+ jest.spyOn(Math, 'random').mockImplementation(() => 0.6);
+
+ mockSdkInit({ tracesSampleRate: 0.5 });
+
+ const traceId = 'd4cda95b652f4a1592b449d5929fda1b';
+ const parentSpanId = '6e0c63257de34c92';
+
+ const spanContext = {
+ traceId,
+ spanId: parentSpanId,
+ sampled: false,
+ isRemote: true,
+ traceFlags: TraceFlags.NONE,
+ };
+
+ const propagationContext: PropagationContext = {
+ traceId,
+ sampled: false,
+ parentSpanId,
+ spanId: '6e0c63257de34c93',
+ };
+
+ // We simulate the correct context we'd normally get from the SentryPropagator
+ context.with(
+ trace.setSpanContext(
+ context.active().setValue(SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY, propagationContext),
+ spanContext,
+ ),
+ () => {
+ // This will def. be sampled because of the tracesSampleRate
+ Sentry.startSpan({ name: 'outer' }, outerSpan => {
+ expect(outerSpan).toBeUndefined();
+ });
+ },
+ );
+ });
+
+ it('samples with a tracesSampler returning a boolean', () => {
+ let tracesSamplerResponse: boolean = true;
+
+ const tracesSampler = jest.fn(() => {
+ return tracesSamplerResponse;
+ });
+
+ mockSdkInit({ tracesSampler });
+
+ Sentry.startSpan({ name: 'outer' }, outerSpan => {
+ expect(outerSpan).toBeDefined();
+ });
+
+ expect(tracesSampler).toBeCalledTimes(1);
+ expect(tracesSampler).toHaveBeenLastCalledWith({
+ parentSampled: undefined,
+ transactionContext: { name: 'outer', parentSampled: undefined },
+ });
+
+ // Now return `false`, it should not sample
+ tracesSamplerResponse = false;
- innerSpan?.finish();
+ Sentry.startSpan({ name: 'outer2' }, outerSpan => {
+ expect(outerSpan).toBeUndefined();
- expect(innerSpan?.endTimestamp).toEqual(expect.any(Number));
- expect(Sentry.getActiveSpan()).toEqual(outerSpan);
+ Sentry.startSpan({ name: 'inner2' }, outerSpan => {
+ expect(outerSpan).toBeUndefined();
});
});
+
+ expect(tracesSampler).toHaveBeenCalledTimes(3);
+ expect(tracesSampler).toHaveBeenLastCalledWith({
+ parentSampled: false,
+ transactionContext: { name: 'inner2', parentSampled: false },
+ });
+ });
+
+ it('samples with a tracesSampler returning a number', () => {
+ jest.spyOn(Math, 'random').mockImplementation(() => 0.6);
+
+ let tracesSamplerResponse: number = 1;
+
+ const tracesSampler = jest.fn(() => {
+ return tracesSamplerResponse;
+ });
+
+ mockSdkInit({ tracesSampler });
+
+ Sentry.startSpan({ name: 'outer' }, outerSpan => {
+ expect(outerSpan).toBeDefined();
+ });
+
+ expect(tracesSampler).toBeCalledTimes(1);
+ expect(tracesSampler).toHaveBeenLastCalledWith({
+ parentSampled: undefined,
+ transactionContext: { name: 'outer', parentSampled: undefined },
+ });
+
+ // Now return `0`, it should not sample
+ tracesSamplerResponse = 0;
+
+ Sentry.startSpan({ name: 'outer2' }, outerSpan => {
+ expect(outerSpan).toBeUndefined();
+
+ Sentry.startSpan({ name: 'inner2' }, outerSpan => {
+ expect(outerSpan).toBeUndefined();
+ });
+ });
+
+ expect(tracesSampler).toHaveBeenCalledTimes(3);
+ expect(tracesSampler).toHaveBeenLastCalledWith({
+ parentSampled: false,
+ transactionContext: { name: 'inner2', parentSampled: false },
+ });
+
+ // Now return `0.4`, it should not sample
+ tracesSamplerResponse = 0.4;
+
+ Sentry.startSpan({ name: 'outer3' }, outerSpan => {
+ expect(outerSpan).toBeUndefined();
+ });
+
+ expect(tracesSampler).toHaveBeenCalledTimes(4);
+ expect(tracesSampler).toHaveBeenLastCalledWith({
+ parentSampled: undefined,
+ transactionContext: { name: 'outer3', parentSampled: undefined },
+ });
+ });
+
+ it('samples with a tracesSampler even if parent is remotely sampled', () => {
+ const tracesSampler = jest.fn(() => {
+ return false;
+ });
+
+ mockSdkInit({ tracesSampler });
+ const traceId = 'd4cda95b652f4a1592b449d5929fda1b';
+ const parentSpanId = '6e0c63257de34c92';
+
+ const spanContext = {
+ traceId,
+ spanId: parentSpanId,
+ sampled: true,
+ isRemote: true,
+ traceFlags: TraceFlags.SAMPLED,
+ };
+
+ const propagationContext: PropagationContext = {
+ traceId,
+ sampled: true,
+ parentSpanId,
+ spanId: '6e0c63257de34c93',
+ };
+
+ // We simulate the correct context we'd normally get from the SentryPropagator
+ context.with(
+ trace.setSpanContext(
+ context.active().setValue(SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY, propagationContext),
+ spanContext,
+ ),
+ () => {
+ // This will def. be sampled because of the tracesSampleRate
+ Sentry.startSpan({ name: 'outer' }, outerSpan => {
+ expect(outerSpan).toBeUndefined();
+ });
+ },
+ );
+
+ expect(tracesSampler).toBeCalledTimes(1);
+ expect(tracesSampler).toHaveBeenLastCalledWith({
+ parentSampled: true,
+ transactionContext: {
+ name: 'outer',
+ parentSampled: true,
+ },
+ });
});
});
diff --git a/packages/node-experimental/test/sdk/transaction.test.ts b/packages/node-experimental/test/sdk/transaction.test.ts
new file mode 100644
index 000000000000..132696655b09
--- /dev/null
+++ b/packages/node-experimental/test/sdk/transaction.test.ts
@@ -0,0 +1,197 @@
+import { NodeExperimentalClient } from '../../src/sdk/client';
+import { getCurrentHub } from '../../src/sdk/hub';
+import { NodeExperimentalScope } from '../../src/sdk/scope';
+import { NodeExperimentalTransaction, startTransaction } from '../../src/sdk/transaction';
+import { getDefaultNodeExperimentalClientOptions } from '../helpers/getDefaultNodePreviewClientOptions';
+
+describe('NodeExperimentalTransaction', () => {
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+
+ it('works with finishWithScope without arguments', () => {
+ const client = new NodeExperimentalClient(getDefaultNodeExperimentalClientOptions());
+
+ const mockSend = jest.spyOn(client, 'captureEvent').mockImplementation(() => 'mocked');
+
+ const hub = getCurrentHub();
+ hub.bindClient(client);
+
+ const transaction = new NodeExperimentalTransaction({ name: 'test' }, hub);
+ transaction.sampled = true;
+
+ const res = transaction.finishWithScope();
+
+ expect(mockSend).toBeCalledTimes(1);
+ expect(mockSend).toBeCalledWith(
+ expect.objectContaining({
+ contexts: {
+ trace: {
+ span_id: expect.any(String),
+ trace_id: expect.any(String),
+ },
+ },
+ spans: [],
+ start_timestamp: expect.any(Number),
+ tags: {},
+ timestamp: expect.any(Number),
+ transaction: 'test',
+ type: 'transaction',
+ sdkProcessingMetadata: {
+ source: 'custom',
+ spanMetadata: {},
+ dynamicSamplingContext: {
+ environment: 'production',
+ trace_id: expect.any(String),
+ transaction: 'test',
+ sampled: 'true',
+ },
+ },
+ transaction_info: { source: 'custom' },
+ }),
+ { event_id: expect.any(String) },
+ undefined,
+ );
+ expect(res).toBe('mocked');
+ });
+
+ it('works with finishWithScope with endTime', () => {
+ const client = new NodeExperimentalClient(getDefaultNodeExperimentalClientOptions());
+
+ const mockSend = jest.spyOn(client, 'captureEvent').mockImplementation(() => 'mocked');
+
+ const hub = getCurrentHub();
+ hub.bindClient(client);
+
+ const transaction = new NodeExperimentalTransaction({ name: 'test', startTimestamp: 123456 }, hub);
+ transaction.sampled = true;
+
+ const res = transaction.finishWithScope(1234567);
+
+ expect(mockSend).toBeCalledTimes(1);
+ expect(mockSend).toBeCalledWith(
+ expect.objectContaining({
+ start_timestamp: 123456,
+ timestamp: 1234567,
+ }),
+ { event_id: expect.any(String) },
+ undefined,
+ );
+ expect(res).toBe('mocked');
+ });
+
+ it('works with finishWithScope with endTime & scope', () => {
+ const client = new NodeExperimentalClient(getDefaultNodeExperimentalClientOptions());
+
+ const mockSend = jest.spyOn(client, 'captureEvent').mockImplementation(() => 'mocked');
+
+ const hub = getCurrentHub();
+ hub.bindClient(client);
+
+ const transaction = new NodeExperimentalTransaction({ name: 'test', startTimestamp: 123456 }, hub);
+ transaction.sampled = true;
+
+ const scope = new NodeExperimentalScope();
+ scope.setTags({
+ tag1: 'yes',
+ tag2: 'no',
+ });
+ scope.setContext('os', { name: 'Custom OS' });
+
+ const res = transaction.finishWithScope(1234567, scope);
+
+ expect(mockSend).toBeCalledTimes(1);
+ expect(mockSend).toBeCalledWith(
+ expect.objectContaining({
+ contexts: {
+ trace: {
+ span_id: expect.any(String),
+ trace_id: expect.any(String),
+ },
+ },
+ spans: [],
+ start_timestamp: 123456,
+ tags: {},
+ timestamp: 1234567,
+ transaction: 'test',
+ type: 'transaction',
+ sdkProcessingMetadata: {
+ source: 'custom',
+ spanMetadata: {},
+ dynamicSamplingContext: {
+ environment: 'production',
+ trace_id: expect.any(String),
+ transaction: 'test',
+ sampled: 'true',
+ },
+ },
+ transaction_info: { source: 'custom' },
+ }),
+ { event_id: expect.any(String) },
+ scope,
+ );
+ expect(res).toBe('mocked');
+ });
+});
+
+describe('startTranscation', () => {
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+
+ it('creates a NodeExperimentalTransaction', () => {
+ const client = new NodeExperimentalClient(getDefaultNodeExperimentalClientOptions({ tracesSampleRate: 0 }));
+ const hub = getCurrentHub();
+ hub.bindClient(client);
+
+ const transaction = startTransaction(hub, { name: 'test' });
+
+ expect(transaction).toBeInstanceOf(NodeExperimentalTransaction);
+
+ expect(transaction.sampled).toBe(undefined);
+ expect(transaction.spanRecorder).toBeDefined();
+ expect(transaction.spanRecorder?.spans).toHaveLength(1);
+ expect(transaction.metadata).toEqual({
+ source: 'custom',
+ spanMetadata: {},
+ });
+
+ expect(transaction.toJSON()).toEqual(
+ expect.objectContaining({
+ origin: 'manual',
+ span_id: expect.any(String),
+ start_timestamp: expect.any(Number),
+ trace_id: expect.any(String),
+ }),
+ );
+ });
+
+ it('allows to pass data to transaction', () => {
+ const client = new NodeExperimentalClient(getDefaultNodeExperimentalClientOptions());
+ const hub = getCurrentHub();
+ hub.bindClient(client);
+
+ const transaction = startTransaction(hub, {
+ name: 'test',
+ startTimestamp: 1234,
+ spanId: 'span1',
+ traceId: 'trace1',
+ });
+
+ expect(transaction).toBeInstanceOf(NodeExperimentalTransaction);
+
+ expect(transaction.metadata).toEqual({
+ source: 'custom',
+ spanMetadata: {},
+ });
+
+ expect(transaction.toJSON()).toEqual(
+ expect.objectContaining({
+ origin: 'manual',
+ span_id: 'span1',
+ start_timestamp: 1234,
+ trace_id: 'trace1',
+ }),
+ );
+ });
+});
diff --git a/packages/node-experimental/test/utils/convertOtelTimeToSeconds.test.ts b/packages/node-experimental/test/utils/convertOtelTimeToSeconds.test.ts
new file mode 100644
index 000000000000..4f4911cee0cb
--- /dev/null
+++ b/packages/node-experimental/test/utils/convertOtelTimeToSeconds.test.ts
@@ -0,0 +1,9 @@
+import { convertOtelTimeToSeconds } from '../../src/utils/convertOtelTimeToSeconds';
+
+describe('convertOtelTimeToSeconds', () => {
+ it('works', () => {
+ expect(convertOtelTimeToSeconds([0, 0])).toEqual(0);
+ expect(convertOtelTimeToSeconds([1000, 50])).toEqual(1000.00000005);
+ expect(convertOtelTimeToSeconds([1000, 505])).toEqual(1000.000000505);
+ });
+});
diff --git a/packages/node-experimental/test/utils/getActiveSpan.test.ts b/packages/node-experimental/test/utils/getActiveSpan.test.ts
new file mode 100644
index 000000000000..b97ced5bdbf8
--- /dev/null
+++ b/packages/node-experimental/test/utils/getActiveSpan.test.ts
@@ -0,0 +1,157 @@
+import { trace } from '@opentelemetry/api';
+import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
+
+import { NodeExperimentalClient } from '../../src/sdk/client';
+import { setupOtel } from '../../src/sdk/initOtel';
+import { getActiveSpan, getRootSpan } from '../../src/utils/getActiveSpan';
+import { getDefaultNodeExperimentalClientOptions } from '../helpers/getDefaultNodePreviewClientOptions';
+import { cleanupOtel } from '../helpers/mockSdkInit';
+
+describe('getActiveSpan', () => {
+ let provider: BasicTracerProvider | undefined;
+
+ beforeEach(() => {
+ const options = getDefaultNodeExperimentalClientOptions();
+ const client = new NodeExperimentalClient(options);
+ provider = setupOtel(client);
+ });
+
+ afterEach(() => {
+ cleanupOtel(provider);
+ });
+
+ it('returns undefined if no span is active', () => {
+ const span = getActiveSpan();
+ expect(span).toBeUndefined();
+ });
+
+ it('returns undefined if no provider is active', async () => {
+ await provider?.forceFlush();
+ await provider?.shutdown();
+ provider = undefined;
+
+ const span = getActiveSpan();
+ expect(span).toBeUndefined();
+ });
+
+ it('returns currently active span', () => {
+ const tracer = trace.getTracer('test');
+
+ expect(getActiveSpan()).toBeUndefined();
+
+ tracer.startActiveSpan('test', span => {
+ expect(getActiveSpan()).toBe(span);
+
+ const inner1 = tracer.startSpan('inner1');
+
+ expect(getActiveSpan()).toBe(span);
+
+ inner1.end();
+
+ tracer.startActiveSpan('inner2', inner2 => {
+ expect(getActiveSpan()).toBe(inner2);
+
+ inner2.end();
+ });
+
+ expect(getActiveSpan()).toBe(span);
+
+ span.end();
+ });
+
+ expect(getActiveSpan()).toBeUndefined();
+ });
+
+ it('returns currently active span in concurrent spans', () => {
+ const tracer = trace.getTracer('test');
+
+ expect(getActiveSpan()).toBeUndefined();
+
+ tracer.startActiveSpan('test1', span => {
+ expect(getActiveSpan()).toBe(span);
+
+ tracer.startActiveSpan('inner1', inner1 => {
+ expect(getActiveSpan()).toBe(inner1);
+ inner1.end();
+ });
+
+ span.end();
+ });
+
+ tracer.startActiveSpan('test2', span => {
+ expect(getActiveSpan()).toBe(span);
+
+ tracer.startActiveSpan('inner2', inner => {
+ expect(getActiveSpan()).toBe(inner);
+ inner.end();
+ });
+
+ span.end();
+ });
+
+ expect(getActiveSpan()).toBeUndefined();
+ });
+});
+
+describe('getRootSpan', () => {
+ let provider: BasicTracerProvider | undefined;
+
+ beforeEach(() => {
+ const options = getDefaultNodeExperimentalClientOptions();
+ const client = new NodeExperimentalClient(options);
+ provider = setupOtel(client);
+ });
+
+ afterEach(async () => {
+ await provider?.forceFlush();
+ await provider?.shutdown();
+ });
+
+ it('returns currently active root span', () => {
+ const tracer = trace.getTracer('test');
+
+ tracer.startActiveSpan('test', span => {
+ expect(getRootSpan(span)).toBe(span);
+
+ const inner1 = tracer.startSpan('inner1');
+
+ expect(getRootSpan(inner1)).toBe(span);
+
+ inner1.end();
+
+ tracer.startActiveSpan('inner2', inner2 => {
+ expect(getRootSpan(inner2)).toBe(span);
+
+ inner2.end();
+ });
+
+ span.end();
+ });
+ });
+
+ it('returns currently active root span in concurrent spans', () => {
+ const tracer = trace.getTracer('test');
+
+ tracer.startActiveSpan('test1', span => {
+ expect(getRootSpan(span)).toBe(span);
+
+ tracer.startActiveSpan('inner1', inner1 => {
+ expect(getRootSpan(inner1)).toBe(span);
+ inner1.end();
+ });
+
+ span.end();
+ });
+
+ tracer.startActiveSpan('test2', span => {
+ expect(getRootSpan(span)).toBe(span);
+
+ tracer.startActiveSpan('inner2', inner => {
+ expect(getRootSpan(inner)).toBe(span);
+ inner.end();
+ });
+
+ span.end();
+ });
+ });
+});
diff --git a/packages/node-experimental/test/utils/getRequestSpanData.test.ts b/packages/node-experimental/test/utils/getRequestSpanData.test.ts
new file mode 100644
index 000000000000..0edd2befea6c
--- /dev/null
+++ b/packages/node-experimental/test/utils/getRequestSpanData.test.ts
@@ -0,0 +1,59 @@
+import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
+
+import { getRequestSpanData } from '../../src/utils/getRequestSpanData';
+import { createSpan } from '../helpers/createSpan';
+
+describe('getRequestSpanData', () => {
+ it('works with basic span', () => {
+ const span = createSpan();
+ const data = getRequestSpanData(span);
+
+ expect(data).toEqual({});
+ });
+
+ it('works with http span', () => {
+ const span = createSpan();
+ span.setAttributes({
+ [SemanticAttributes.HTTP_URL]: 'http://example.com?foo=bar#baz',
+ [SemanticAttributes.HTTP_METHOD]: 'GET',
+ });
+
+ const data = getRequestSpanData(span);
+
+ expect(data).toEqual({
+ url: 'http://example.com',
+ 'http.method': 'GET',
+ 'http.query': '?foo=bar',
+ 'http.fragment': '#baz',
+ });
+ });
+
+ it('works without method', () => {
+ const span = createSpan();
+ span.setAttributes({
+ [SemanticAttributes.HTTP_URL]: 'http://example.com',
+ });
+
+ const data = getRequestSpanData(span);
+
+ expect(data).toEqual({
+ url: 'http://example.com',
+ 'http.method': 'GET',
+ });
+ });
+
+ it('works with incorrect URL', () => {
+ const span = createSpan();
+ span.setAttributes({
+ [SemanticAttributes.HTTP_URL]: 'malformed-url-here',
+ [SemanticAttributes.HTTP_METHOD]: 'GET',
+ });
+
+ const data = getRequestSpanData(span);
+
+ expect(data).toEqual({
+ url: 'malformed-url-here',
+ 'http.method': 'GET',
+ });
+ });
+});
diff --git a/packages/node-experimental/test/utils/groupSpansWithParents.test.ts b/packages/node-experimental/test/utils/groupSpansWithParents.test.ts
new file mode 100644
index 000000000000..d9a3fa60cb97
--- /dev/null
+++ b/packages/node-experimental/test/utils/groupSpansWithParents.test.ts
@@ -0,0 +1,123 @@
+import { groupSpansWithParents } from '../../src/utils/groupSpansWithParents';
+import { createSpan } from '../helpers/createSpan';
+
+describe('groupSpansWithParents', () => {
+ it('works with no spans', () => {
+ const actual = groupSpansWithParents([]);
+ expect(actual).toEqual([]);
+ });
+
+ it('works with a single root span & in-order spans', () => {
+ const rootSpan = createSpan('root', { spanId: 'rootId' });
+ const parentSpan1 = createSpan('parent1', { spanId: 'parent1Id', parentSpanId: 'rootId' });
+ const parentSpan2 = createSpan('parent2', { spanId: 'parent2Id', parentSpanId: 'rootId' });
+ const child1 = createSpan('child1', { spanId: 'child1', parentSpanId: 'parent1Id' });
+
+ const actual = groupSpansWithParents([rootSpan, parentSpan1, parentSpan2, child1]);
+ expect(actual).toHaveLength(4);
+
+ // Ensure parent & span is correctly set
+ const rootRef = actual.find(ref => ref.span === rootSpan);
+ const parent1Ref = actual.find(ref => ref.span === parentSpan1);
+ const parent2Ref = actual.find(ref => ref.span === parentSpan2);
+ const child1Ref = actual.find(ref => ref.span === child1);
+
+ expect(rootRef).toBeDefined();
+ expect(parent1Ref).toBeDefined();
+ expect(parent2Ref).toBeDefined();
+ expect(child1Ref).toBeDefined();
+
+ expect(rootRef?.parentNode).toBeUndefined();
+ expect(rootRef?.children).toEqual([parent1Ref, parent2Ref]);
+
+ expect(parent1Ref?.span).toBe(parentSpan1);
+ expect(parent2Ref?.span).toBe(parentSpan2);
+
+ expect(parent1Ref?.parentNode).toBe(rootRef);
+ expect(parent2Ref?.parentNode).toBe(rootRef);
+
+ expect(parent1Ref?.children).toEqual([child1Ref]);
+ expect(parent2Ref?.children).toEqual([]);
+
+ expect(child1Ref?.parentNode).toBe(parent1Ref);
+ expect(child1Ref?.children).toEqual([]);
+ });
+
+ it('works with a spans with missing root span', () => {
+ const parentSpan1 = createSpan('parent1', { spanId: 'parent1Id', parentSpanId: 'rootId' });
+ const parentSpan2 = createSpan('parent2', { spanId: 'parent2Id', parentSpanId: 'rootId' });
+ const child1 = createSpan('child1', { spanId: 'child1', parentSpanId: 'parent1Id' });
+
+ const actual = groupSpansWithParents([parentSpan1, parentSpan2, child1]);
+ expect(actual).toHaveLength(4);
+
+ // Ensure parent & span is correctly set
+ const rootRef = actual.find(ref => ref.id === 'rootId');
+ const parent1Ref = actual.find(ref => ref.span === parentSpan1);
+ const parent2Ref = actual.find(ref => ref.span === parentSpan2);
+ const child1Ref = actual.find(ref => ref.span === child1);
+
+ expect(rootRef).toBeDefined();
+ expect(parent1Ref).toBeDefined();
+ expect(parent2Ref).toBeDefined();
+ expect(child1Ref).toBeDefined();
+
+ expect(rootRef?.parentNode).toBeUndefined();
+ expect(rootRef?.span).toBeUndefined();
+ expect(rootRef?.children).toEqual([parent1Ref, parent2Ref]);
+
+ expect(parent1Ref?.span).toBe(parentSpan1);
+ expect(parent2Ref?.span).toBe(parentSpan2);
+
+ expect(parent1Ref?.parentNode).toBe(rootRef);
+ expect(parent2Ref?.parentNode).toBe(rootRef);
+
+ expect(parent1Ref?.children).toEqual([child1Ref]);
+ expect(parent2Ref?.children).toEqual([]);
+
+ expect(child1Ref?.parentNode).toBe(parent1Ref);
+ expect(child1Ref?.children).toEqual([]);
+ });
+
+ it('works with multiple root spans & out-of-order spans', () => {
+ const rootSpan1 = createSpan('root1', { spanId: 'root1Id' });
+ const rootSpan2 = createSpan('root2', { spanId: 'root2Id' });
+ const parentSpan1 = createSpan('parent1', { spanId: 'parent1Id', parentSpanId: 'root1Id' });
+ const parentSpan2 = createSpan('parent2', { spanId: 'parent2Id', parentSpanId: 'root2Id' });
+ const childSpan1 = createSpan('child1', { spanId: 'child1Id', parentSpanId: 'parent1Id' });
+
+ const actual = groupSpansWithParents([childSpan1, parentSpan1, parentSpan2, rootSpan2, rootSpan1]);
+ expect(actual).toHaveLength(5);
+
+ // Ensure parent & span is correctly set
+ const root1Ref = actual.find(ref => ref.span === rootSpan1);
+ const root2Ref = actual.find(ref => ref.span === rootSpan2);
+ const parent1Ref = actual.find(ref => ref.span === parentSpan1);
+ const parent2Ref = actual.find(ref => ref.span === parentSpan2);
+ const child1Ref = actual.find(ref => ref.span === childSpan1);
+
+ expect(root1Ref).toBeDefined();
+ expect(root2Ref).toBeDefined();
+ expect(parent1Ref).toBeDefined();
+ expect(parent2Ref).toBeDefined();
+ expect(child1Ref).toBeDefined();
+
+ expect(root1Ref?.parentNode).toBeUndefined();
+ expect(root1Ref?.children).toEqual([parent1Ref]);
+
+ expect(root2Ref?.parentNode).toBeUndefined();
+ expect(root2Ref?.children).toEqual([parent2Ref]);
+
+ expect(parent1Ref?.span).toBe(parentSpan1);
+ expect(parent2Ref?.span).toBe(parentSpan2);
+
+ expect(parent1Ref?.parentNode).toBe(root1Ref);
+ expect(parent2Ref?.parentNode).toBe(root2Ref);
+
+ expect(parent1Ref?.children).toEqual([child1Ref]);
+ expect(parent2Ref?.children).toEqual([]);
+
+ expect(child1Ref?.parentNode).toBe(parent1Ref);
+ expect(child1Ref?.children).toEqual([]);
+ });
+});
diff --git a/packages/node-experimental/test/utils/setupEventContextTrace.test.ts b/packages/node-experimental/test/utils/setupEventContextTrace.test.ts
new file mode 100644
index 000000000000..15d7f0976b9e
--- /dev/null
+++ b/packages/node-experimental/test/utils/setupEventContextTrace.test.ts
@@ -0,0 +1,111 @@
+import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
+import { makeMain } from '@sentry/core';
+
+import { NodeExperimentalClient } from '../../src/sdk/client';
+import { NodeExperimentalHub } from '../../src/sdk/hub';
+import { setupOtel } from '../../src/sdk/initOtel';
+import { startSpan } from '../../src/sdk/trace';
+import { setupEventContextTrace } from '../../src/utils/setupEventContextTrace';
+import { getDefaultNodeExperimentalClientOptions } from '../helpers/getDefaultNodePreviewClientOptions';
+import { cleanupOtel } from '../helpers/mockSdkInit';
+
+const PUBLIC_DSN = 'https://username@domain/123';
+
+describe('setupEventContextTrace', () => {
+ const beforeSend = jest.fn(() => null);
+ let client: NodeExperimentalClient;
+ let hub: NodeExperimentalHub;
+ let provider: BasicTracerProvider | undefined;
+
+ beforeEach(() => {
+ client = new NodeExperimentalClient(
+ getDefaultNodeExperimentalClientOptions({
+ sampleRate: 1,
+ enableTracing: true,
+ beforeSend,
+ debug: true,
+ dsn: PUBLIC_DSN,
+ }),
+ );
+
+ hub = new NodeExperimentalHub(client);
+ makeMain(hub);
+
+ setupEventContextTrace(client);
+ provider = setupOtel(client);
+ });
+
+ afterEach(() => {
+ beforeSend.mockReset();
+ cleanupOtel(provider);
+ });
+
+ afterAll(() => {
+ jest.clearAllMocks();
+ });
+
+ it('works with no active span', async () => {
+ const error = new Error('test');
+ hub.captureException(error);
+ await client.flush();
+
+ expect(beforeSend).toHaveBeenCalledTimes(1);
+ expect(beforeSend).toHaveBeenCalledWith(
+ expect.objectContaining({
+ contexts: expect.objectContaining({
+ trace: {
+ span_id: expect.any(String),
+ trace_id: expect.any(String),
+ },
+ }),
+ }),
+ expect.objectContaining({
+ event_id: expect.any(String),
+ originalException: error,
+ syntheticException: expect.any(Error),
+ }),
+ );
+ });
+
+ it('works with active span', async () => {
+ const error = new Error('test');
+
+ let outerId: string | undefined;
+ let innerId: string | undefined;
+ let traceId: string | undefined;
+
+ startSpan({ name: 'outer' }, outerSpan => {
+ outerId = outerSpan?.spanContext().spanId;
+ traceId = outerSpan?.spanContext().traceId;
+
+ startSpan({ name: 'inner' }, innerSpan => {
+ innerId = innerSpan?.spanContext().spanId;
+ hub.captureException(error);
+ });
+ });
+
+ await client.flush();
+
+ expect(outerId).toBeDefined();
+ expect(innerId).toBeDefined();
+ expect(traceId).toBeDefined();
+
+ expect(beforeSend).toHaveBeenCalledTimes(1);
+ expect(beforeSend).toHaveBeenCalledWith(
+ expect.objectContaining({
+ contexts: expect.objectContaining({
+ trace: {
+ span_id: innerId,
+ parent_span_id: outerId,
+ trace_id: traceId,
+ },
+ }),
+ }),
+ expect.objectContaining({
+ event_id: expect.any(String),
+ originalException: error,
+ syntheticException: expect.any(Error),
+ }),
+ );
+ });
+});
diff --git a/packages/node-experimental/test/utils/spanTypes.test.ts b/packages/node-experimental/test/utils/spanTypes.test.ts
new file mode 100644
index 000000000000..fcd4703db9ce
--- /dev/null
+++ b/packages/node-experimental/test/utils/spanTypes.test.ts
@@ -0,0 +1,98 @@
+import type { Span } from '@opentelemetry/api';
+
+import {
+ spanHasAttributes,
+ spanHasEvents,
+ spanHasKind,
+ spanHasParentId,
+ spanIsSdkTraceBaseSpan,
+} from '../../src/utils/spanTypes';
+import { createSpan } from '../helpers/createSpan';
+
+describe('spanTypes', () => {
+ describe('spanHasAttributes', () => {
+ it.each([
+ [{}, false],
+ [{ attributes: null }, false],
+ [{ attributes: {} }, true],
+ ])('works with %p', (span, expected) => {
+ const castSpan = span as unknown as Span;
+ const actual = spanHasAttributes(castSpan);
+
+ expect(actual).toBe(expected);
+
+ if (actual) {
+ expect(castSpan.attributes).toBeDefined();
+ }
+ });
+ });
+
+ describe('spanHasKind', () => {
+ it.each([
+ [{}, false],
+ [{ kind: null }, false],
+ [{ kind: 'TEST_KIND' }, true],
+ ])('works with %p', (span, expected) => {
+ const castSpan = span as unknown as Span;
+ const actual = spanHasKind(castSpan);
+
+ expect(actual).toBe(expected);
+
+ if (actual) {
+ expect(castSpan.kind).toBeDefined();
+ }
+ });
+ });
+
+ describe('spanHasParentId', () => {
+ it.each([
+ [{}, false],
+ [{ parentSpanId: null }, false],
+ [{ parentSpanId: 'TEST_PARENT_ID' }, true],
+ ])('works with %p', (span, expected) => {
+ const castSpan = span as unknown as Span;
+ const actual = spanHasParentId(castSpan);
+
+ expect(actual).toBe(expected);
+
+ if (actual) {
+ expect(castSpan.parentSpanId).toBeDefined();
+ }
+ });
+ });
+
+ describe('spanHasEvents', () => {
+ it.each([
+ [{}, false],
+ [{ events: null }, false],
+ [{ events: [] }, true],
+ ])('works with %p', (span, expected) => {
+ const castSpan = span as unknown as Span;
+ const actual = spanHasEvents(castSpan);
+
+ expect(actual).toBe(expected);
+
+ if (actual) {
+ expect(castSpan.events).toBeDefined();
+ }
+ });
+ });
+
+ describe('spanIsSdkTraceBaseSpan', () => {
+ it.each([
+ [{}, false],
+ [createSpan(), true],
+ ])('works with %p', (span, expected) => {
+ const castSpan = span as unknown as Span;
+ const actual = spanIsSdkTraceBaseSpan(castSpan);
+
+ expect(actual).toBe(expected);
+
+ if (actual) {
+ expect(castSpan.events).toBeDefined();
+ expect(castSpan.attributes).toBeDefined();
+ expect(castSpan.kind).toBeDefined();
+ }
+ });
+ });
+});
diff --git a/packages/node-integration-tests/suites/anr/basic.js b/packages/node-integration-tests/suites/anr/basic.js
index 3abadc09b9c3..45a324e507c5 100644
--- a/packages/node-integration-tests/suites/anr/basic.js
+++ b/packages/node-integration-tests/suites/anr/basic.js
@@ -10,13 +10,14 @@ setTimeout(() => {
Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
+ debug: true,
beforeSend: event => {
// eslint-disable-next-line no-console
console.log(JSON.stringify(event));
},
});
-Sentry.enableAnrDetection({ captureStackTrace: true, anrThreshold: 200, debug: true }).then(() => {
+Sentry.enableAnrDetection({ captureStackTrace: true, anrThreshold: 200 }).then(() => {
function longWork() {
for (let i = 0; i < 100; i++) {
const salt = crypto.randomBytes(128).toString('base64');
diff --git a/packages/node-integration-tests/suites/anr/basic.mjs b/packages/node-integration-tests/suites/anr/basic.mjs
index ba9c8623da7e..1d89ac1b3989 100644
--- a/packages/node-integration-tests/suites/anr/basic.mjs
+++ b/packages/node-integration-tests/suites/anr/basic.mjs
@@ -10,13 +10,14 @@ setTimeout(() => {
Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
+ debug: true,
beforeSend: event => {
// eslint-disable-next-line no-console
console.log(JSON.stringify(event));
},
});
-await Sentry.enableAnrDetection({ captureStackTrace: true, anrThreshold: 200, debug: true });
+await Sentry.enableAnrDetection({ captureStackTrace: true, anrThreshold: 200 });
function longWork() {
for (let i = 0; i < 100; i++) {
diff --git a/packages/node-integration-tests/suites/anr/forked.js b/packages/node-integration-tests/suites/anr/forked.js
index 3abadc09b9c3..45a324e507c5 100644
--- a/packages/node-integration-tests/suites/anr/forked.js
+++ b/packages/node-integration-tests/suites/anr/forked.js
@@ -10,13 +10,14 @@ setTimeout(() => {
Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
+ debug: true,
beforeSend: event => {
// eslint-disable-next-line no-console
console.log(JSON.stringify(event));
},
});
-Sentry.enableAnrDetection({ captureStackTrace: true, anrThreshold: 200, debug: true }).then(() => {
+Sentry.enableAnrDetection({ captureStackTrace: true, anrThreshold: 200 }).then(() => {
function longWork() {
for (let i = 0; i < 100; i++) {
const salt = crypto.randomBytes(128).toString('base64');
diff --git a/packages/node-integration-tests/suites/anr/test.ts b/packages/node-integration-tests/suites/anr/test.ts
index 4fd83c0b3205..96d83c64a6a7 100644
--- a/packages/node-integration-tests/suites/anr/test.ts
+++ b/packages/node-integration-tests/suites/anr/test.ts
@@ -5,6 +5,22 @@ import * as path from 'path';
const NODE_VERSION = parseSemver(process.versions.node).major || 0;
+/** The output will contain logging so we need to find the line that parses as JSON */
+function parseJsonLine(input: string): T {
+ return (
+ input
+ .split('\n')
+ .map(line => {
+ try {
+ return JSON.parse(line) as T;
+ } catch {
+ return undefined;
+ }
+ })
+ .filter(a => a) as T[]
+ )[0];
+}
+
describe('should report ANR when event loop blocked', () => {
test('CJS', done => {
// The stack trace is different when node < 12
@@ -15,7 +31,7 @@ describe('should report ANR when event loop blocked', () => {
const testScriptPath = path.resolve(__dirname, 'basic.js');
childProcess.exec(`node ${testScriptPath}`, { encoding: 'utf8' }, (_, stdout) => {
- const event = JSON.parse(stdout) as Event;
+ const event = parseJsonLine(stdout);
expect(event.exception?.values?.[0].mechanism).toEqual({ type: 'ANR' });
expect(event.exception?.values?.[0].type).toEqual('ApplicationNotResponding');
@@ -42,7 +58,7 @@ describe('should report ANR when event loop blocked', () => {
const testScriptPath = path.resolve(__dirname, 'basic.mjs');
childProcess.exec(`node ${testScriptPath}`, { encoding: 'utf8' }, (_, stdout) => {
- const event = JSON.parse(stdout) as Event;
+ const event = parseJsonLine(stdout);
expect(event.exception?.values?.[0].mechanism).toEqual({ type: 'ANR' });
expect(event.exception?.values?.[0].type).toEqual('ApplicationNotResponding');
@@ -64,7 +80,7 @@ describe('should report ANR when event loop blocked', () => {
const testScriptPath = path.resolve(__dirname, 'forker.js');
childProcess.exec(`node ${testScriptPath}`, { encoding: 'utf8' }, (_, stdout) => {
- const event = JSON.parse(stdout) as Event;
+ const event = parseJsonLine(stdout);
expect(event.exception?.values?.[0].mechanism).toEqual({ type: 'ANR' });
expect(event.exception?.values?.[0].type).toEqual('ApplicationNotResponding');
diff --git a/packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutCallback/scenario.ts b/packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutCallback/scenario.ts
index a47f05203d35..ac1d6421dec8 100644
--- a/packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutCallback/scenario.ts
+++ b/packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutCallback/scenario.ts
@@ -13,6 +13,12 @@ const connection = mysql.createConnection({
password: 'docker',
});
+connection.connect(function (err: unknown) {
+ if (err) {
+ return;
+ }
+});
+
const transaction = Sentry.startTransaction({
op: 'transaction',
name: 'Test Transaction',
@@ -22,10 +28,18 @@ Sentry.configureScope(scope => {
scope.setSpan(transaction);
});
-connection.query('SELECT 1 + 1 AS solution');
-connection.query('SELECT NOW()', ['1', '2']);
+const query = connection.query('SELECT 1 + 1 AS solution');
+const query2 = connection.query('SELECT NOW()', ['1', '2']);
+
+query.on('end', () => {
+ transaction.setTag('result_done', 'yes');
-// Wait a bit to ensure the queries completed
-setTimeout(() => {
- transaction.finish();
-}, 500);
+ query2.on('end', () => {
+ transaction.setTag('result_done2', 'yes');
+
+ // Wait a bit to ensure the queries completed
+ setTimeout(() => {
+ transaction.finish();
+ }, 500);
+ });
+});
diff --git a/packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutCallback/test.ts b/packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutCallback/test.ts
index 9e58b59fecad..ccc5df1c4739 100644
--- a/packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutCallback/test.ts
+++ b/packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutCallback/test.ts
@@ -8,6 +8,10 @@ test('should auto-instrument `mysql` package when using query without callback',
assertSentryTransaction(envelope[2], {
transaction: 'Test Transaction',
+ tags: {
+ result_done: 'yes',
+ result_done2: 'yes',
+ },
spans: [
{
description: 'SELECT 1 + 1 AS solution',
diff --git a/packages/node/src/anr/debugger.ts b/packages/node/src/anr/debugger.ts
index 4d4a2799fa64..01b2a90fe0f9 100644
--- a/packages/node/src/anr/debugger.ts
+++ b/packages/node/src/anr/debugger.ts
@@ -1,33 +1,10 @@
import type { StackFrame } from '@sentry/types';
-import { dropUndefinedKeys, filenameIsInApp } from '@sentry/utils';
+import { createDebugPauseMessageHandler } from '@sentry/utils';
import type { Debugger } from 'inspector';
import { getModuleFromFilename } from '../module';
import { createWebSocketClient } from './websocket';
-/**
- * Converts Debugger.CallFrame to Sentry StackFrame
- */
-function callFrameToStackFrame(
- frame: Debugger.CallFrame,
- filenameFromScriptId: (id: string) => string | undefined,
-): StackFrame {
- const filename = filenameFromScriptId(frame.location.scriptId)?.replace(/^file:\/\//, '');
-
- // CallFrame row/col are 0 based, whereas StackFrame are 1 based
- const colno = frame.location.columnNumber ? frame.location.columnNumber + 1 : undefined;
- const lineno = frame.location.lineNumber ? frame.location.lineNumber + 1 : undefined;
-
- return dropUndefinedKeys({
- filename,
- module: getModuleFromFilename(filename),
- function: frame.functionName || '?',
- colno,
- lineno,
- in_app: filename ? filenameIsInApp(filename) : undefined,
- });
-}
-
// The only messages we care about
type DebugMessage =
| {
@@ -45,7 +22,7 @@ type DebugMessage =
async function webSocketDebugger(
url: string,
onMessage: (message: DebugMessage) => void,
-): Promise<(method: string, params?: unknown) => void> {
+): Promise<(method: string) => void> {
let id = 0;
const webSocket = await createWebSocketClient(url);
@@ -54,8 +31,8 @@ async function webSocketDebugger(
onMessage(message);
});
- return (method: string, params?: unknown) => {
- webSocket.send(JSON.stringify({ id: id++, method, params }));
+ return (method: string) => {
+ webSocket.send(JSON.stringify({ id: id++, method }));
};
}
@@ -66,27 +43,10 @@ async function webSocketDebugger(
* @returns A function that triggers the debugger to pause and capture a stack trace
*/
export async function captureStackTrace(url: string, callback: (frames: StackFrame[]) => void): Promise<() => void> {
- // Collect scriptId -> url map so we can look up the filenames later
- const scripts = new Map();
-
- const sendCommand = await webSocketDebugger(url, message => {
- if (message.method === 'Debugger.scriptParsed') {
- scripts.set(message.params.scriptId, message.params.url);
- } else if (message.method === 'Debugger.paused') {
- // copy the frames
- const callFrames = [...message.params.callFrames];
- // and resume immediately!
- sendCommand('Debugger.resume');
- sendCommand('Debugger.disable');
-
- const frames = callFrames
- .map(frame => callFrameToStackFrame(frame, id => scripts.get(id)))
- // Sentry expects the frames to be in the opposite order
- .reverse();
-
- callback(frames);
- }
- });
+ const sendCommand: (method: string) => void = await webSocketDebugger(
+ url,
+ createDebugPauseMessageHandler(cmd => sendCommand(cmd), getModuleFromFilename, callback),
+ );
return () => {
sendCommand('Debugger.enable');
diff --git a/packages/node/src/anr/index.ts b/packages/node/src/anr/index.ts
index 5c4edd808aa3..1d4d61b78b55 100644
--- a/packages/node/src/anr/index.ts
+++ b/packages/node/src/anr/index.ts
@@ -1,7 +1,6 @@
import type { Event, StackFrame } from '@sentry/types';
-import { logger } from '@sentry/utils';
+import { logger, watchdogTimer } from '@sentry/utils';
import { spawn } from 'child_process';
-import * as inspector from 'inspector';
import { addGlobalEventProcessor, captureEvent, flush } from '..';
import { captureStackTrace } from './debugger';
@@ -9,36 +8,6 @@ import { captureStackTrace } from './debugger';
const DEFAULT_INTERVAL = 50;
const DEFAULT_HANG_THRESHOLD = 5000;
-/**
- * A node.js watchdog timer
- * @param pollInterval The interval that we expect to get polled at
- * @param anrThreshold The threshold for when we consider ANR
- * @param callback The callback to call for ANR
- * @returns A function to call to reset the timer
- */
-function watchdogTimer(pollInterval: number, anrThreshold: number, callback: () => void): () => void {
- let lastPoll = process.hrtime();
- let triggered = false;
-
- setInterval(() => {
- const [seconds, nanoSeconds] = process.hrtime(lastPoll);
- const diffMs = Math.floor(seconds * 1e3 + nanoSeconds / 1e6);
-
- if (triggered === false && diffMs > pollInterval + anrThreshold) {
- triggered = true;
- callback();
- }
-
- if (diffMs < pollInterval + anrThreshold) {
- triggered = false;
- }
- }, 20);
-
- return () => {
- lastPoll = process.hrtime();
- };
-}
-
interface Options {
/**
* The app entry script. This is used to run the same script as the child process.
@@ -67,7 +36,7 @@ interface Options {
*/
captureStackTrace: boolean;
/**
- * Log debug information.
+ * @deprecated Use 'init' debug option instead
*/
debug: boolean;
}
@@ -98,12 +67,19 @@ function sendEvent(blockedMs: number, frames?: StackFrame[]): void {
});
}
+interface InspectorApi {
+ open: (port: number) => void;
+ url: () => string | undefined;
+}
+
/**
* Starts the node debugger and returns the inspector url.
*
* When inspector.url() returns undefined, it means the port is already in use so we try the next port.
*/
function startInspector(startPort: number = 9229): string | undefined {
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
+ const inspector: InspectorApi = require('inspector');
let inspectorUrl: string | undefined = undefined;
let port = startPort;
@@ -118,9 +94,7 @@ function startInspector(startPort: number = 9229): string | undefined {
function startChildProcess(options: Options): void {
function log(message: string, ...args: unknown[]): void {
- if (options.debug) {
- logger.log(`[ANR] ${message}`, ...args);
- }
+ logger.log(`[ANR] ${message}`, ...args);
}
try {
@@ -135,7 +109,7 @@ function startChildProcess(options: Options): void {
const child = spawn(process.execPath, [options.entryScript], {
env,
- stdio: options.debug ? ['inherit', 'inherit', 'inherit', 'ipc'] : ['ignore', 'ignore', 'ignore', 'ipc'],
+ stdio: logger.isEnabled() ? ['inherit', 'inherit', 'inherit', 'ipc'] : ['ignore', 'ignore', 'ignore', 'ipc'],
});
// The child process should not keep the main process alive
child.unref();
@@ -166,9 +140,7 @@ function startChildProcess(options: Options): void {
function handleChildProcess(options: Options): void {
function log(message: string): void {
- if (options.debug) {
- logger.log(`[ANR child process] ${message}`);
- }
+ logger.log(`[ANR child process] ${message}`);
}
process.title = 'sentry-anr';
@@ -210,10 +182,10 @@ function handleChildProcess(options: Options): void {
}
}
- const ping = watchdogTimer(options.pollInterval, options.anrThreshold, watchdogTimeout);
+ const { poll } = watchdogTimer(options.pollInterval, options.anrThreshold, watchdogTimeout);
process.on('message', () => {
- ping();
+ poll();
});
}
@@ -257,6 +229,7 @@ export function enableAnrDetection(options: Partial): Promise {
pollInterval: options.pollInterval || DEFAULT_INTERVAL,
anrThreshold: options.anrThreshold || DEFAULT_HANG_THRESHOLD,
captureStackTrace: !!options.captureStackTrace,
+ // eslint-disable-next-line deprecation/deprecation
debug: !!options.debug,
};
diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts
index 503f2749ea29..b0ab9dffefcb 100644
--- a/packages/node/src/index.ts
+++ b/packages/node/src/index.ts
@@ -26,6 +26,7 @@ export type { NodeOptions } from './types';
export {
addGlobalEventProcessor,
addBreadcrumb,
+ addIntegration,
captureException,
captureEvent,
captureMessage,
@@ -61,6 +62,7 @@ export {
startActiveSpan,
startInactiveSpan,
startSpanManual,
+ continueTrace,
} from '@sentry/core';
export type { SpanStatusType } from '@sentry/core';
export { autoDiscoverNodePerformanceMonitoringIntegrations } from './tracing';
diff --git a/packages/node/src/module.ts b/packages/node/src/module.ts
index 44bff87a02d2..6085813e6035 100644
--- a/packages/node/src/module.ts
+++ b/packages/node/src/module.ts
@@ -30,8 +30,8 @@ export function getModuleFromFilename(
// It's specifically a module
let file = basename;
- if (ext === '.js') {
- file = file.slice(0, file.length - '.js'.length);
+ if (ext === '.js' || ext === '.mjs' || ext === '.cjs') {
+ file = file.slice(0, ext.length * -1);
}
if (!root && !dir) {
diff --git a/packages/node/test/module.test.ts b/packages/node/test/module.test.ts
index e27f1482ff90..89c8878a433e 100644
--- a/packages/node/test/module.test.ts
+++ b/packages/node/test/module.test.ts
@@ -27,4 +27,16 @@ describe('getModuleFromFilename', () => {
expect(getModuleFromFilename('/Users/users/Tim/Desktop/node_modules/module.js')).toEqual('module');
}, '/Users/Tim/app.js');
});
+
+ test('POSIX .mjs', () => {
+ withFilename(() => {
+ expect(getModuleFromFilename('/Users/users/Tim/Desktop/node_modules/module.mjs')).toEqual('module');
+ }, '/Users/Tim/app.js');
+ });
+
+ test('POSIX .cjs', () => {
+ withFilename(() => {
+ expect(getModuleFromFilename('/Users/users/Tim/Desktop/node_modules/module.cjs')).toEqual('module');
+ }, '/Users/Tim/app.js');
+ });
});
diff --git a/packages/opentelemetry-node/src/index.ts b/packages/opentelemetry-node/src/index.ts
index 630acd960059..7ed84c517b45 100644
--- a/packages/opentelemetry-node/src/index.ts
+++ b/packages/opentelemetry-node/src/index.ts
@@ -1,19 +1,10 @@
-import { getSentrySpan } from './utils/spanMap';
-
export { SentrySpanProcessor } from './spanprocessor';
export { SentryPropagator } from './propagator';
+export { maybeCaptureExceptionForTimedEvent } from './utils/captureExceptionForTimedEvent';
+export { parseOtelSpanDescription } from './utils/parseOtelSpanDescription';
+export { mapOtelStatus } from './utils/mapOtelStatus';
/* eslint-disable deprecation/deprecation */
export { addOtelSpanData, getOtelSpanData, clearOtelSpanData } from './utils/spanData';
export type { AdditionalOtelSpanData } from './utils/spanData';
/* eslint-enable deprecation/deprecation */
-
-/**
- * This is only exported for internal use.
- * Semver etc. does not apply here, this is subject to change at any time!
- * This is explicitly _NOT_ public because we may have to change the underlying way we store/handle spans,
- * which may make this API unusable without further notice.
- *
- * @private
- */
-export { getSentrySpan as _INTERNAL_getSentrySpan };
diff --git a/packages/opentelemetry-node/src/spanprocessor.ts b/packages/opentelemetry-node/src/spanprocessor.ts
index 012ead8b9d3d..671cdbb7894a 100644
--- a/packages/opentelemetry-node/src/spanprocessor.ts
+++ b/packages/opentelemetry-node/src/spanprocessor.ts
@@ -10,7 +10,7 @@ import { SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, SENTRY_TRACE_PARENT_CONTEXT_KEY }
import { maybeCaptureExceptionForTimedEvent } from './utils/captureExceptionForTimedEvent';
import { isSentryRequestSpan } from './utils/isSentryRequest';
import { mapOtelStatus } from './utils/mapOtelStatus';
-import { parseSpanDescription } from './utils/parseOtelSpanDescription';
+import { parseOtelSpanDescription } from './utils/parseOtelSpanDescription';
import { clearSpan, getSentrySpan, setSentrySpan } from './utils/spanMap';
/**
@@ -182,7 +182,7 @@ function getTraceData(otelSpan: OtelSpan, parentContext: Context): Partial {
+ errorHandler: (err: Error & { __rrweb__?: boolean }) => {
try {
- // @ts-expect-error Set this so that replay SDK can ignore errors originating from rrweb
err.__rrweb__ = true;
- } catch {
- // avoid any potential hazards here
+ } catch (error) {
+ // ignore errors here
+ // this can happen if the error is frozen or does not allow mutation for other reasons
}
- // return true to suppress throwing the error inside of rrweb
- return true;
},
};
diff --git a/packages/serverless/src/awslambda.ts b/packages/serverless/src/awslambda.ts
index e8847fc7b212..e29f77aaf879 100644
--- a/packages/serverless/src/awslambda.ts
+++ b/packages/serverless/src/awslambda.ts
@@ -2,7 +2,7 @@
import type { Scope } from '@sentry/node';
import * as Sentry from '@sentry/node';
import { captureException, captureMessage, flush, getCurrentHub, withScope } from '@sentry/node';
-import type { Integration } from '@sentry/types';
+import type { Integration, SdkMetadata } from '@sentry/types';
import { isString, logger, tracingContextFromHeaders } from '@sentry/utils';
// NOTE: I have no idea how to fix this right now, and don't want to waste more time, as it builds just fine — Kamil
// eslint-disable-next-line import/no-unresolved
@@ -34,7 +34,9 @@ export type AsyncHandler = (
export interface WrapperOptions {
flushTimeout: number;
- // TODO: DEPRECATED - remove `rethrowAfterCapture` in v7
+ /**
+ * @deprecated This option is unused since v6 and will be removed in v8.
+ */
rethrowAfterCapture?: boolean;
callbackWaitsForEmptyEventLoop: boolean;
captureTimeoutWarning: boolean;
@@ -45,6 +47,12 @@ export interface WrapperOptions {
* @default false
*/
captureAllSettledReasons: boolean;
+ /**
+ * Automatically trace all handler invocations.
+ * You may want to disable this if you use express within Lambda (use tracingHandler instead).
+ * @default true
+ */
+ startTrace: boolean;
}
export const defaultIntegrations: Integration[] = [...Sentry.defaultIntegrations, new AWSServices({ optional: true })];
@@ -61,12 +69,13 @@ interface AWSLambdaOptions extends Sentry.NodeOptions {
* @see {@link Sentry.init}
*/
export function init(options: AWSLambdaOptions = {}): void {
- if (options.defaultIntegrations === undefined) {
- options.defaultIntegrations = defaultIntegrations;
- }
+ const opts = {
+ _metadata: {} as SdkMetadata,
+ defaultIntegrations,
+ ...options,
+ };
- options._metadata = options._metadata || {};
- options._metadata.sdk = {
+ opts._metadata.sdk = opts._metadata.sdk || {
name: 'sentry.javascript.serverless',
integrations: ['AWSLambda'],
packages: [
@@ -78,7 +87,7 @@ export function init(options: AWSLambdaOptions = {}): void {
version: Sentry.SDK_VERSION,
};
- Sentry.init(options);
+ Sentry.init(opts);
Sentry.addGlobalEventProcessor(serverlessEventProcessor);
}
@@ -175,11 +184,6 @@ function tryGetRemainingTimeInMillis(context: Context): number {
* @param startTime performance.now() when wrapHandler was invoked
*/
function enhanceScopeWithEnvironmentData(scope: Scope, context: Context, startTime: number): void {
- scope.setTransactionName(context.functionName);
-
- scope.setTag('server_name', process.env._AWS_XRAY_DAEMON_ADDRESS || process.env.SENTRY_NAME || hostname());
- scope.setTag('url', `awslambda:///${context.functionName}`);
-
scope.setContext('aws.lambda', {
aws_request_id: context.awsRequestId,
function_name: context.functionName,
@@ -201,6 +205,18 @@ function enhanceScopeWithEnvironmentData(scope: Scope, context: Context, startTi
});
}
+/**
+ * Adds additional transaction-related information from the environment and AWS Context to the Sentry Scope.
+ *
+ * @param scope Scope that should be enhanced
+ * @param context AWS Lambda context that will be used to extract some part of the data
+ */
+function enhanceScopeWithTransactionData(scope: Scope, context: Context): void {
+ scope.setTransactionName(context.functionName);
+ scope.setTag('server_name', process.env._AWS_XRAY_DAEMON_ADDRESS || process.env.SENTRY_NAME || hostname());
+ scope.setTag('url', `awslambda:///${context.functionName}`);
+}
+
/**
* Wraps a lambda handler adding it error capture and tracing capabilities.
*
@@ -219,6 +235,7 @@ export function wrapHandler(
captureTimeoutWarning: true,
timeoutWarningLimit: 500,
captureAllSettledReasons: false,
+ startTrace: true,
...wrapOptions,
};
let timeoutWarningTimer: NodeJS.Timeout;
@@ -276,36 +293,42 @@ export function wrapHandler(
const hub = getCurrentHub();
- const eventWithHeaders = event as { headers?: { [key: string]: string } };
-
- const sentryTrace =
- eventWithHeaders.headers && isString(eventWithHeaders.headers['sentry-trace'])
- ? eventWithHeaders.headers['sentry-trace']
- : undefined;
- const baggage = eventWithHeaders.headers?.baggage;
- const { traceparentData, dynamicSamplingContext, propagationContext } = tracingContextFromHeaders(
- sentryTrace,
- baggage,
- );
- hub.getScope().setPropagationContext(propagationContext);
-
- const transaction = hub.startTransaction({
- name: context.functionName,
- op: 'function.aws.lambda',
- origin: 'auto.function.serverless',
- ...traceparentData,
- metadata: {
- dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext,
- source: 'component',
- },
- }) as Sentry.Transaction | undefined;
+ let transaction: Sentry.Transaction | undefined;
+ if (options.startTrace) {
+ const eventWithHeaders = event as { headers?: { [key: string]: string } };
+
+ const sentryTrace =
+ eventWithHeaders.headers && isString(eventWithHeaders.headers['sentry-trace'])
+ ? eventWithHeaders.headers['sentry-trace']
+ : undefined;
+ const baggage = eventWithHeaders.headers?.baggage;
+ const { traceparentData, dynamicSamplingContext, propagationContext } = tracingContextFromHeaders(
+ sentryTrace,
+ baggage,
+ );
+ hub.getScope().setPropagationContext(propagationContext);
+
+ transaction = hub.startTransaction({
+ name: context.functionName,
+ op: 'function.aws.lambda',
+ origin: 'auto.function.serverless',
+ ...traceparentData,
+ metadata: {
+ dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext,
+ source: 'component',
+ },
+ });
+ }
const scope = hub.pushScope();
let rv: TResult;
try {
enhanceScopeWithEnvironmentData(scope, context, START_TIME);
- // We put the transaction on the scope so users can attach children to it
- scope.setSpan(transaction);
+ if (options.startTrace) {
+ enhanceScopeWithTransactionData(scope, context);
+ // We put the transaction on the scope so users can attach children to it
+ scope.setSpan(transaction);
+ }
rv = await asyncHandler(event, context);
// We manage lambdas that use Promise.allSettled by capturing the errors of failed promises
diff --git a/packages/serverless/src/gcpfunction/index.ts b/packages/serverless/src/gcpfunction/index.ts
index 12e912d45b77..aa8f800d0d52 100644
--- a/packages/serverless/src/gcpfunction/index.ts
+++ b/packages/serverless/src/gcpfunction/index.ts
@@ -1,5 +1,5 @@
import * as Sentry from '@sentry/node';
-import type { Integration } from '@sentry/types';
+import type { Integration, SdkMetadata } from '@sentry/types';
import { GoogleCloudGrpc } from '../google-cloud-grpc';
import { GoogleCloudHttp } from '../google-cloud-http';
@@ -19,12 +19,13 @@ export const defaultIntegrations: Integration[] = [
* @see {@link Sentry.init}
*/
export function init(options: Sentry.NodeOptions = {}): void {
- if (options.defaultIntegrations === undefined) {
- options.defaultIntegrations = defaultIntegrations;
- }
+ const opts = {
+ _metadata: {} as SdkMetadata,
+ defaultIntegrations,
+ ...options,
+ };
- options._metadata = options._metadata || {};
- options._metadata.sdk = {
+ opts._metadata.sdk = opts._metadata.sdk || {
name: 'sentry.javascript.serverless',
integrations: ['GCPFunction'],
packages: [
@@ -36,6 +37,6 @@ export function init(options: Sentry.NodeOptions = {}): void {
version: Sentry.SDK_VERSION,
};
- Sentry.init(options);
+ Sentry.init(opts);
Sentry.addGlobalEventProcessor(serverlessEventProcessor);
}
diff --git a/packages/serverless/src/index.ts b/packages/serverless/src/index.ts
index a17d0463202d..62fc55012719 100644
--- a/packages/serverless/src/index.ts
+++ b/packages/serverless/src/index.ts
@@ -15,6 +15,7 @@ export {
Scope,
addBreadcrumb,
addGlobalEventProcessor,
+ addIntegration,
autoDiscoverNodePerformanceMonitoringIntegrations,
captureEvent,
captureException,
@@ -56,4 +57,5 @@ export {
startActiveSpan,
startInactiveSpan,
startSpanManual,
+ continueTrace,
} from '@sentry/node';
diff --git a/packages/serverless/test/__mocks__/@sentry/node.ts b/packages/serverless/test/__mocks__/@sentry/node.ts
index 6da20c091780..c29f8f78dd0a 100644
--- a/packages/serverless/test/__mocks__/@sentry/node.ts
+++ b/packages/serverless/test/__mocks__/@sentry/node.ts
@@ -50,6 +50,7 @@ export const resetMocks = (): void => {
fakeHub.pushScope.mockClear();
fakeHub.popScope.mockClear();
fakeHub.getScope.mockClear();
+ fakeHub.startTransaction.mockClear();
fakeScope.addEventProcessor.mockClear();
fakeScope.setTransactionName.mockClear();
diff --git a/packages/serverless/test/awslambda.test.ts b/packages/serverless/test/awslambda.test.ts
index 454b36296adb..199d0ac295ab 100644
--- a/packages/serverless/test/awslambda.test.ts
+++ b/packages/serverless/test/awslambda.test.ts
@@ -45,6 +45,8 @@ function expectScopeSettings(fakeTransactionContext: any) {
// @ts-expect-error see "Why @ts-expect-error" note
const fakeTransaction = { ...SentryNode.fakeTransaction, ...fakeTransactionContext };
// @ts-expect-error see "Why @ts-expect-error" note
+ expect(SentryNode.fakeScope.setTransactionName).toBeCalledWith('functionName');
+ // @ts-expect-error see "Why @ts-expect-error" note
expect(SentryNode.fakeScope.setSpan).toBeCalledWith(fakeTransaction);
// @ts-expect-error see "Why @ts-expect-error" note
expect(SentryNode.fakeScope.setTag).toBeCalledWith('server_name', expect.anything());
@@ -180,11 +182,27 @@ describe('AWSLambda', () => {
expect(SentryNode.captureException).toHaveBeenNthCalledWith(2, error2, expect.any(Function));
expect(SentryNode.captureException).toBeCalledTimes(2);
});
+
+ // "wrapHandler() ... successful execution" tests the default of startTrace enabled
+ test('startTrace disabled', async () => {
+ expect.assertions(3);
+
+ const handler: Handler = async (_event, _context) => 42;
+ const wrappedHandler = wrapHandler(handler, { startTrace: false });
+ await wrappedHandler(fakeEvent, fakeContext, fakeCallback);
+
+ // @ts-expect-error see "Why @ts-expect-error" note
+ expect(SentryNode.fakeScope.setTransactionName).toBeCalledTimes(0);
+ // @ts-expect-error see "Why @ts-expect-error" note
+ expect(SentryNode.fakeScope.setTag).toBeCalledTimes(0);
+ // @ts-expect-error see "Why @ts-expect-error" note
+ expect(SentryNode.fakeHub.startTransaction).toBeCalledTimes(0);
+ });
});
describe('wrapHandler() on sync handler', () => {
test('successful execution', async () => {
- expect.assertions(9);
+ expect.assertions(10);
const handler: Handler = (_event, _context, callback) => {
callback(null, 42);
@@ -209,7 +227,7 @@ describe('AWSLambda', () => {
});
test('unsuccessful execution', async () => {
- expect.assertions(9);
+ expect.assertions(10);
const error = new Error('sorry');
const handler: Handler = (_event, _context, callback) => {
@@ -284,7 +302,7 @@ describe('AWSLambda', () => {
});
test('capture error', async () => {
- expect.assertions(9);
+ expect.assertions(10);
const error = new Error('wat');
const handler: Handler = (_event, _context, _callback) => {
@@ -319,7 +337,7 @@ describe('AWSLambda', () => {
describe('wrapHandler() on async handler', () => {
test('successful execution', async () => {
- expect.assertions(9);
+ expect.assertions(10);
const handler: Handler = async (_event, _context) => {
return 42;
@@ -355,7 +373,7 @@ describe('AWSLambda', () => {
});
test('capture error', async () => {
- expect.assertions(9);
+ expect.assertions(10);
const error = new Error('wat');
const handler: Handler = async (_event, _context) => {
@@ -401,7 +419,7 @@ describe('AWSLambda', () => {
describe('wrapHandler() on async handler with a callback method (aka incorrect usage)', () => {
test('successful execution', async () => {
- expect.assertions(9);
+ expect.assertions(10);
const handler: Handler = async (_event, _context, _callback) => {
return 42;
@@ -437,7 +455,7 @@ describe('AWSLambda', () => {
});
test('capture error', async () => {
- expect.assertions(9);
+ expect.assertions(10);
const error = new Error('wat');
const handler: Handler = async (_event, _context, _callback) => {
diff --git a/packages/svelte/src/sdk.ts b/packages/svelte/src/sdk.ts
index 3a7c671a7d1d..c09e101a72c4 100644
--- a/packages/svelte/src/sdk.ts
+++ b/packages/svelte/src/sdk.ts
@@ -1,13 +1,17 @@
import type { BrowserOptions } from '@sentry/browser';
import { addGlobalEventProcessor, init as browserInit, SDK_VERSION } from '@sentry/browser';
-import type { EventProcessor } from '@sentry/types';
+import type { EventProcessor, SdkMetadata } from '@sentry/types';
import { getDomElement } from '@sentry/utils';
/**
* Inits the Svelte SDK
*/
export function init(options: BrowserOptions): void {
- options._metadata = options._metadata || {};
- options._metadata.sdk = options._metadata.sdk || {
+ const opts = {
+ _metadata: {} as SdkMetadata,
+ ...options,
+ };
+
+ opts._metadata.sdk = opts._metadata.sdk || {
name: 'sentry.javascript.svelte',
packages: [
{
@@ -17,8 +21,7 @@ export function init(options: BrowserOptions): void {
],
version: SDK_VERSION,
};
-
- browserInit(options);
+ browserInit(opts);
detectAndReportSvelteKit();
}
diff --git a/packages/sveltekit/README.md b/packages/sveltekit/README.md
index 5ca2cff3e73d..477d9181b8a5 100644
--- a/packages/sveltekit/README.md
+++ b/packages/sveltekit/README.md
@@ -194,7 +194,7 @@ export default {
### Configuring Source maps upload
Under `sourceMapsUploadOptions`, you can also specify all additional options supported by the
-[Sentry Vite Plugin](https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/main/packages/vite-plugin/README.md#configuration).
+[Sentry Vite Plugin](https://www.npmjs.com/package/@sentry/vite-plugin).
This might be useful if you're using adapters other than the Node adapter or have a more customized build setup.
```js
diff --git a/packages/sveltekit/src/common/metadata.ts b/packages/sveltekit/src/common/metadata.ts
index 76a9642ee36b..d6acf72510cb 100644
--- a/packages/sveltekit/src/common/metadata.ts
+++ b/packages/sveltekit/src/common/metadata.ts
@@ -8,7 +8,7 @@ const PACKAGE_NAME_PREFIX = 'npm:@sentry/';
*
* Note: This function is identical to `buildMetadata` in Remix and NextJS.
* We don't extract it for bundle size reasons.
- * If you make changes to this function consider updating the othera as well.
+ * If you make changes to this function consider updating the others as well.
*
* @param options SDK options object that gets mutated
* @param names list of package names
diff --git a/packages/sveltekit/src/server/handle.ts b/packages/sveltekit/src/server/handle.ts
index 245fcee9658e..5076710970a8 100644
--- a/packages/sveltekit/src/server/handle.ts
+++ b/packages/sveltekit/src/server/handle.ts
@@ -1,12 +1,12 @@
/* eslint-disable @sentry-internal/sdk/no-optional-chaining */
import type { Span } from '@sentry/core';
-import { getActiveTransaction, getCurrentHub, runWithAsyncContext, trace } from '@sentry/core';
+import { getActiveTransaction, getCurrentHub, runWithAsyncContext, startSpan } from '@sentry/core';
import { captureException } from '@sentry/node';
import { addExceptionMechanism, dynamicSamplingContextToSentryBaggageHeader, objectify } from '@sentry/utils';
import type { Handle, ResolveOptions } from '@sveltejs/kit';
import { isHttpError, isRedirect } from '../common/utils';
-import { getTracePropagationData } from './utils';
+import { flushIfServerless, getTracePropagationData } from './utils';
export type SentryHandleOptions = {
/**
@@ -118,7 +118,10 @@ export function sentryHandle(handlerOptions?: SentryHandleOptions): Handle {
return sentryRequestHandler;
}
-function instrumentHandle({ event, resolve }: Parameters[0], options: SentryHandleOptions): ReturnType {
+async function instrumentHandle(
+ { event, resolve }: Parameters[0],
+ options: SentryHandleOptions,
+): Promise {
if (!event.route?.id && !options.handleUnknownRoutes) {
return resolve(event);
}
@@ -126,25 +129,32 @@ function instrumentHandle({ event, resolve }: Parameters[0], options: Se
const { dynamicSamplingContext, traceparentData, propagationContext } = getTracePropagationData(event);
getCurrentHub().getScope().setPropagationContext(propagationContext);
- return trace(
- {
- op: 'http.server',
- origin: 'auto.http.sveltekit',
- name: `${event.request.method} ${event.route?.id || event.url.pathname}`,
- status: 'ok',
- ...traceparentData,
- metadata: {
- source: event.route?.id ? 'route' : 'url',
- dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext,
+ try {
+ const resolveResult = await startSpan(
+ {
+ op: 'http.server',
+ origin: 'auto.http.sveltekit',
+ name: `${event.request.method} ${event.route?.id || event.url.pathname}`,
+ status: 'ok',
+ ...traceparentData,
+ metadata: {
+ source: event.route?.id ? 'route' : 'url',
+ dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext,
+ },
+ },
+ async (span?: Span) => {
+ const res = await resolve(event, { transformPageChunk });
+ if (span) {
+ span.setHttpStatus(res.status);
+ }
+ return res;
},
- },
- async (span?: Span) => {
- const res = await resolve(event, { transformPageChunk });
- if (span) {
- span.setHttpStatus(res.status);
- }
- return res;
- },
- sendErrorToSentry,
- );
+ );
+ return resolveResult;
+ } catch (e: unknown) {
+ sendErrorToSentry(e);
+ throw e;
+ } finally {
+ await flushIfServerless();
+ }
}
diff --git a/packages/sveltekit/src/server/handleError.ts b/packages/sveltekit/src/server/handleError.ts
index 022c1c814930..938cbf612e2f 100644
--- a/packages/sveltekit/src/server/handleError.ts
+++ b/packages/sveltekit/src/server/handleError.ts
@@ -6,6 +6,8 @@ import { addExceptionMechanism } from '@sentry/utils';
// eslint-disable-next-line import/no-unresolved
import type { HandleServerError, RequestEvent } from '@sveltejs/kit';
+import { flushIfServerless } from './utils';
+
// The SvelteKit default error handler just logs the error's stack trace to the console
// see: https://github.com/sveltejs/kit/blob/369e7d6851f543a40c947e033bfc4a9506fdc0a8/packages/kit/src/runtime/server/index.js#L43
function defaultErrorHandler({ error }: Parameters[0]): ReturnType {
@@ -20,7 +22,7 @@ function defaultErrorHandler({ error }: Parameters[0]): Retur
* @param handleError The original SvelteKit error handler.
*/
export function handleErrorWithSentry(handleError: HandleServerError = defaultErrorHandler): HandleServerError {
- return (input: { error: unknown; event: RequestEvent }): ReturnType => {
+ return async (input: { error: unknown; event: RequestEvent }): Promise => {
if (isNotFoundError(input)) {
return handleError(input);
}
@@ -36,6 +38,8 @@ export function handleErrorWithSentry(handleError: HandleServerError = defaultEr
return scope;
});
+ await flushIfServerless();
+
return handleError(input);
};
}
diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts
index 90c651a41175..f81cedd8444b 100644
--- a/packages/sveltekit/src/server/index.ts
+++ b/packages/sveltekit/src/server/index.ts
@@ -6,6 +6,7 @@
export {
addGlobalEventProcessor,
addBreadcrumb,
+ addIntegration,
captureException,
captureEvent,
captureMessage,
@@ -51,6 +52,7 @@ export {
startActiveSpan,
startInactiveSpan,
startSpanManual,
+ continueTrace,
} from '@sentry/node';
// We can still leave this for the carrier init and type exports
diff --git a/packages/sveltekit/src/server/load.ts b/packages/sveltekit/src/server/load.ts
index c286ad5e3834..e819c434e81b 100644
--- a/packages/sveltekit/src/server/load.ts
+++ b/packages/sveltekit/src/server/load.ts
@@ -1,5 +1,5 @@
/* eslint-disable @sentry-internal/sdk/no-optional-chaining */
-import { getCurrentHub, trace } from '@sentry/core';
+import { getCurrentHub, startSpan } from '@sentry/core';
import { captureException } from '@sentry/node';
import type { TransactionContext } from '@sentry/types';
import { addExceptionMechanism, addNonEnumerableProperty, objectify } from '@sentry/utils';
@@ -7,7 +7,7 @@ import type { LoadEvent, ServerLoadEvent } from '@sveltejs/kit';
import type { SentryWrappedFlag } from '../common/utils';
import { isHttpError, isRedirect } from '../common/utils';
-import { getTracePropagationData } from './utils';
+import { flushIfServerless, getTracePropagationData } from './utils';
type PatchedLoadEvent = LoadEvent & SentryWrappedFlag;
type PatchedServerLoadEvent = ServerLoadEvent & SentryWrappedFlag;
@@ -57,7 +57,7 @@ function sendErrorToSentry(e: unknown): unknown {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function wrapLoadWithSentry any>(origLoad: T): T {
return new Proxy(origLoad, {
- apply: (wrappingTarget, thisArg, args: Parameters) => {
+ apply: async (wrappingTarget, thisArg, args: Parameters) => {
// Type casting here because `T` cannot extend `Load` (see comment above function signature)
// Also, this event possibly already has a sentry wrapped flag attached
const event = args[0] as PatchedLoadEvent;
@@ -80,7 +80,15 @@ export function wrapLoadWithSentry any>(origLoad: T)
},
};
- return trace(traceLoadContext, () => wrappingTarget.apply(thisArg, args), sendErrorToSentry);
+ try {
+ // We need to await before returning, otherwise we won't catch any errors thrown by the load function
+ return await startSpan(traceLoadContext, () => wrappingTarget.apply(thisArg, args));
+ } catch (e) {
+ sendErrorToSentry(e);
+ throw e;
+ } finally {
+ await flushIfServerless();
+ }
},
});
}
@@ -109,7 +117,7 @@ export function wrapLoadWithSentry any>(origLoad: T)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function wrapServerLoadWithSentry any>(origServerLoad: T): T {
return new Proxy(origServerLoad, {
- apply: (wrappingTarget, thisArg, args: Parameters) => {
+ apply: async (wrappingTarget, thisArg, args: Parameters) => {
// Type casting here because `T` cannot extend `ServerLoad` (see comment above function signature)
// Also, this event possibly already has a sentry wrapped flag attached
const event = args[0] as PatchedServerLoadEvent;
@@ -144,7 +152,15 @@ export function wrapServerLoadWithSentry any>(origSe
...traceparentData,
};
- return trace(traceLoadContext, () => wrappingTarget.apply(thisArg, args), sendErrorToSentry);
+ try {
+ // We need to await before returning, otherwise we won't catch any errors thrown by the load function
+ return await startSpan(traceLoadContext, () => wrappingTarget.apply(thisArg, args));
+ } catch (e: unknown) {
+ sendErrorToSentry(e);
+ throw e;
+ } finally {
+ await flushIfServerless();
+ }
},
});
}
diff --git a/packages/sveltekit/src/server/utils.ts b/packages/sveltekit/src/server/utils.ts
index 1a9e1781643c..cf591568486b 100644
--- a/packages/sveltekit/src/server/utils.ts
+++ b/packages/sveltekit/src/server/utils.ts
@@ -1,5 +1,6 @@
+import { flush } from '@sentry/node';
import type { StackFrame } from '@sentry/types';
-import { basename, escapeStringForRegex, GLOBAL_OBJ, join, tracingContextFromHeaders } from '@sentry/utils';
+import { basename, escapeStringForRegex, GLOBAL_OBJ, join, logger, tracingContextFromHeaders } from '@sentry/utils';
import type { RequestEvent } from '@sveltejs/kit';
import { WRAPPED_MODULE_SUFFIX } from '../vite/autoInstrument';
@@ -68,3 +69,18 @@ export function rewriteFramesIteratee(frame: StackFrame): StackFrame {
return frame;
}
+
+/** Flush the event queue to ensure that events get sent to Sentry before the response is finished and the lambda ends */
+export async function flushIfServerless(): Promise {
+ const platformSupportsStreaming = !process.env.LAMBDA_TASK_ROOT && !process.env.VERCEL;
+
+ if (!platformSupportsStreaming) {
+ try {
+ __DEBUG_BUILD__ && logger.log('Flushing events...');
+ await flush(2000);
+ __DEBUG_BUILD__ && logger.log('Done flushing events');
+ } catch (e) {
+ __DEBUG_BUILD__ && logger.log('Error while flushing events:\n', e);
+ }
+ }
+}
diff --git a/packages/sveltekit/test/server/load.test.ts b/packages/sveltekit/test/server/load.test.ts
index c2b35bb7d2e9..e68e075c7ebd 100644
--- a/packages/sveltekit/test/server/load.test.ts
+++ b/packages/sveltekit/test/server/load.test.ts
@@ -21,26 +21,28 @@ vi.mock('@sentry/node', async () => {
};
});
-const mockTrace = vi.fn();
+const mockStartSpan = vi.fn();
vi.mock('@sentry/core', async () => {
const original = (await vi.importActual('@sentry/core')) as any;
return {
...original,
- trace: (...args: unknown[]) => {
- mockTrace(...args);
- return original.trace(...args);
+ startSpan: (...args: unknown[]) => {
+ mockStartSpan(...args);
+ return original.startSpan(...args);
},
};
});
-const mockAddExceptionMechanism = vi.fn();
+const mockAddExceptionMechanism = vi.fn((_e, _m) => {});
vi.mock('@sentry/utils', async () => {
const original = (await vi.importActual('@sentry/utils')) as any;
return {
...original,
- addExceptionMechanism: (...args: unknown[]) => mockAddExceptionMechanism(...args),
+ addExceptionMechanism: (...args: unknown[]) => {
+ return mockAddExceptionMechanism(args[0], args[1]);
+ },
};
});
@@ -127,10 +129,10 @@ beforeAll(() => {
addTracingExtensions();
});
-beforeEach(() => {
+afterEach(() => {
mockCaptureException.mockClear();
mockAddExceptionMechanism.mockClear();
- mockTrace.mockClear();
+ mockStartSpan.mockClear();
mockScope = new Scope();
});
@@ -203,11 +205,11 @@ describe.each([
};
}
- const wrappedLoad = sentryLoadWrapperFn.call(this, load);
+ const wrappedLoad = sentryLoadWrapperFn(load);
const res = wrappedLoad(getServerOnlyArgs());
await expect(res).rejects.toThrow();
- expect(addEventProcessorSpy).toBeCalledTimes(1);
+ expect(addEventProcessorSpy).toHaveBeenCalledTimes(1);
expect(mockAddExceptionMechanism).toBeCalledTimes(1);
expect(mockAddExceptionMechanism).toBeCalledWith(
{},
@@ -226,8 +228,8 @@ describe('wrapLoadWithSentry calls trace', () => {
const wrappedLoad = wrapLoadWithSentry(load);
await wrappedLoad(getLoadArgs());
- expect(mockTrace).toHaveBeenCalledTimes(1);
- expect(mockTrace).toHaveBeenCalledWith(
+ expect(mockStartSpan).toHaveBeenCalledTimes(1);
+ expect(mockStartSpan).toHaveBeenCalledWith(
{
op: 'function.sveltekit.load',
origin: 'auto.function.sveltekit',
@@ -238,7 +240,6 @@ describe('wrapLoadWithSentry calls trace', () => {
},
},
expect.any(Function),
- expect.any(Function),
);
});
@@ -246,8 +247,8 @@ describe('wrapLoadWithSentry calls trace', () => {
const wrappedLoad = wrapLoadWithSentry(load);
await wrappedLoad(getLoadArgsWithoutRoute());
- expect(mockTrace).toHaveBeenCalledTimes(1);
- expect(mockTrace).toHaveBeenCalledWith(
+ expect(mockStartSpan).toHaveBeenCalledTimes(1);
+ expect(mockStartSpan).toHaveBeenCalledWith(
{
op: 'function.sveltekit.load',
origin: 'auto.function.sveltekit',
@@ -258,7 +259,6 @@ describe('wrapLoadWithSentry calls trace', () => {
},
},
expect.any(Function),
- expect.any(Function),
);
});
@@ -266,7 +266,7 @@ describe('wrapLoadWithSentry calls trace', () => {
const wrappedLoad = wrapLoadWithSentry(wrapLoadWithSentry(wrapLoadWithSentry(load)));
await wrappedLoad(getLoadArgs());
- expect(mockTrace).toHaveBeenCalledTimes(1);
+ expect(mockStartSpan).toHaveBeenCalledTimes(1);
});
});
@@ -281,8 +281,8 @@ describe('wrapServerLoadWithSentry calls trace', () => {
const wrappedLoad = wrapServerLoadWithSentry(serverLoad);
await wrappedLoad(getServerOnlyArgs());
- expect(mockTrace).toHaveBeenCalledTimes(1);
- expect(mockTrace).toHaveBeenCalledWith(
+ expect(mockStartSpan).toHaveBeenCalledTimes(1);
+ expect(mockStartSpan).toHaveBeenCalledWith(
{
op: 'function.sveltekit.server.load',
origin: 'auto.function.sveltekit',
@@ -308,7 +308,6 @@ describe('wrapServerLoadWithSentry calls trace', () => {
},
},
expect.any(Function),
- expect.any(Function),
);
});
@@ -316,8 +315,8 @@ describe('wrapServerLoadWithSentry calls trace', () => {
const wrappedLoad = wrapServerLoadWithSentry(serverLoad);
await wrappedLoad(getServerArgsWithoutTracingHeaders());
- expect(mockTrace).toHaveBeenCalledTimes(1);
- expect(mockTrace).toHaveBeenCalledWith(
+ expect(mockStartSpan).toHaveBeenCalledTimes(1);
+ expect(mockStartSpan).toHaveBeenCalledWith(
{
op: 'function.sveltekit.server.load',
origin: 'auto.function.sveltekit',
@@ -331,7 +330,6 @@ describe('wrapServerLoadWithSentry calls trace', () => {
},
},
expect.any(Function),
- expect.any(Function),
);
});
@@ -339,8 +337,8 @@ describe('wrapServerLoadWithSentry calls trace', () => {
const wrappedLoad = wrapServerLoadWithSentry(serverLoad);
await wrappedLoad(getServerArgsWithoutBaggageHeader());
- expect(mockTrace).toHaveBeenCalledTimes(1);
- expect(mockTrace).toHaveBeenCalledWith(
+ expect(mockStartSpan).toHaveBeenCalledTimes(1);
+ expect(mockStartSpan).toHaveBeenCalledWith(
{
op: 'function.sveltekit.server.load',
origin: 'auto.function.sveltekit',
@@ -358,7 +356,6 @@ describe('wrapServerLoadWithSentry calls trace', () => {
},
},
expect.any(Function),
- expect.any(Function),
);
});
@@ -369,8 +366,8 @@ describe('wrapServerLoadWithSentry calls trace', () => {
const wrappedLoad = wrapServerLoadWithSentry(serverLoad);
await wrappedLoad(event);
- expect(mockTrace).toHaveBeenCalledTimes(1);
- expect(mockTrace).toHaveBeenCalledWith(
+ expect(mockStartSpan).toHaveBeenCalledTimes(1);
+ expect(mockStartSpan).toHaveBeenCalledWith(
{
op: 'function.sveltekit.server.load',
origin: 'auto.function.sveltekit',
@@ -396,7 +393,6 @@ describe('wrapServerLoadWithSentry calls trace', () => {
},
},
expect.any(Function),
- expect.any(Function),
);
});
@@ -404,7 +400,7 @@ describe('wrapServerLoadWithSentry calls trace', () => {
const wrappedLoad = wrapServerLoadWithSentry(wrapServerLoadWithSentry(serverLoad));
await wrappedLoad(getServerOnlyArgs());
- expect(mockTrace).toHaveBeenCalledTimes(1);
+ expect(mockStartSpan).toHaveBeenCalledTimes(1);
});
it("doesn't invoke the proxy set on `event.route`", async () => {
@@ -423,14 +419,13 @@ describe('wrapServerLoadWithSentry calls trace', () => {
const wrappedLoad = wrapServerLoadWithSentry(serverLoad);
await wrappedLoad(event);
- expect(mockTrace).toHaveBeenCalledTimes(1);
- expect(mockTrace).toHaveBeenCalledWith(
+ expect(mockStartSpan).toHaveBeenCalledTimes(1);
+ expect(mockStartSpan).toHaveBeenCalledWith(
expect.objectContaining({
op: 'function.sveltekit.server.load',
name: '/users/[id]', // <-- this shows that the route was still accessed
}),
expect.any(Function),
- expect.any(Function),
);
expect(proxyFn).not.toHaveBeenCalled();
diff --git a/packages/tracing-internal/package.json b/packages/tracing-internal/package.json
index 4818702c81bb..266a5cce9183 100644
--- a/packages/tracing-internal/package.json
+++ b/packages/tracing-internal/package.json
@@ -43,7 +43,7 @@
"build:transpile:watch": "rollup -c rollup.npm.config.js --watch",
"build:types:watch": "tsc -p tsconfig.types.json --watch",
"build:tarball": "ts-node ../../scripts/prepack.ts && npm pack ./build",
- "clean": "rimraf build coverage sentry-tracing-*.tgz",
+ "clean": "rimraf build coverage sentry-internal-tracing-*.tgz",
"fix": "run-s fix:eslint fix:prettier",
"fix:eslint": "eslint . --format stylish --fix",
"fix:prettier": "prettier --write \"{src,test,scripts}/**/**.ts\"",
diff --git a/packages/tracing-internal/src/node/integrations/mysql.ts b/packages/tracing-internal/src/node/integrations/mysql.ts
index 748c43ec51b2..d176730f5847 100644
--- a/packages/tracing-internal/src/node/integrations/mysql.ts
+++ b/packages/tracing-internal/src/node/integrations/mysql.ts
@@ -84,7 +84,7 @@ export class Mysql implements LazyLoadedIntegration {
}
function finishSpan(span: Span | undefined): void {
- if (!span) {
+ if (!span || span.endTimestamp) {
return;
}
@@ -128,9 +128,14 @@ export class Mysql implements LazyLoadedIntegration {
});
}
- return orig.call(this, options, values, function () {
+ // streaming, no callback!
+ const query = orig.call(this, options, values) as { on: (event: string, callback: () => void) => void };
+
+ query.on('end', () => {
finishSpan(span);
});
+
+ return query;
};
});
}
diff --git a/packages/tracing-internal/src/node/integrations/postgres.ts b/packages/tracing-internal/src/node/integrations/postgres.ts
index 4f3e2a94fc11..a3d94653477f 100644
--- a/packages/tracing-internal/src/node/integrations/postgres.ts
+++ b/packages/tracing-internal/src/node/integrations/postgres.ts
@@ -5,9 +5,15 @@ import { fill, isThenable, loadModule, logger } from '@sentry/utils';
import type { LazyLoadedIntegration } from './lazy';
import { shouldDisableAutoInstrumentation } from './utils/node-utils';
+type PgClientQuery = (
+ config: unknown,
+ values?: unknown,
+ callback?: (err: unknown, result: unknown) => void,
+) => void | Promise;
+
interface PgClient {
prototype: {
- query: () => void | Promise;
+ query: PgClientQuery;
};
}
@@ -20,9 +26,23 @@ interface PgClientThis {
interface PgOptions {
usePgNative?: boolean;
+ /**
+ * Supply your postgres module directly, instead of having Sentry attempt automatic resolution.
+ * Use this if you (a) use a module that's not `pg`, or (b) use a bundler that breaks resolution (e.g. esbuild).
+ *
+ * Usage:
+ * ```
+ * import pg from 'pg';
+ *
+ * Sentry.init({
+ * integrations: [new Sentry.Integrations.Postgres({ module: pg })],
+ * });
+ * ```
+ */
+ module?: PGModule;
}
-type PGModule = { Client: PgClient; native: { Client: PgClient } };
+type PGModule = { Client: PgClient; native: { Client: PgClient } | null };
/** Tracing integration for node-postgres package */
export class Postgres implements LazyLoadedIntegration {
@@ -43,6 +63,7 @@ export class Postgres implements LazyLoadedIntegration {
public constructor(options: PgOptions = {}) {
this.name = Postgres.id;
this._usePgNative = !!options.usePgNative;
+ this._module = options.module;
}
/** @inheritdoc */
@@ -66,13 +87,13 @@ export class Postgres implements LazyLoadedIntegration {
return;
}
- if (this._usePgNative && !pkg.native?.Client) {
+ const Client = this._usePgNative ? pkg.native?.Client : pkg.Client;
+
+ if (!Client) {
__DEBUG_BUILD__ && logger.error("Postgres Integration was unable to access 'pg-native' bindings.");
return;
}
- const { Client } = this._usePgNative ? pkg.native : pkg;
-
/**
* function (query, callback) => void
* function (query, params, callback) => void
@@ -80,7 +101,7 @@ export class Postgres implements LazyLoadedIntegration {
* function (query, params) => Promise
* function (pg.Cursor) => pg.Cursor
*/
- fill(Client.prototype, 'query', function (orig: () => void | Promise) {
+ fill(Client.prototype, 'query', function (orig: PgClientQuery) {
return function (this: PgClientThis, config: unknown, values: unknown, callback: unknown) {
const scope = getCurrentHub().getScope();
const parentSpan = scope.getSpan();
diff --git a/packages/tracing-internal/test/browser/backgroundtab.test.ts b/packages/tracing-internal/test/browser/backgroundtab.test.ts
index 3903f2eb2406..031d68d01d78 100644
--- a/packages/tracing-internal/test/browser/backgroundtab.test.ts
+++ b/packages/tracing-internal/test/browser/backgroundtab.test.ts
@@ -33,7 +33,7 @@ conditionalTest({ min: 10 })('registerBackgroundTabDetection', () => {
hub.configureScope(scope => scope.setSpan(undefined));
});
- it('does not creates an event listener if global document is undefined', () => {
+ it('does not create an event listener if global document is undefined', () => {
// @ts-expect-error need to override global document
global.document = undefined;
registerBackgroundTabDetection();
diff --git a/packages/tracing/test/integrations/node/postgres.test.ts b/packages/tracing/test/integrations/node/postgres.test.ts
index bb735ca40d8b..446d837d22d7 100644
--- a/packages/tracing/test/integrations/node/postgres.test.ts
+++ b/packages/tracing/test/integrations/node/postgres.test.ts
@@ -1,16 +1,17 @@
/* eslint-disable deprecation/deprecation */
/* eslint-disable @typescript-eslint/unbound-method */
import { Hub, Scope } from '@sentry/core';
-import { logger } from '@sentry/utils';
+import { loadModule, logger } from '@sentry/utils';
+import pg from 'pg';
import { Integrations, Span } from '../../../src';
import { getTestClient } from '../../testutils';
class PgClient {
// https://node-postgres.com/api/client#clientquery
- public query(_text: unknown, values: unknown, callback?: () => void) {
+ public query(_text: unknown, values: unknown, callback?: (err: unknown, result: unknown) => void) {
if (typeof callback === 'function') {
- callback();
+ callback(null, null);
return;
}
@@ -25,25 +26,28 @@ class PgClient {
// Jest mocks get hoisted. vars starting with `mock` are hoisted before imports.
/* eslint-disable no-var */
-var mockClient = PgClient;
+var mockModule = {
+ Client: PgClient,
+ native: {
+ Client: PgClient,
+ },
+};
// mock for 'pg' / 'pg-native' package
jest.mock('@sentry/utils', () => {
const actual = jest.requireActual('@sentry/utils');
return {
...actual,
- loadModule() {
- return {
- Client: mockClient,
- native: {
- Client: mockClient,
- },
- };
- },
+ loadModule: jest.fn(() => mockModule),
};
});
describe('setupOnce', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ jest.resetAllMocks();
+ });
+
['pg', 'pg-native'].forEach(pgApi => {
const Client: PgClient = new PgClient();
let scope = new Scope();
@@ -127,4 +131,23 @@ describe('setupOnce', () => {
expect(loggerLogSpy).toBeCalledWith('Postgres Integration is skipped because of instrumenter configuration.');
});
+
+ it('does not attempt resolution when module is passed directly', async () => {
+ const scope = new Scope();
+ jest.spyOn(scope, 'getSpan').mockReturnValueOnce(new Span());
+
+ new Integrations.Postgres({ module: mockModule }).setupOnce(
+ () => undefined,
+ () => new Hub(undefined, scope),
+ );
+
+ await new PgClient().query('SELECT NOW()', null);
+
+ expect(loadModule).not.toBeCalled();
+ expect(scope.getSpan).toBeCalled();
+ });
+
+ it('has valid module type', () => {
+ expect(() => new Integrations.Postgres({ module: pg })).not.toThrow();
+ });
});
diff --git a/packages/types/src/integration.ts b/packages/types/src/integration.ts
index 19df0b9e67c2..0e60b7a530ee 100644
--- a/packages/types/src/integration.ts
+++ b/packages/types/src/integration.ts
@@ -36,5 +36,5 @@ export interface Integration {
* Return `null` to drop the event, or mutate the event & return it.
* This receives the client that the integration was installed for as third argument.
*/
- processEvent?(event: Event, hint: EventHint | undefined, client: Client): Event | null | PromiseLike;
+ processEvent?(event: Event, hint: EventHint, client: Client): Event | null | PromiseLike;
}
diff --git a/packages/utils/src/anr.ts b/packages/utils/src/anr.ts
new file mode 100644
index 000000000000..b007c1355cb7
--- /dev/null
+++ b/packages/utils/src/anr.ts
@@ -0,0 +1,133 @@
+import type { StackFrame } from '@sentry/types';
+
+import { dropUndefinedKeys } from './object';
+import { filenameIsInApp, stripSentryFramesAndReverse } from './stacktrace';
+
+type WatchdogReturn = {
+ /** Resets the watchdog timer */
+ poll: () => void;
+ /** Enables or disables the watchdog timer */
+ enabled: (state: boolean) => void;
+};
+
+/**
+ * A node.js watchdog timer
+ * @param pollInterval The interval that we expect to get polled at
+ * @param anrThreshold The threshold for when we consider ANR
+ * @param callback The callback to call for ANR
+ * @returns An object with `poll` and `enabled` functions {@link WatchdogReturn}
+ */
+export function watchdogTimer(pollInterval: number, anrThreshold: number, callback: () => void): WatchdogReturn {
+ let lastPoll = process.hrtime();
+ let triggered = false;
+ let enabled = true;
+
+ setInterval(() => {
+ const [seconds, nanoSeconds] = process.hrtime(lastPoll);
+ const diffMs = Math.floor(seconds * 1e3 + nanoSeconds / 1e6);
+
+ if (triggered === false && diffMs > pollInterval + anrThreshold) {
+ triggered = true;
+ if (enabled) {
+ callback();
+ }
+ }
+
+ if (diffMs < pollInterval + anrThreshold) {
+ triggered = false;
+ }
+ }, 20);
+
+ return {
+ poll: () => {
+ lastPoll = process.hrtime();
+ },
+ enabled: (state: boolean) => {
+ enabled = state;
+ },
+ };
+}
+
+// types copied from inspector.d.ts
+interface Location {
+ scriptId: string;
+ lineNumber: number;
+ columnNumber?: number;
+}
+
+interface CallFrame {
+ functionName: string;
+ location: Location;
+ url: string;
+}
+
+interface ScriptParsedEventDataType {
+ scriptId: string;
+ url: string;
+}
+
+interface PausedEventDataType {
+ callFrames: CallFrame[];
+ reason: string;
+}
+
+/**
+ * Converts Debugger.CallFrame to Sentry StackFrame
+ */
+function callFrameToStackFrame(
+ frame: CallFrame,
+ url: string | undefined,
+ getModuleFromFilename: (filename: string | undefined) => string | undefined,
+): StackFrame {
+ const filename = url ? url.replace(/^file:\/\//, '') : undefined;
+
+ // CallFrame row/col are 0 based, whereas StackFrame are 1 based
+ const colno = frame.location.columnNumber ? frame.location.columnNumber + 1 : undefined;
+ const lineno = frame.location.lineNumber ? frame.location.lineNumber + 1 : undefined;
+
+ return dropUndefinedKeys({
+ filename,
+ module: getModuleFromFilename(filename),
+ function: frame.functionName || '?',
+ colno,
+ lineno,
+ in_app: filename ? filenameIsInApp(filename) : undefined,
+ });
+}
+
+// The only messages we care about
+type DebugMessage =
+ | { method: 'Debugger.scriptParsed'; params: ScriptParsedEventDataType }
+ | { method: 'Debugger.paused'; params: PausedEventDataType };
+
+/**
+ * Creates a message handler from the v8 debugger protocol and passed stack frames to the callback when paused.
+ */
+export function createDebugPauseMessageHandler(
+ sendCommand: (message: string) => void,
+ getModuleFromFilename: (filename?: string) => string | undefined,
+ pausedStackFrames: (frames: StackFrame[]) => void,
+): (message: DebugMessage) => void {
+ // Collect scriptId -> url map so we can look up the filenames later
+ const scripts = new Map();
+
+ return message => {
+ if (message.method === 'Debugger.scriptParsed') {
+ scripts.set(message.params.scriptId, message.params.url);
+ } else if (message.method === 'Debugger.paused') {
+ // copy the frames
+ const callFrames = [...message.params.callFrames];
+ // and resume immediately
+ sendCommand('Debugger.resume');
+ sendCommand('Debugger.disable');
+
+ const stackFrames = stripSentryFramesAndReverse(
+ callFrames.map(frame =>
+ callFrameToStackFrame(frame, scripts.get(frame.location.scriptId), getModuleFromFilename),
+ ),
+ );
+
+ pausedStackFrames(stackFrames);
+ }
+ };
+}
diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts
index 8de4941f6b96..81f4d947cd0d 100644
--- a/packages/utils/src/index.ts
+++ b/packages/utils/src/index.ts
@@ -31,3 +31,4 @@ export * from './url';
export * from './userIntegrations';
export * from './cache';
export * from './eventbuilder';
+export * from './anr';
diff --git a/packages/utils/src/instrument.ts b/packages/utils/src/instrument.ts
index f3364ba92b9c..99ddc3aaefc8 100644
--- a/packages/utils/src/instrument.ts
+++ b/packages/utils/src/instrument.ts
@@ -12,7 +12,7 @@ import type {
import { isString } from './is';
import type { ConsoleLevel } from './logger';
import { CONSOLE_LEVELS, logger, originalConsoleMethods } from './logger';
-import { fill } from './object';
+import { addNonEnumerableProperty, fill } from './object';
import { getFunctionName } from './stacktrace';
import { supportsHistory, supportsNativeFetch } from './supports';
import { getGlobalObject, GLOBAL_OBJ } from './worldwide';
@@ -400,31 +400,24 @@ function instrumentHistory(): void {
fill(WINDOW.history, 'replaceState', historyReplacementFunction);
}
-const debounceDuration = 1000;
+const DEBOUNCE_DURATION = 1000;
let debounceTimerID: number | undefined;
let lastCapturedEvent: Event | undefined;
/**
- * Decide whether the current event should finish the debounce of previously captured one.
- * @param previous previously captured event
- * @param current event to be captured
+ * Check whether two DOM events are similar to eachother. For example, two click events on the same button.
*/
-function shouldShortcircuitPreviousDebounce(previous: Event | undefined, current: Event): boolean {
- // If there was no previous event, it should always be swapped for the new one.
- if (!previous) {
- return true;
- }
-
+function areSimilarDomEvents(a: Event, b: Event): boolean {
// If both events have different type, then user definitely performed two separate actions. e.g. click + keypress.
- if (previous.type !== current.type) {
- return true;
+ if (a.type !== b.type) {
+ return false;
}
try {
// If both events have the same type, it's still possible that actions were performed on different targets.
// e.g. 2 clicks on different buttons.
- if (previous.target !== current.target) {
- return true;
+ if (a.target !== b.target) {
+ return false;
}
} catch (e) {
// just accessing `target` property can throw an exception in some rare circumstances
@@ -434,7 +427,7 @@ function shouldShortcircuitPreviousDebounce(previous: Event | undefined, current
// If both events have the same type _and_ same `target` (an element which triggered an event, _not necessarily_
// to which an event listener was attached), we treat them as the same action, as we want to capture
// only one breadcrumb. e.g. multiple clicks on the same button, or typing inside a user input box.
- return false;
+ return true;
}
/**
@@ -475,11 +468,11 @@ function shouldSkipDOMEvent(event: Event): boolean {
* @hidden
*/
function makeDOMEventHandler(handler: Function, globalListener: boolean = false): (event: Event) => void {
- return (event: Event): void => {
+ return (event: Event & { _sentryCaptured?: true }): void => {
// It's possible this handler might trigger multiple times for the same
// event (e.g. event propagation through node ancestors).
// Ignore if we've already captured that event.
- if (!event || lastCapturedEvent === event) {
+ if (!event || event['_sentryCaptured']) {
return;
}
@@ -488,20 +481,15 @@ function makeDOMEventHandler(handler: Function, globalListener: boolean = false)
return;
}
+ // Mark event as "seen"
+ addNonEnumerableProperty(event, '_sentryCaptured', true);
+
const name = event.type === 'keypress' ? 'input' : event.type;
- // If there is no debounce timer, it means that we can safely capture the new event and store it for future comparisons.
- if (debounceTimerID === undefined) {
- handler({
- event: event,
- name,
- global: globalListener,
- });
- lastCapturedEvent = event;
- }
- // If there is a debounce awaiting, see if the new event is different enough to treat it as a unique one.
+ // If there is no last captured event, it means that we can safely capture the new event and store it for future comparisons.
+ // If there is a last captured event, see if the new event is different enough to treat it as a unique one.
// If that's the case, emit the previous event and store locally the newly-captured DOM event.
- else if (shouldShortcircuitPreviousDebounce(lastCapturedEvent, event)) {
+ if (lastCapturedEvent === undefined || !areSimilarDomEvents(lastCapturedEvent, event)) {
handler({
event: event,
name,
@@ -513,8 +501,8 @@ function makeDOMEventHandler(handler: Function, globalListener: boolean = false)
// Start a new debounce timer that will prevent us from capturing multiple events that should be grouped together.
clearTimeout(debounceTimerID);
debounceTimerID = WINDOW.setTimeout(() => {
- debounceTimerID = undefined;
- }, debounceDuration);
+ lastCapturedEvent = undefined;
+ }, DEBOUNCE_DURATION);
};
}
diff --git a/packages/utils/src/logger.ts b/packages/utils/src/logger.ts
index 07642b8c4f04..9d37855e9114 100644
--- a/packages/utils/src/logger.ts
+++ b/packages/utils/src/logger.ts
@@ -19,6 +19,7 @@ export const originalConsoleMethods: {
interface Logger extends LoggerConsoleMethods {
disable(): void;
enable(): void;
+ isEnabled(): boolean;
}
/**
@@ -63,6 +64,7 @@ function makeLogger(): Logger {
disable: () => {
enabled = false;
},
+ isEnabled: () => enabled,
};
if (__DEBUG_BUILD__) {
diff --git a/packages/utils/src/normalize.ts b/packages/utils/src/normalize.ts
index 7c1adaa32ccc..5445cc33ba58 100644
--- a/packages/utils/src/normalize.ts
+++ b/packages/utils/src/normalize.ts
@@ -169,7 +169,9 @@ function visit(
return normalized;
}
-// TODO remove this in v7 (this means the method will no longer be exported, under any name)
+/**
+ * @deprecated This export will be removed in v8.
+ */
export { visit as walk };
/* eslint-disable complexity */
diff --git a/packages/utils/src/object.ts b/packages/utils/src/object.ts
index e705214f950d..e4e866bf2763 100644
--- a/packages/utils/src/object.ts
+++ b/packages/utils/src/object.ts
@@ -42,7 +42,7 @@ export function fill(source: { [key: string]: any }, name: string, replacementFa
* @param name The name of the property to be set
* @param value The value to which to set the property
*/
-export function addNonEnumerableProperty(obj: { [key: string]: unknown }, name: string, value: unknown): void {
+export function addNonEnumerableProperty(obj: object, name: string, value: unknown): void {
try {
Object.defineProperty(obj, name, {
// enumerable: false, // the default, so we can save on bundle size by not explicitly setting it
diff --git a/packages/vercel-edge/package.json b/packages/vercel-edge/package.json
index bd72bbb9a0ab..b3d945ca9818 100644
--- a/packages/vercel-edge/package.json
+++ b/packages/vercel-edge/package.json
@@ -45,7 +45,7 @@
"build:types:watch": "tsc -p tsconfig.types.json --watch",
"build:tarball": "ts-node ../../scripts/prepack.ts && npm pack ./build",
"circularDepCheck": "madge --circular src/index.ts",
- "clean": "rimraf build coverage sentry-core-*.tgz",
+ "clean": "rimraf build coverage sentry-vercel-edge-*.tgz",
"fix": "run-s fix:eslint fix:prettier",
"fix:eslint": "eslint . --format stylish --fix",
"fix:prettier": "prettier --write \"{src,test,scripts}/**/**.ts\"",
diff --git a/packages/vercel-edge/src/index.ts b/packages/vercel-edge/src/index.ts
index cd596269a36f..bba58f568db0 100644
--- a/packages/vercel-edge/src/index.ts
+++ b/packages/vercel-edge/src/index.ts
@@ -25,6 +25,7 @@ export type { VercelEdgeOptions } from './types';
export {
addGlobalEventProcessor,
addBreadcrumb,
+ addIntegration,
captureException,
captureEvent,
captureMessage,
@@ -58,6 +59,7 @@ export {
startSpan,
startInactiveSpan,
startSpanManual,
+ continueTrace,
} from '@sentry/core';
export type { SpanStatusType } from '@sentry/core';
diff --git a/packages/vercel-edge/src/transports/index.ts b/packages/vercel-edge/src/transports/index.ts
index a479425f96e6..d73e7fd4341b 100644
--- a/packages/vercel-edge/src/transports/index.ts
+++ b/packages/vercel-edge/src/transports/index.ts
@@ -83,7 +83,6 @@ export function makeEdgeTransport(options: VercelEdgeTransportOptions): Transpor
const requestOptions: RequestInit = {
body: request.body,
method: 'POST',
- referrerPolicy: 'origin',
headers: options.headers,
...options.fetchOptions,
};
diff --git a/packages/vercel-edge/test/transports/index.test.ts b/packages/vercel-edge/test/transports/index.test.ts
index cab31eca5bf2..da0ab1389325 100644
--- a/packages/vercel-edge/test/transports/index.test.ts
+++ b/packages/vercel-edge/test/transports/index.test.ts
@@ -57,7 +57,6 @@ describe('Edge Transport', () => {
expect(mockFetch).toHaveBeenLastCalledWith(DEFAULT_EDGE_TRANSPORT_OPTIONS.url, {
body: serializeEnvelope(ERROR_ENVELOPE, new TextEncoder()),
method: 'POST',
- referrerPolicy: 'origin',
});
});
diff --git a/packages/vue/src/errorhandler.ts b/packages/vue/src/errorhandler.ts
index 542d341c322f..900bed5a5074 100644
--- a/packages/vue/src/errorhandler.ts
+++ b/packages/vue/src/errorhandler.ts
@@ -1,12 +1,12 @@
import { getCurrentHub } from '@sentry/browser';
import { addExceptionMechanism } from '@sentry/utils';
-import type { Options, ViewModel, Vue } from './types';
+import type { ViewModel, Vue, VueOptions } from './types';
import { formatComponentName, generateComponentTrace } from './vendor/components';
type UnknownFunc = (...args: unknown[]) => void;
-export const attachErrorHandler = (app: Vue, options: Options): void => {
+export const attachErrorHandler = (app: Vue, options: VueOptions): void => {
const { errorHandler, warnHandler, silent } = app.config;
app.config.errorHandler = (error: Error, vm: ViewModel, lifecycleHook: string): void => {
diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts
index 24a352aba99a..6afcc0f60ae8 100644
--- a/packages/vue/src/index.ts
+++ b/packages/vue/src/index.ts
@@ -4,3 +4,4 @@ export { init } from './sdk';
export { vueRouterInstrumentation } from './router';
export { attachErrorHandler } from './errorhandler';
export { createTracingMixins } from './tracing';
+export { VueIntegration } from './integration';
diff --git a/packages/vue/src/integration.ts b/packages/vue/src/integration.ts
new file mode 100644
index 000000000000..9a3969f5c415
--- /dev/null
+++ b/packages/vue/src/integration.ts
@@ -0,0 +1,99 @@
+import { hasTracingEnabled } from '@sentry/core';
+import type { Hub, Integration } from '@sentry/types';
+import { arrayify, GLOBAL_OBJ } from '@sentry/utils';
+
+import { DEFAULT_HOOKS } from './constants';
+import { attachErrorHandler } from './errorhandler';
+import { createTracingMixins } from './tracing';
+import type { Options, Vue, VueOptions } from './types';
+
+const globalWithVue = GLOBAL_OBJ as typeof GLOBAL_OBJ & { Vue: Vue };
+
+const DEFAULT_CONFIG: VueOptions = {
+ Vue: globalWithVue.Vue,
+ attachProps: true,
+ logErrors: true,
+ hooks: DEFAULT_HOOKS,
+ timeout: 2000,
+ trackComponents: false,
+};
+
+/**
+ * Initialize Vue error & performance tracking.
+ */
+export class VueIntegration implements Integration {
+ /**
+ * @inheritDoc
+ */
+ public static id: string = 'Vue';
+
+ /**
+ * @inheritDoc
+ */
+ public name: string;
+
+ private readonly _options: Partial;
+
+ public constructor(options: Partial = {}) {
+ this.name = VueIntegration.id;
+ this._options = options;
+ }
+
+ /** @inheritDoc */
+ public setupOnce(_addGlobaleventProcessor: unknown, getCurrentHub: () => Hub): void {
+ this._setupIntegration(getCurrentHub());
+ }
+
+ /** Just here for easier testing */
+ protected _setupIntegration(hub: Hub): void {
+ const client = hub.getClient();
+ const options: Options = { ...DEFAULT_CONFIG, ...(client && client.getOptions()), ...this._options };
+
+ if (!options.Vue && !options.app) {
+ // eslint-disable-next-line no-console
+ console.warn(
+ `[@sentry/vue]: Misconfigured SDK. Vue specific errors will not be captured.
+Update your \`Sentry.init\` call with an appropriate config option:
+\`app\` (Application Instance - Vue 3) or \`Vue\` (Vue Constructor - Vue 2).`,
+ );
+ return;
+ }
+
+ if (options.app) {
+ const apps = arrayify(options.app);
+ apps.forEach(app => vueInit(app, options));
+ } else if (options.Vue) {
+ vueInit(options.Vue, options);
+ }
+ }
+}
+
+const vueInit = (app: Vue, options: Options): void => {
+ // Check app is not mounted yet - should be mounted _after_ init()!
+ // This is _somewhat_ private, but in the case that this doesn't exist we simply ignore it
+ // See: https://github.com/vuejs/core/blob/eb2a83283caa9de0a45881d860a3cbd9d0bdd279/packages/runtime-core/src/component.ts#L394
+ const appWithInstance = app as Vue & {
+ _instance?: {
+ isMounted?: boolean;
+ };
+ };
+
+ const isMounted = appWithInstance._instance && appWithInstance._instance.isMounted;
+ if (isMounted === true) {
+ // eslint-disable-next-line no-console
+ console.warn(
+ '[@sentry/vue]: Misconfigured SDK. Vue app is already mounted. Make sure to call `app.mount()` after `Sentry.init()`.',
+ );
+ }
+
+ attachErrorHandler(app, options);
+
+ if (hasTracingEnabled(options)) {
+ app.mixin(
+ createTracingMixins({
+ ...options,
+ ...options.tracingOptions,
+ }),
+ );
+ }
+};
diff --git a/packages/vue/src/sdk.ts b/packages/vue/src/sdk.ts
index ecc879bccbd7..21d7246f503c 100644
--- a/packages/vue/src/sdk.ts
+++ b/packages/vue/src/sdk.ts
@@ -1,34 +1,7 @@
-import { init as browserInit, SDK_VERSION } from '@sentry/browser';
-import { hasTracingEnabled } from '@sentry/core';
-import { arrayify, GLOBAL_OBJ } from '@sentry/utils';
+import { defaultIntegrations, init as browserInit, SDK_VERSION } from '@sentry/browser';
-import { DEFAULT_HOOKS } from './constants';
-import { attachErrorHandler } from './errorhandler';
-import { createTracingMixins } from './tracing';
-import type { Options, TracingOptions, Vue } from './types';
-
-const globalWithVue = GLOBAL_OBJ as typeof GLOBAL_OBJ & { Vue: Vue };
-
-const DEFAULT_CONFIG: Options = {
- Vue: globalWithVue.Vue,
- attachProps: true,
- logErrors: true,
- hooks: DEFAULT_HOOKS,
- timeout: 2000,
- trackComponents: false,
- _metadata: {
- sdk: {
- name: 'sentry.javascript.vue',
- packages: [
- {
- name: 'npm:@sentry/vue',
- version: SDK_VERSION,
- },
- ],
- version: SDK_VERSION,
- },
- },
-};
+import { VueIntegration } from './integration';
+import type { Options, TracingOptions } from './types';
/**
* Inits the Vue SDK
@@ -37,56 +10,21 @@ export function init(
config: Partial & { tracingOptions: Partial }> = {},
): void {
const options = {
- ...DEFAULT_CONFIG,
+ _metadata: {
+ sdk: {
+ name: 'sentry.javascript.vue',
+ packages: [
+ {
+ name: 'npm:@sentry/vue',
+ version: SDK_VERSION,
+ },
+ ],
+ version: SDK_VERSION,
+ },
+ },
+ defaultIntegrations: [...defaultIntegrations, new VueIntegration()],
...config,
};
browserInit(options);
-
- if (!options.Vue && !options.app) {
- // eslint-disable-next-line no-console
- console.warn(
- `[@sentry/vue]: Misconfigured SDK. Vue specific errors will not be captured.
-Update your \`Sentry.init\` call with an appropriate config option:
-\`app\` (Application Instance - Vue 3) or \`Vue\` (Vue Constructor - Vue 2).`,
- );
- return;
- }
-
- if (options.app) {
- const apps = arrayify(options.app);
- apps.forEach(app => vueInit(app, options));
- } else if (options.Vue) {
- vueInit(options.Vue, options);
- }
}
-
-const vueInit = (app: Vue, options: Options): void => {
- // Check app is not mounted yet - should be mounted _after_ init()!
- // This is _somewhat_ private, but in the case that this doesn't exist we simply ignore it
- // See: https://github.com/vuejs/core/blob/eb2a83283caa9de0a45881d860a3cbd9d0bdd279/packages/runtime-core/src/component.ts#L394
- const appWithInstance = app as Vue & {
- _instance?: {
- isMounted?: boolean;
- };
- };
-
- const isMounted = appWithInstance._instance && appWithInstance._instance.isMounted;
- if (isMounted === true) {
- // eslint-disable-next-line no-console
- console.warn(
- '[@sentry/vue]: Misconfigured SDK. Vue app is already mounted. Make sure to call `app.mount()` after `Sentry.init()`.',
- );
- }
-
- attachErrorHandler(app, options);
-
- if (hasTracingEnabled(options)) {
- app.mixin(
- createTracingMixins({
- ...options,
- ...options.tracingOptions,
- }),
- );
- }
-};
diff --git a/packages/vue/src/types.ts b/packages/vue/src/types.ts
index 1cc39b97b887..2a1ee6d89046 100644
--- a/packages/vue/src/types.ts
+++ b/packages/vue/src/types.ts
@@ -25,11 +25,13 @@ export type ViewModel = {
};
};
-export interface Options extends TracingOptions, BrowserOptions {
+export interface VueOptions extends TracingOptions {
/** Vue constructor to be used inside the integration (as imported by `import Vue from 'vue'` in Vue2) */
Vue?: Vue;
- /** Vue app instance(s) to be used inside the integration (as generated by `createApp` in Vue3 ) */
+ /**
+ * Vue app instance(s) to be used inside the integration (as generated by `createApp` in Vue3).
+ */
app?: Vue | Vue[];
/**
@@ -48,6 +50,8 @@ export interface Options extends TracingOptions, BrowserOptions {
tracingOptions?: Partial;
}
+export interface Options extends BrowserOptions, VueOptions {}
+
/** Vue specific configuration for Tracing Integration */
export interface TracingOptions {
/**
diff --git a/packages/vue/test/integration/VueIntegration.test.ts b/packages/vue/test/integration/VueIntegration.test.ts
new file mode 100644
index 000000000000..22f53df4c498
--- /dev/null
+++ b/packages/vue/test/integration/VueIntegration.test.ts
@@ -0,0 +1,68 @@
+import { logger } from '@sentry/utils';
+import { createApp } from 'vue';
+
+import * as Sentry from '../../src';
+
+const PUBLIC_DSN = 'https://username@domain/123';
+
+describe('Sentry.VueIntegration', () => {
+ let loggerWarnings: unknown[] = [];
+ let warnings: unknown[] = [];
+
+ beforeEach(() => {
+ warnings = [];
+ loggerWarnings = [];
+
+ jest.spyOn(logger, 'warn').mockImplementation((message: unknown) => {
+ loggerWarnings.push(message);
+ });
+
+ jest.spyOn(console, 'warn').mockImplementation((message: unknown) => {
+ warnings.push(message);
+ });
+ });
+
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+
+ it('allows to initialize integration later', () => {
+ Sentry.init({ dsn: PUBLIC_DSN, defaultIntegrations: false, autoSessionTracking: false });
+
+ const el = document.createElement('div');
+ const app = createApp({
+ template: 'hello
',
+ });
+
+ // This would normally happen through client.addIntegration()
+ const integration = new Sentry.VueIntegration({ app });
+ integration['_setupIntegration'](Sentry.getCurrentHub());
+
+ app.mount(el);
+
+ expect(warnings).toEqual([]);
+ expect(loggerWarnings).toEqual([]);
+
+ expect(app.config.errorHandler).toBeDefined();
+ });
+
+ it('warns when mounting before SDK.VueIntegration', () => {
+ Sentry.init({ dsn: PUBLIC_DSN, defaultIntegrations: false, autoSessionTracking: false });
+
+ const el = document.createElement('div');
+ const app = createApp({
+ template: 'hello
',
+ });
+
+ app.mount(el);
+
+ // This would normally happen through client.addIntegration()
+ const integration = new Sentry.VueIntegration({ app });
+ integration['_setupIntegration'](Sentry.getCurrentHub());
+
+ expect(warnings).toEqual([
+ '[@sentry/vue]: Misconfigured SDK. Vue app is already mounted. Make sure to call `app.mount()` after `Sentry.init()`.',
+ ]);
+ expect(loggerWarnings).toEqual([]);
+ });
+});
diff --git a/packages/vue/test/integration/init.test.ts b/packages/vue/test/integration/init.test.ts
index a9936c97bc89..e176e5b1691c 100644
--- a/packages/vue/test/integration/init.test.ts
+++ b/packages/vue/test/integration/init.test.ts
@@ -1,24 +1,23 @@
import { createApp } from 'vue';
+import { VueIntegration } from '../../src/integration';
+import type { Options } from '../../src/types';
import * as Sentry from './../../src';
+const PUBLIC_DSN = 'https://username@domain/123';
+
describe('Sentry.init', () => {
- let _consoleWarn: any;
- let warnings: string[] = [];
+ let warnings: unknown[] = [];
beforeEach(() => {
warnings = [];
- // eslint-disable-next-line no-console
- _consoleWarn = console.warn;
- // eslint-disable-next-line no-console
- console.warn = jest.fn((message: string) => {
+ jest.spyOn(console, 'warn').mockImplementation((message: unknown) => {
warnings.push(message);
});
});
afterEach(() => {
- // eslint-disable-next-line no-console
- console.warn = _consoleWarn;
+ jest.clearAllMocks();
});
it('does not warn when correctly setup (Vue 3)', () => {
@@ -27,9 +26,8 @@ describe('Sentry.init', () => {
template: 'hello
',
});
- Sentry.init({
+ runInit({
app,
- defaultIntegrations: false,
});
app.mount(el);
@@ -43,10 +41,9 @@ describe('Sentry.init', () => {
template: 'hello
',
});
- Sentry.init({
+ runInit({
// this is a bit "hacky", but good enough to test what we want
Vue: app,
- defaultIntegrations: false,
});
app.mount(el);
@@ -62,9 +59,8 @@ describe('Sentry.init', () => {
app.mount(el);
- Sentry.init({
+ runInit({
app,
- defaultIntegrations: false,
});
expect(warnings).toEqual([
@@ -78,9 +74,7 @@ describe('Sentry.init', () => {
template: 'hello
',
});
- Sentry.init({
- defaultIntegrations: false,
- });
+ runInit({});
app.mount(el);
@@ -90,4 +84,41 @@ Update your \`Sentry.init\` call with an appropriate config option:
\`app\` (Application Instance - Vue 3) or \`Vue\` (Vue Constructor - Vue 2).`,
]);
});
+
+ it('does not warn when skipping Vue integration', () => {
+ const el = document.createElement('div');
+ const app = createApp({
+ template: 'hello
',
+ });
+
+ Sentry.init({
+ dsn: PUBLIC_DSN,
+ defaultIntegrations: false,
+ integrations: [],
+ });
+
+ app.mount(el);
+
+ expect(warnings).toEqual([]);
+ });
});
+
+function runInit(options: Partial): void {
+ const hasRunBefore = Sentry.getCurrentHub().getIntegration(VueIntegration);
+
+ const integration = new VueIntegration();
+
+ Sentry.init({
+ dsn: PUBLIC_DSN,
+ defaultIntegrations: false,
+ integrations: [integration],
+ ...options,
+ });
+
+ // Because our integrations API is terrible to test, we need to make sure to check
+ // If we've already had this integration registered before
+ // if that's the case, `setup()` will not be run, so we need to manually run it :(
+ if (hasRunBefore) {
+ integration['_setupIntegration'](Sentry.getCurrentHub());
+ }
+}
diff --git a/rollup/npmHelpers.js b/rollup/npmHelpers.js
index be6a900b2115..fe2a55543e59 100644
--- a/rollup/npmHelpers.js
+++ b/rollup/npmHelpers.js
@@ -25,10 +25,11 @@ export function makeBaseNPMConfig(options = {}) {
esModuleInterop = false,
hasBundles = false,
packageSpecificConfig = {},
+ addPolyfills = true,
} = options;
const nodeResolvePlugin = makeNodeResolvePlugin();
- const sucrasePlugin = makeSucrasePlugin();
+ const sucrasePlugin = makeSucrasePlugin({ disableESTransforms: !addPolyfills });
const debugBuildStatementReplacePlugin = makeDebugBuildStatementReplacePlugin();
const cleanupPlugin = makeCleanupPlugin();
const extractPolyfillsPlugin = makeExtractPolyfillsPlugin();
@@ -83,14 +84,7 @@ export function makeBaseNPMConfig(options = {}) {
interop: esModuleInterop ? 'auto' : 'esModule',
},
- plugins: [
- nodeResolvePlugin,
- setSdkSourcePlugin,
- sucrasePlugin,
- debugBuildStatementReplacePlugin,
- cleanupPlugin,
- extractPolyfillsPlugin,
- ],
+ plugins: [nodeResolvePlugin, setSdkSourcePlugin, sucrasePlugin, debugBuildStatementReplacePlugin, cleanupPlugin],
// don't include imported modules from outside the package in the final output
external: [
@@ -100,6 +94,10 @@ export function makeBaseNPMConfig(options = {}) {
],
};
+ if (addPolyfills) {
+ defaultBaseConfig.plugins.push(extractPolyfillsPlugin);
+ }
+
return deepMerge(defaultBaseConfig, packageSpecificConfig, {
// Plugins have to be in the correct order or everything breaks, so when merging we have to manually re-order them
customMerge: key => (key === 'plugins' ? mergePlugins : undefined),
diff --git a/rollup/plugins/npmPlugins.js b/rollup/plugins/npmPlugins.js
index ec162615623f..5265f5007755 100644
--- a/rollup/plugins/npmPlugins.js
+++ b/rollup/plugins/npmPlugins.js
@@ -16,9 +16,10 @@ import sucrase from '@rollup/plugin-sucrase';
*
* @returns An instance of the `@rollup/plugin-sucrase` plugin
*/
-export function makeSucrasePlugin() {
+export function makeSucrasePlugin(options = {}) {
return sucrase({
transforms: ['typescript', 'jsx'],
+ ...options,
});
}
diff --git a/scripts/node-unit-tests.ts b/scripts/node-unit-tests.ts
index 28167e15d557..8824cee77d66 100644
--- a/scripts/node-unit-tests.ts
+++ b/scripts/node-unit-tests.ts
@@ -22,6 +22,7 @@ const DEFAULT_SKIP_TESTS_PACKAGES = [
'@sentry/replay',
'@sentry/wasm',
'@sentry/bun',
+ '@sentry/deno',
];
const SKIP_TEST_PACKAGES: Record = {
@@ -35,6 +36,7 @@ const SKIP_TEST_PACKAGES: Record = {
'@sentry-internal/replay-worker',
'@sentry/node-experimental',
'@sentry/vercel-edge',
+ '@sentry/astro',
],
legacyDeps: [
'jsdom@15.x',
@@ -53,22 +55,29 @@ const SKIP_TEST_PACKAGES: Record = {
'@sentry-internal/replay-worker',
'@sentry/node-experimental',
'@sentry/vercel-edge',
+ '@sentry/astro',
],
legacyDeps: ['jsdom@16.x', 'lerna@3.13.4'],
shouldES6Utils: true,
},
'12': {
- ignoredPackages: ['@sentry/remix', '@sentry/sveltekit', '@sentry/node-experimental', '@sentry/vercel-edge'],
+ ignoredPackages: [
+ '@sentry/remix',
+ '@sentry/sveltekit',
+ '@sentry/node-experimental',
+ '@sentry/vercel-edge',
+ '@sentry/astro',
+ ],
legacyDeps: ['lerna@3.13.4'],
shouldES6Utils: true,
},
'14': {
- ignoredPackages: ['@sentry/sveltekit', '@sentry/vercel-edge'],
+ ignoredPackages: ['@sentry/sveltekit', '@sentry/vercel-edge', '@sentry/astro'],
legacyDeps: [],
shouldES6Utils: false,
},
'16': {
- ignoredPackages: ['@sentry/vercel-edge'],
+ ignoredPackages: ['@sentry/vercel-edge', '@sentry/astro'],
legacyDeps: [],
shouldES6Utils: false,
},
diff --git a/scripts/prepack.ts b/scripts/prepack.ts
index 0c810f3e9030..ed280c45d088 100644
--- a/scripts/prepack.ts
+++ b/scripts/prepack.ts
@@ -21,13 +21,13 @@ const TYPES_VERSIONS_ENTRY_POINT = 'typesVersions';
const packageWithBundles = process.argv.includes('--bundles');
const buildDir = packageWithBundles ? NPM_BUILD_DIR : BUILD_DIR;
-type PackageJsonEntryPoints = Record;
+type PackageJsonEntryPoints = Record<(typeof ENTRY_POINTS)[number], string>;
interface TypeVersions {
[key: string]: {
[key: string]: string[];
};
-};
+}
interface PackageJson extends Record, PackageJsonEntryPoints {
[EXPORT_MAP_ENTRY_POINT]: {
@@ -35,6 +35,9 @@ interface PackageJson extends Record, PackageJsonEntryPoints {
import: string;
require: string;
types: string;
+ node: string;
+ browser: string;
+ default: string;
};
};
[TYPES_VERSIONS_ENTRY_POINT]: TypeVersions;
diff --git a/yarn.lock b/yarn.lock
index bc849269e256..f64756e09026 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -17,6 +17,14 @@
dependencies:
"@jridgewell/trace-mapping" "^0.3.0"
+"@ampproject/remapping@^2.2.0":
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630"
+ integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.0"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
"@angular-devkit/architect@0.1002.4":
version "0.1002.4"
resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.1002.4.tgz#2e1fa9c7a4718a4d0d101516ab0cc9cb653c5c57"
@@ -540,6 +548,56 @@
resolved "https://registry.yarnpkg.com/@assemblyscript/loader/-/loader-0.10.1.tgz#70e45678f06c72fa2e350e8553ec4a4d72b92e06"
integrity sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==
+"@astrojs/compiler@^2.1.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@astrojs/compiler/-/compiler-2.2.0.tgz#a6b106b7878b461e3d55715d90810a7df5df3ca2"
+ integrity sha512-JvmckEJgg8uXUw8Rs6VZDvN7LcweCHOdcxsCXpC+4KMDC9FaB5t9EH/NooSE+hu/rnACEhsXA3FKmf9wnhb7hA==
+
+"@astrojs/internal-helpers@0.2.1":
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/@astrojs/internal-helpers/-/internal-helpers-0.2.1.tgz#4e2e6aabaa9819f17119aa10f413c4d6122c94cf"
+ integrity sha512-06DD2ZnItMwUnH81LBLco3tWjcZ1lGU9rLCCBaeUCGYe9cI0wKyY2W3kDyoW1I6GmcWgt1fu+D1CTvz+FIKf8A==
+
+"@astrojs/markdown-remark@3.2.1":
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/@astrojs/markdown-remark/-/markdown-remark-3.2.1.tgz#0014c9c2d8666af4b2fee0cbd4185201eb328d76"
+ integrity sha512-Z4YNMRtgFZeHhB29uCZl0B9MbMZddW9ZKCNucapoysbvygbDFF1gGtqpVnf+Lyv3rUBHwM/J5qWB2MSZuTuz1g==
+ dependencies:
+ "@astrojs/prism" "^3.0.0"
+ github-slugger "^2.0.0"
+ import-meta-resolve "^3.0.0"
+ mdast-util-definitions "^6.0.0"
+ rehype-raw "^6.1.1"
+ rehype-stringify "^9.0.4"
+ remark-gfm "^3.0.1"
+ remark-parse "^10.0.2"
+ remark-rehype "^10.1.0"
+ remark-smartypants "^2.0.0"
+ shiki "^0.14.3"
+ unified "^10.1.2"
+ unist-util-visit "^4.1.2"
+ vfile "^5.3.7"
+
+"@astrojs/prism@^3.0.0":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/@astrojs/prism/-/prism-3.0.0.tgz#c9443e4cbf435acf0b5adc2c627d9789991514e7"
+ integrity sha512-g61lZupWq1bYbcBnYZqdjndShr/J3l/oFobBKPA3+qMat146zce3nz2kdO4giGbhYDt4gYdhmoBz0vZJ4sIurQ==
+ dependencies:
+ prismjs "^1.29.0"
+
+"@astrojs/telemetry@3.0.3":
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/@astrojs/telemetry/-/telemetry-3.0.3.tgz#a7a87a40de74bfeaae78fc4cbec1f6ec1cbf1c36"
+ integrity sha512-j19Cf5mfyLt9hxgJ9W/FMdAA5Lovfp7/CINNB/7V71GqvygnL7KXhRC3TzfB+PsVQcBtgWZzCXhUWRbmJ64Raw==
+ dependencies:
+ ci-info "^3.8.0"
+ debug "^4.3.4"
+ dlv "^1.1.3"
+ dset "^3.1.2"
+ is-docker "^3.0.0"
+ is-wsl "^3.0.0"
+ which-pm-runs "^1.1.0"
+
"@babel/code-frame@7.12.11":
version "7.12.11"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f"
@@ -554,6 +612,14 @@
dependencies:
"@babel/highlight" "^7.18.6"
+"@babel/code-frame@^7.22.13":
+ version "7.22.13"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e"
+ integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==
+ dependencies:
+ "@babel/highlight" "^7.22.13"
+ chalk "^2.4.2"
+
"@babel/compat-data@^7.11.0", "@babel/compat-data@^7.13.0", "@babel/compat-data@^7.17.7", "@babel/compat-data@^7.19.4", "@babel/compat-data@^7.20.0":
version "7.20.1"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.1.tgz#f2e6ef7790d8c8dbf03d379502dcc246dcce0b30"
@@ -564,6 +630,11 @@
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.14.tgz#4106fc8b755f3e3ee0a0a7c27dde5de1d2b2baf8"
integrity sha512-0YpKHD6ImkWMEINCyDAD0HLLUH/lPCefG8ld9it8DJB2wnApraKuhgYTvTY1z7UFIfBTGy5LwncZ+5HWWGbhFw==
+"@babel/compat-data@^7.22.9":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.20.tgz#8df6e96661209623f1975d66c35ffca66f3306d0"
+ integrity sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw==
+
"@babel/core@7.11.1":
version "7.11.1"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.11.1.tgz#2c55b604e73a40dc21b0e52650b11c65cf276643"
@@ -628,6 +699,27 @@
json5 "^2.2.1"
semver "^6.3.0"
+"@babel/core@^7.22.10":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.0.tgz#f8259ae0e52a123eb40f552551e647b506a94d83"
+ integrity sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==
+ dependencies:
+ "@ampproject/remapping" "^2.2.0"
+ "@babel/code-frame" "^7.22.13"
+ "@babel/generator" "^7.23.0"
+ "@babel/helper-compilation-targets" "^7.22.15"
+ "@babel/helper-module-transforms" "^7.23.0"
+ "@babel/helpers" "^7.23.0"
+ "@babel/parser" "^7.23.0"
+ "@babel/template" "^7.22.15"
+ "@babel/traverse" "^7.23.0"
+ "@babel/types" "^7.23.0"
+ convert-source-map "^2.0.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.2"
+ json5 "^2.2.3"
+ semver "^6.3.1"
+
"@babel/core@^7.8.6":
version "7.20.12"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.12.tgz#7930db57443c6714ad216953d1356dac0eb8496d"
@@ -685,6 +777,16 @@
"@jridgewell/gen-mapping" "^0.3.2"
jsesc "^2.5.1"
+"@babel/generator@^7.22.10", "@babel/generator@^7.23.0":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420"
+ integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==
+ dependencies:
+ "@babel/types" "^7.23.0"
+ "@jridgewell/gen-mapping" "^0.3.2"
+ "@jridgewell/trace-mapping" "^0.3.17"
+ jsesc "^2.5.1"
+
"@babel/helper-annotate-as-pure@7.14.5":
version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.14.5.tgz#7bf478ec3b71726d56a8ca5775b046fc29879e61"
@@ -699,6 +801,13 @@
dependencies:
"@babel/types" "^7.18.6"
+"@babel/helper-annotate-as-pure@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882"
+ integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
"@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz#acd4edfd7a566d1d51ea975dff38fd52906981bb"
@@ -728,6 +837,17 @@
lru-cache "^5.1.1"
semver "^6.3.0"
+"@babel/helper-compilation-targets@^7.22.15":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52"
+ integrity sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==
+ dependencies:
+ "@babel/compat-data" "^7.22.9"
+ "@babel/helper-validator-option" "^7.22.15"
+ browserslist "^4.21.9"
+ lru-cache "^5.1.1"
+ semver "^6.3.1"
+
"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.19.0", "@babel/helper-create-class-features-plugin@^7.5.5":
version "7.19.0"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz#bfd6904620df4e46470bae4850d66be1054c404b"
@@ -816,6 +936,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be"
integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==
+"@babel/helper-environment-visitor@^7.22.20":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167"
+ integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==
+
"@babel/helper-explode-assignable-expression@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096"
@@ -831,6 +956,14 @@
"@babel/template" "^7.18.10"
"@babel/types" "^7.19.0"
+"@babel/helper-function-name@^7.23.0":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759"
+ integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==
+ dependencies:
+ "@babel/template" "^7.22.15"
+ "@babel/types" "^7.23.0"
+
"@babel/helper-hoist-variables@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678"
@@ -838,6 +971,13 @@
dependencies:
"@babel/types" "^7.18.6"
+"@babel/helper-hoist-variables@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb"
+ integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
"@babel/helper-member-expression-to-functions@^7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz#1531661e8375af843ad37ac692c132841e2fd815"
@@ -859,6 +999,13 @@
dependencies:
"@babel/types" "^7.18.6"
+"@babel/helper-module-imports@^7.22.15":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0"
+ integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==
+ dependencies:
+ "@babel/types" "^7.22.15"
+
"@babel/helper-module-transforms@^7.11.0", "@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.6", "@babel/helper-module-transforms@^7.20.2":
version "7.20.2"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz#ac53da669501edd37e658602a21ba14c08748712"
@@ -887,6 +1034,17 @@
"@babel/traverse" "^7.20.10"
"@babel/types" "^7.20.7"
+"@babel/helper-module-transforms@^7.23.0":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz#3ec246457f6c842c0aee62a01f60739906f7047e"
+ integrity sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.22.20"
+ "@babel/helper-module-imports" "^7.22.15"
+ "@babel/helper-simple-access" "^7.22.5"
+ "@babel/helper-split-export-declaration" "^7.22.6"
+ "@babel/helper-validator-identifier" "^7.22.20"
+
"@babel/helper-optimise-call-expression@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe"
@@ -904,6 +1062,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629"
integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==
+"@babel/helper-plugin-utils@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295"
+ integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==
+
"@babel/helper-remap-async-to-generator@^7.14.5", "@babel/helper-remap-async-to-generator@^7.18.6", "@babel/helper-remap-async-to-generator@^7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519"
@@ -944,6 +1107,13 @@
dependencies:
"@babel/types" "^7.20.2"
+"@babel/helper-simple-access@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de"
+ integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
"@babel/helper-skip-transparent-expression-wrappers@^7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz#778d87b3a758d90b471e7b9918f34a9a02eb5818"
@@ -965,6 +1135,13 @@
dependencies:
"@babel/types" "^7.18.6"
+"@babel/helper-split-export-declaration@^7.22.6":
+ version "7.22.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c"
+ integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
"@babel/helper-string-parser@^7.19.4":
version "7.19.4"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63"
@@ -975,16 +1152,31 @@
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz#2b3eea65443c6bdc31c22d037c65f6d323b6b2bd"
integrity sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==
+"@babel/helper-string-parser@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f"
+ integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==
+
"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1":
version "7.19.1"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2"
integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==
+"@babel/helper-validator-identifier@^7.22.20":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
+ integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
+
"@babel/helper-validator-option@^7.14.5", "@babel/helper-validator-option@^7.16.7", "@babel/helper-validator-option@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8"
integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==
+"@babel/helper-validator-option@^7.22.15":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040"
+ integrity sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==
+
"@babel/helper-wrap-function@^7.18.9":
version "7.19.0"
resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz#89f18335cff1152373222f76a4b37799636ae8b1"
@@ -1013,6 +1205,15 @@
"@babel/traverse" "^7.20.13"
"@babel/types" "^7.20.7"
+"@babel/helpers@^7.23.0":
+ version "7.23.1"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.1.tgz#44e981e8ce2b9e99f8f0b703f3326a4636c16d15"
+ integrity sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA==
+ dependencies:
+ "@babel/template" "^7.22.15"
+ "@babel/traverse" "^7.23.0"
+ "@babel/types" "^7.23.0"
+
"@babel/highlight@^7.10.4", "@babel/highlight@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf"
@@ -1022,6 +1223,15 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
+"@babel/highlight@^7.22.13":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54"
+ integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.22.20"
+ chalk "^2.4.2"
+ js-tokens "^4.0.0"
+
"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.11.1", "@babel/parser@^7.14.7", "@babel/parser@^7.16.4", "@babel/parser@^7.18.10", "@babel/parser@^7.20.1", "@babel/parser@^7.20.2", "@babel/parser@^7.4.5", "@babel/parser@^7.7.0":
version "7.20.3"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.3.tgz#5358cf62e380cf69efcb87a7bb922ff88bfac6e2"
@@ -1037,6 +1247,11 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.4.tgz#a770e98fd785c231af9d93f6459d36770993fb32"
integrity sha512-VLLsx06XkEYqBtE5YGPwfSGwfrjnyPP5oiGty3S8pQLFDFLaS8VwWSIxkTXpcvr5zeYLE6+MBNl2npl/YnfofA==
+"@babel/parser@^7.22.10", "@babel/parser@^7.22.15", "@babel/parser@^7.23.0":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719"
+ integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==
+
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2"
@@ -1338,6 +1553,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.0"
+"@babel/plugin-syntax-jsx@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz#a6b68e84fb76e759fc3b93e901876ffabbe1d918"
+ integrity sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
"@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3":
version "7.10.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699"
@@ -1689,6 +1911,17 @@
dependencies:
"@babel/helper-plugin-utils" "^7.18.6"
+"@babel/plugin-transform-react-jsx@^7.22.5":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.15.tgz#7e6266d88705d7c49f11c98db8b9464531289cd6"
+ integrity sha512-oKckg2eZFa8771O/5vi7XeTvmM6+O9cxZu+kanTU7tD4sin5nO/G8jGJhq8Hvt2Z0kUoEDRayuZLaUlYl8QuGA==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.22.5"
+ "@babel/helper-module-imports" "^7.22.15"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-jsx" "^7.22.5"
+ "@babel/types" "^7.22.15"
+
"@babel/plugin-transform-regenerator@^7.10.4", "@babel/plugin-transform-regenerator@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz#585c66cb84d4b4bf72519a34cfce761b8676ca73"
@@ -2169,6 +2402,15 @@
"@babel/parser" "^7.20.7"
"@babel/types" "^7.20.7"
+"@babel/template@^7.22.15":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
+ integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==
+ dependencies:
+ "@babel/code-frame" "^7.22.13"
+ "@babel/parser" "^7.22.15"
+ "@babel/types" "^7.22.15"
+
"@babel/traverse@^7.11.0", "@babel/traverse@^7.13.0", "@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.20.1", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2":
version "7.20.1"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.1.tgz#9b15ccbf882f6d107eeeecf263fbcdd208777ec8"
@@ -2201,6 +2443,22 @@
debug "^4.1.0"
globals "^11.1.0"
+"@babel/traverse@^7.22.10", "@babel/traverse@^7.23.0":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.0.tgz#18196ddfbcf4ccea324b7f6d3ada00d8c5a99c53"
+ integrity sha512-t/QaEvyIoIkwzpiZ7aoSKK8kObQYeF7T2v+dazAYCb8SXtp58zEVkWW7zAnju8FNKNdr4ScAOEDmMItbyOmEYw==
+ dependencies:
+ "@babel/code-frame" "^7.22.13"
+ "@babel/generator" "^7.23.0"
+ "@babel/helper-environment-visitor" "^7.22.20"
+ "@babel/helper-function-name" "^7.23.0"
+ "@babel/helper-hoist-variables" "^7.22.5"
+ "@babel/helper-split-export-declaration" "^7.22.6"
+ "@babel/parser" "^7.23.0"
+ "@babel/types" "^7.23.0"
+ debug "^4.1.0"
+ globals "^11.1.0"
+
"@babel/types@7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.3.tgz#5a383dffa5416db1b73dedffd311ffd0788fb31c"
@@ -2237,6 +2495,15 @@
"@babel/helper-validator-identifier" "^7.19.1"
to-fast-properties "^2.0.0"
+"@babel/types@^7.22.10", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb"
+ integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==
+ dependencies:
+ "@babel/helper-string-parser" "^7.22.5"
+ "@babel/helper-validator-identifier" "^7.22.20"
+ to-fast-properties "^2.0.0"
+
"@bcoe/v8-coverage@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
@@ -2457,111 +2724,331 @@
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz#cf91e86df127aa3d141744edafcba0abdc577d23"
integrity sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==
+"@esbuild/android-arm64@0.18.20":
+ version "0.18.20"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622"
+ integrity sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==
+
+"@esbuild/android-arm64@0.19.4":
+ version "0.19.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.4.tgz#74752a09301b8c6b9a415fbda9fb71406a62a7b7"
+ integrity sha512-mRsi2vJsk4Bx/AFsNBqOH2fqedxn5L/moT58xgg51DjX1la64Z3Npicut2VbhvDFO26qjWtPMsVxCd80YTFVeg==
+
"@esbuild/android-arm@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.16.17.tgz#025b6246d3f68b7bbaa97069144fb5fb70f2fff2"
integrity sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==
+"@esbuild/android-arm@0.18.20":
+ version "0.18.20"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682"
+ integrity sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==
+
+"@esbuild/android-arm@0.19.4":
+ version "0.19.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.4.tgz#c27363e1e280e577d9b5c8fa7c7a3be2a8d79bf5"
+ integrity sha512-uBIbiYMeSsy2U0XQoOGVVcpIktjLMEKa7ryz2RLr7L/vTnANNEsPVAh4xOv7ondGz6ac1zVb0F8Jx20rQikffQ==
+
"@esbuild/android-x64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.16.17.tgz#c820e0fef982f99a85c4b8bfdd582835f04cd96e"
integrity sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==
+"@esbuild/android-x64@0.18.20":
+ version "0.18.20"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2"
+ integrity sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==
+
+"@esbuild/android-x64@0.19.4":
+ version "0.19.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.4.tgz#6c9ee03d1488973d928618100048b75b147e0426"
+ integrity sha512-4iPufZ1TMOD3oBlGFqHXBpa3KFT46aLl6Vy7gwed0ZSYgHaZ/mihbYb4t7Z9etjkC9Al3ZYIoOaHrU60gcMy7g==
+
"@esbuild/darwin-arm64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz#edef4487af6b21afabba7be5132c26d22379b220"
integrity sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==
+"@esbuild/darwin-arm64@0.18.20":
+ version "0.18.20"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1"
+ integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==
+
+"@esbuild/darwin-arm64@0.19.4":
+ version "0.19.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.4.tgz#64e2ee945e5932cd49812caa80e8896e937e2f8b"
+ integrity sha512-Lviw8EzxsVQKpbS+rSt6/6zjn9ashUZ7Tbuvc2YENgRl0yZTktGlachZ9KMJUsVjZEGFVu336kl5lBgDN6PmpA==
+
"@esbuild/darwin-x64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz#42829168730071c41ef0d028d8319eea0e2904b4"
integrity sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==
+"@esbuild/darwin-x64@0.18.20":
+ version "0.18.20"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d"
+ integrity sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==
+
+"@esbuild/darwin-x64@0.19.4":
+ version "0.19.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.4.tgz#d8e26e1b965df284692e4d1263ba69a49b39ac7a"
+ integrity sha512-YHbSFlLgDwglFn0lAO3Zsdrife9jcQXQhgRp77YiTDja23FrC2uwnhXMNkAucthsf+Psr7sTwYEryxz6FPAVqw==
+
"@esbuild/freebsd-arm64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz#1f4af488bfc7e9ced04207034d398e793b570a27"
integrity sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==
+"@esbuild/freebsd-arm64@0.18.20":
+ version "0.18.20"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54"
+ integrity sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==
+
+"@esbuild/freebsd-arm64@0.19.4":
+ version "0.19.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.4.tgz#29751a41b242e0a456d89713b228f1da4f45582f"
+ integrity sha512-vz59ijyrTG22Hshaj620e5yhs2dU1WJy723ofc+KUgxVCM6zxQESmWdMuVmUzxtGqtj5heHyB44PjV/HKsEmuQ==
+
"@esbuild/freebsd-x64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz#636306f19e9bc981e06aa1d777302dad8fddaf72"
integrity sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==
+"@esbuild/freebsd-x64@0.18.20":
+ version "0.18.20"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e"
+ integrity sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==
+
+"@esbuild/freebsd-x64@0.19.4":
+ version "0.19.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.4.tgz#873edc0f73e83a82432460ea59bf568c1e90b268"
+ integrity sha512-3sRbQ6W5kAiVQRBWREGJNd1YE7OgzS0AmOGjDmX/qZZecq8NFlQsQH0IfXjjmD0XtUYqr64e0EKNFjMUlPL3Cw==
+
"@esbuild/linux-arm64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz#a003f7ff237c501e095d4f3a09e58fc7b25a4aca"
integrity sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==
+"@esbuild/linux-arm64@0.18.20":
+ version "0.18.20"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0"
+ integrity sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==
+
+"@esbuild/linux-arm64@0.19.4":
+ version "0.19.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.4.tgz#659f2fa988d448dbf5010b5cc583be757cc1b914"
+ integrity sha512-ZWmWORaPbsPwmyu7eIEATFlaqm0QGt+joRE9sKcnVUG3oBbr/KYdNE2TnkzdQwX6EDRdg/x8Q4EZQTXoClUqqA==
+
"@esbuild/linux-arm@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz#b591e6a59d9c4fe0eeadd4874b157ab78cf5f196"
integrity sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==
+"@esbuild/linux-arm@0.18.20":
+ version "0.18.20"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0"
+ integrity sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==
+
+"@esbuild/linux-arm@0.19.4":
+ version "0.19.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.4.tgz#d5b13a7ec1f1c655ce05c8d319b3950797baee55"
+ integrity sha512-z/4ArqOo9EImzTi4b6Vq+pthLnepFzJ92BnofU1jgNlcVb+UqynVFdoXMCFreTK7FdhqAzH0vmdwW5373Hm9pg==
+
"@esbuild/linux-ia32@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz#24333a11027ef46a18f57019450a5188918e2a54"
integrity sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==
+"@esbuild/linux-ia32@0.18.20":
+ version "0.18.20"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7"
+ integrity sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==
+
+"@esbuild/linux-ia32@0.19.4":
+ version "0.19.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.4.tgz#878cd8bf24c9847c77acdb5dd1b2ef6e4fa27a82"
+ integrity sha512-EGc4vYM7i1GRUIMqRZNCTzJh25MHePYsnQfKDexD8uPTCm9mK56NIL04LUfX2aaJ+C9vyEp2fJ7jbqFEYgO9lQ==
+
"@esbuild/linux-loong64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz#d5ad459d41ed42bbd4d005256b31882ec52227d8"
integrity sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==
+"@esbuild/linux-loong64@0.18.20":
+ version "0.18.20"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d"
+ integrity sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==
+
+"@esbuild/linux-loong64@0.19.4":
+ version "0.19.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.4.tgz#df890499f6e566b7de3aa2361be6df2b8d5fa015"
+ integrity sha512-WVhIKO26kmm8lPmNrUikxSpXcgd6HDog0cx12BUfA2PkmURHSgx9G6vA19lrlQOMw+UjMZ+l3PpbtzffCxFDRg==
+
"@esbuild/linux-mips64el@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz#4e5967a665c38360b0a8205594377d4dcf9c3726"
integrity sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==
+"@esbuild/linux-mips64el@0.18.20":
+ version "0.18.20"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231"
+ integrity sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==
+
+"@esbuild/linux-mips64el@0.19.4":
+ version "0.19.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.4.tgz#76eae4e88d2ce9f4f1b457e93892e802851b6807"
+ integrity sha512-keYY+Hlj5w86hNp5JJPuZNbvW4jql7c1eXdBUHIJGTeN/+0QFutU3GrS+c27L+NTmzi73yhtojHk+lr2+502Mw==
+
"@esbuild/linux-ppc64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz#206443a02eb568f9fdf0b438fbd47d26e735afc8"
integrity sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==
+"@esbuild/linux-ppc64@0.18.20":
+ version "0.18.20"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb"
+ integrity sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==
+
+"@esbuild/linux-ppc64@0.19.4":
+ version "0.19.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.4.tgz#c49032f4abbcfa3f747b543a106931fe3dce41ff"
+ integrity sha512-tQ92n0WMXyEsCH4m32S21fND8VxNiVazUbU4IUGVXQpWiaAxOBvtOtbEt3cXIV3GEBydYsY8pyeRMJx9kn3rvw==
+
"@esbuild/linux-riscv64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz#c351e433d009bf256e798ad048152c8d76da2fc9"
integrity sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==
+"@esbuild/linux-riscv64@0.18.20":
+ version "0.18.20"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6"
+ integrity sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==
+
+"@esbuild/linux-riscv64@0.19.4":
+ version "0.19.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.4.tgz#0f815a090772138503ee0465a747e16865bf94b1"
+ integrity sha512-tRRBey6fG9tqGH6V75xH3lFPpj9E8BH+N+zjSUCnFOX93kEzqS0WdyJHkta/mmJHn7MBaa++9P4ARiU4ykjhig==
+
"@esbuild/linux-s390x@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz#661f271e5d59615b84b6801d1c2123ad13d9bd87"
integrity sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==
+"@esbuild/linux-s390x@0.18.20":
+ version "0.18.20"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071"
+ integrity sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==
+
+"@esbuild/linux-s390x@0.19.4":
+ version "0.19.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.4.tgz#8d2cca20cd4e7c311fde8701d9f1042664f8b92b"
+ integrity sha512-152aLpQqKZYhThiJ+uAM4PcuLCAOxDsCekIbnGzPKVBRUDlgaaAfaUl5NYkB1hgY6WN4sPkejxKlANgVcGl9Qg==
+
"@esbuild/linux-x64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz#e4ba18e8b149a89c982351443a377c723762b85f"
integrity sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==
+"@esbuild/linux-x64@0.18.20":
+ version "0.18.20"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz#c7690b3417af318a9b6f96df3031a8865176d338"
+ integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==
+
+"@esbuild/linux-x64@0.19.4":
+ version "0.19.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.4.tgz#f618bec2655de49bff91c588777e37b5e3169d4a"
+ integrity sha512-Mi4aNA3rz1BNFtB7aGadMD0MavmzuuXNTaYL6/uiYIs08U7YMPETpgNn5oue3ICr+inKwItOwSsJDYkrE9ekVg==
+
"@esbuild/netbsd-x64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz#7d4f4041e30c5c07dd24ffa295c73f06038ec775"
integrity sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==
+"@esbuild/netbsd-x64@0.18.20":
+ version "0.18.20"
+ resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1"
+ integrity sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==
+
+"@esbuild/netbsd-x64@0.19.4":
+ version "0.19.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.4.tgz#7889744ca4d60f1538d62382b95e90a49687cef2"
+ integrity sha512-9+Wxx1i5N/CYo505CTT7T+ix4lVzEdz0uCoYGxM5JDVlP2YdDC1Bdz+Khv6IbqmisT0Si928eAxbmGkcbiuM/A==
+
"@esbuild/openbsd-x64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz#970fa7f8470681f3e6b1db0cc421a4af8060ec35"
integrity sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==
+"@esbuild/openbsd-x64@0.18.20":
+ version "0.18.20"
+ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae"
+ integrity sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==
+
+"@esbuild/openbsd-x64@0.19.4":
+ version "0.19.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.4.tgz#c3e436eb9271a423d2e8436fcb120e3fd90e2b01"
+ integrity sha512-MFsHleM5/rWRW9EivFssop+OulYVUoVcqkyOkjiynKBCGBj9Lihl7kh9IzrreDyXa4sNkquei5/DTP4uCk25xw==
+
"@esbuild/sunos-x64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz#abc60e7c4abf8b89fb7a4fe69a1484132238022c"
integrity sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==
+"@esbuild/sunos-x64@0.18.20":
+ version "0.18.20"
+ resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d"
+ integrity sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==
+
+"@esbuild/sunos-x64@0.19.4":
+ version "0.19.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.4.tgz#f63f5841ba8c8c1a1c840d073afc99b53e8ce740"
+ integrity sha512-6Xq8SpK46yLvrGxjp6HftkDwPP49puU4OF0hEL4dTxqCbfx09LyrbUj/D7tmIRMj5D5FCUPksBbxyQhp8tmHzw==
+
"@esbuild/win32-arm64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz#7b0ff9e8c3265537a7a7b1fd9a24e7bd39fcd87a"
integrity sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==
+"@esbuild/win32-arm64@0.18.20":
+ version "0.18.20"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9"
+ integrity sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==
+
+"@esbuild/win32-arm64@0.19.4":
+ version "0.19.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.4.tgz#80be69cec92da4da7781cf7a8351b95cc5a236b0"
+ integrity sha512-PkIl7Jq4mP6ke7QKwyg4fD4Xvn8PXisagV/+HntWoDEdmerB2LTukRZg728Yd1Fj+LuEX75t/hKXE2Ppk8Hh1w==
+
"@esbuild/win32-ia32@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz#e90fe5267d71a7b7567afdc403dfd198c292eb09"
integrity sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==
+"@esbuild/win32-ia32@0.18.20":
+ version "0.18.20"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102"
+ integrity sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==
+
+"@esbuild/win32-ia32@0.19.4":
+ version "0.19.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.4.tgz#15dc0ed83d2794872b05d8edc4a358fecf97eb54"
+ integrity sha512-ga676Hnvw7/ycdKB53qPusvsKdwrWzEyJ+AtItHGoARszIqvjffTwaaW3b2L6l90i7MO9i+dlAW415INuRhSGg==
+
"@esbuild/win32-x64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz#c5a1a4bfe1b57f0c3e61b29883525c6da3e5c091"
integrity sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==
+"@esbuild/win32-x64@0.18.20":
+ version "0.18.20"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d"
+ integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==
+
+"@esbuild/win32-x64@0.19.4":
+ version "0.19.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.4.tgz#d46a6e220a717f31f39ae80f49477cc3220be0f0"
+ integrity sha512-HP0GDNla1T3ZL8Ko/SHAS2GgtjOg+VmWnnYLhuTksr++EnduYB0f3Y2LzHsUwb2iQ13JGoY6G3R8h6Du/WG6uA==
+
"@eslint-community/eslint-utils@^4.1.2", "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
@@ -3241,6 +3728,11 @@
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
+"@jridgewell/resolve-uri@^3.1.0":
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721"
+ integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==
+
"@jridgewell/set-array@^1.0.1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
@@ -3259,7 +3751,7 @@
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
-"@jridgewell/sourcemap-codec@^1.4.14":
+"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15":
version "1.4.15"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
@@ -3280,6 +3772,14 @@
"@jridgewell/resolve-uri" "3.1.0"
"@jridgewell/sourcemap-codec" "1.4.14"
+"@jridgewell/trace-mapping@^0.3.17":
+ version "0.3.19"
+ resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811"
+ integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==
+ dependencies:
+ "@jridgewell/resolve-uri" "^3.1.0"
+ "@jridgewell/sourcemap-codec" "^1.4.14"
+
"@jsdevtools/coverage-istanbul-loader@3.0.5":
version "3.0.5"
resolved "https://registry.yarnpkg.com/@jsdevtools/coverage-istanbul-loader/-/coverage-istanbul-loader-3.0.5.tgz#2a4bc65d0271df8d4435982db4af35d81754ee26"
@@ -3841,7 +4341,7 @@
dependencies:
"@opentelemetry/semantic-conventions" "1.15.2"
-"@opentelemetry/core@1.17.0", "@opentelemetry/core@^1.1.0", "@opentelemetry/core@^1.7.0", "@opentelemetry/core@^1.8.0":
+"@opentelemetry/core@1.17.0", "@opentelemetry/core@^1.1.0", "@opentelemetry/core@^1.7.0", "@opentelemetry/core@^1.8.0", "@opentelemetry/core@~1.17.0":
version "1.17.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.17.0.tgz#6a72425f5f953dc68b4c7c66d947c018173d30d2"
integrity sha512-tfnl3h+UefCgx1aeN2xtrmr6BmdWGKXypk0pflQR0urFS40aE88trnkOMc2HTJZbMrqEEl4HsaBeFhwLVXsrJg==
@@ -3867,13 +4367,13 @@
"@opentelemetry/semantic-conventions" "^1.0.0"
"@types/express" "4.17.17"
-"@opentelemetry/instrumentation-fastify@~0.32.2":
- version "0.32.2"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.32.2.tgz#4af882938d3c05f7c7f5f860095e568728a2d838"
- integrity sha512-DKa7SgxTtZ0O1ngGtAdwr/g8XguYw6KvLNME+J8rt6QpWQM+xytS0bg4atZAyt6aeYr/kO1sMrGXSlHEEYWIhg==
+"@opentelemetry/instrumentation-fastify@~0.32.3":
+ version "0.32.3"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.32.3.tgz#2c0640c986018d1a41dfff3d9c3bfe3b5b1cf62d"
+ integrity sha512-vRFVoEJXcu6nNpJ61H5syDb84PirOd4b3u8yl8Bcorrr6firGYBQH4pEIVB4PkQWlmi3sLOifqS3VAO2VRloEQ==
dependencies:
"@opentelemetry/core" "^1.8.0"
- "@opentelemetry/instrumentation" "^0.41.2"
+ "@opentelemetry/instrumentation" "^0.44.0"
"@opentelemetry/semantic-conventions" "^1.0.0"
"@opentelemetry/instrumentation-graphql@~0.35.1":
@@ -3960,7 +4460,7 @@
semver "^7.5.1"
shimmer "^1.2.1"
-"@opentelemetry/instrumentation@0.43.0", "@opentelemetry/instrumentation@~0.43.0":
+"@opentelemetry/instrumentation@0.43.0", "@opentelemetry/instrumentation@^0.43.0", "@opentelemetry/instrumentation@~0.43.0":
version "0.43.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.43.0.tgz#749521415df03396f969bf42341fcb4acd2e9c7b"
integrity sha512-S1uHE+sxaepgp+t8lvIDuRgyjJWisAb733198kwQTUc9ZtYQ2V2gmyCtR1x21ePGVLoMiX/NWY7WA290hwkjJQ==
@@ -3971,6 +4471,17 @@
semver "^7.5.2"
shimmer "^1.2.1"
+"@opentelemetry/instrumentation@^0.44.0":
+ version "0.44.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.44.0.tgz#194f16fc96671575b6bd73d3fadffb5aa4497e67"
+ integrity sha512-B6OxJTRRCceAhhnPDBshyQO7K07/ltX3quOLu0icEvPK9QZ7r9P1y0RQX8O5DxB4vTv4URRkxkg+aFU/plNtQw==
+ dependencies:
+ "@types/shimmer" "^1.0.2"
+ import-in-the-middle "1.4.2"
+ require-in-the-middle "^7.1.1"
+ semver "^7.5.2"
+ shimmer "^1.2.1"
+
"@opentelemetry/propagator-b3@1.17.0":
version "1.17.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/propagator-b3/-/propagator-b3-1.17.0.tgz#32509a8214b7ced7709fd06c0ee5a0d86adcc51f"
@@ -4321,6 +4832,18 @@
magic-string "^0.25.7"
resolve "^1.17.0"
+"@rollup/plugin-commonjs@^25.0.5":
+ version "25.0.5"
+ resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.5.tgz#0bac8f985a5de151b4b09338847f8c7f20a28a29"
+ integrity sha512-xY8r/A9oisSeSuLCTfhssyDjo9Vp/eDiRLXkg1MXCcEEgEjPmLU+ZyDB20OOD0NlyDa/8SGbK5uIggF5XTx77w==
+ dependencies:
+ "@rollup/pluginutils" "^5.0.1"
+ commondir "^1.0.1"
+ estree-walker "^2.0.2"
+ glob "^8.0.3"
+ is-reference "1.2.1"
+ magic-string "^0.27.0"
+
"@rollup/plugin-json@^4.0.0", "@rollup/plugin-json@^4.1.0":
version "4.1.0"
resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-4.1.0.tgz#54e09867ae6963c593844d8bd7a9c718294496f3"
@@ -4380,6 +4903,14 @@
"@rollup/pluginutils" "^4.1.1"
sucrase "^3.20.0"
+"@rollup/plugin-typescript@^11.1.5":
+ version "11.1.5"
+ resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-11.1.5.tgz#039c763bf943a5921f3f42be255895e75764cb91"
+ integrity sha512-rnMHrGBB0IUEv69Q8/JGRD/n4/n6b3nfpufUu26axhUcboUzv/twfZU8fIBbTOphRAe0v8EyxzeDpKXqGHfyDA==
+ dependencies:
+ "@rollup/pluginutils" "^5.0.1"
+ resolve "^1.22.1"
+
"@rollup/plugin-typescript@^8.3.1":
version "8.5.0"
resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-8.5.0.tgz#7ea11599a15b0a30fa7ea69ce3b791d41b862515"
@@ -4446,33 +4977,33 @@
semver "7.3.2"
semver-intersect "1.4.0"
-"@sentry-internal/rrdom@2.0.0":
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/@sentry-internal/rrdom/-/rrdom-2.0.0.tgz#326b5f26c76d2077874db7edffd5be3aa72848fb"
- integrity sha512-PLSw54GWCmxOmJWJ2NGDfz9b+/76IBpGsWnIjBiW7L3NDVuTo705/7+DmKTrDADO7xXAZZRpbuQjqBjV8Mu+yQ==
+"@sentry-internal/rrdom@2.0.1":
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/@sentry-internal/rrdom/-/rrdom-2.0.1.tgz#5d41892ff26462bb5e2412c2f2c646ef2dcfe0b5"
+ integrity sha512-uPQyq/ANoXSS5HpYkv9qupRSYh/tfbX4xBgM7XZDlApsnD3t6LxAqdAUP//zQO/z+kOHzJVUX5H5uiauqA96Yg==
dependencies:
- "@sentry-internal/rrweb-snapshot" "2.0.0"
+ "@sentry-internal/rrweb-snapshot" "2.0.1"
-"@sentry-internal/rrweb-snapshot@2.0.0":
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-snapshot/-/rrweb-snapshot-2.0.0.tgz#6d034f4f65736e990e279842cf1c2868fc9f47dd"
- integrity sha512-MFpUw2Kuq4OVQn1dv6l/oSPgbHdy8N0oWBeVeHQlBzxugje4i2KU9tf6K7KH2RAce7Bi9r5UgHvCsNG3PNi/XQ==
+"@sentry-internal/rrweb-snapshot@2.0.1":
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-snapshot/-/rrweb-snapshot-2.0.1.tgz#5467041c33815d7c07ec0e484a85418d31857ddc"
+ integrity sha512-C4fIzcpreOzDXkyPOBwGir9YvLiT9jeTa2WQ96U1RVRiLBvXhEyPKgMxWXQcyYTpzYtGwX9dLfHR29uOejzzxQ==
-"@sentry-internal/rrweb-types@2.0.0":
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-types/-/rrweb-types-2.0.0.tgz#8606e47d98e14580f46f98d5dc5d95bc9ebc8b59"
- integrity sha512-3dgoh4sbqgY8XwsKh6ofA8WRtUE+qWLHPDMzipp1XefKfEhr6qTtw0riurnJBrO5lD6dJuewK5BWwjcrFb3Gag==
+"@sentry-internal/rrweb-types@2.0.1":
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-types/-/rrweb-types-2.0.1.tgz#4f465715df2959cde486fe77fdda528d85a3c7f7"
+ integrity sha512-MQRdjsKm/kypHqumsWN+cmFhU0OWWoJSPNxOEG1efbUxZPvZL64tZSrgWimfisIId9TPDn0tr58sBhIgpqgNuw==
dependencies:
- "@sentry-internal/rrweb-snapshot" "2.0.0"
+ "@sentry-internal/rrweb-snapshot" "2.0.1"
-"@sentry-internal/rrweb@2.0.0":
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb/-/rrweb-2.0.0.tgz#180e2763b77f83aa24bae964dd2f8c8065ddfc49"
- integrity sha512-SOyIGjCi1q9ocMOHAAU6DhO2vecRkLk9/zQ6YbIJsUz1vB1ZoF0L1xDlwuL+fGw3HjZ6Wn8RoZWSSiQRokL7lg==
+"@sentry-internal/rrweb@2.0.1":
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb/-/rrweb-2.0.1.tgz#fa3a60d1e01362ba2ce58583f87bfa076d77ee3b"
+ integrity sha512-X33eL2CioQn0vOgkFVgu9L8LV4D4H48LFz7cqAofnWC5h6n36zsf7eIBpdDJKZ8JCj1z52h9gL5X+X4W2i/yXQ==
dependencies:
- "@sentry-internal/rrdom" "2.0.0"
- "@sentry-internal/rrweb-snapshot" "2.0.0"
- "@sentry-internal/rrweb-types" "2.0.0"
+ "@sentry-internal/rrdom" "2.0.1"
+ "@sentry-internal/rrweb-snapshot" "2.0.1"
+ "@sentry-internal/rrweb-types" "2.0.1"
"@types/css-font-loading-module" "0.0.7"
"@xstate/fsm" "^1.4.0"
base64-arraybuffer "^1.0.1"
@@ -4493,6 +5024,20 @@
unplugin "1.0.1"
webpack-sources "3.2.3"
+"@sentry/bundler-plugin-core@2.8.0":
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-2.8.0.tgz#e01df24d7f909277f453844132b856ed997c182b"
+ integrity sha512-DsTUgeKPqck3DkGzKjRduhPpEn0pez+/THF3gpwQBEfbPnKGr0EYugDvfungZwBFenckIvQBDTOZw0STvbgChA==
+ dependencies:
+ "@sentry/cli" "^2.21.2"
+ "@sentry/node" "^7.60.0"
+ "@sentry/utils" "^7.60.0"
+ dotenv "^16.3.1"
+ find-up "5.0.0"
+ glob "9.3.2"
+ magic-string "0.27.0"
+ unplugin "1.0.1"
+
"@sentry/cli@2.20.5":
version "2.20.5"
resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.20.5.tgz#255a5388ca24c211a0eae01dcc4ad813a7ff335a"
@@ -4528,6 +5073,17 @@
proxy-from-env "^1.1.0"
which "^2.0.2"
+"@sentry/cli@^2.21.2":
+ version "2.21.2"
+ resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.21.2.tgz#89e5633ff48a83d078c76c6997fffd4b68b2da1c"
+ integrity sha512-X1nye89zl+QV3FSuQDGItfM51tW9PQ7ce0TtV/12DgGgTVEgnVp5uvO3wX5XauHvulQzRPzwUL3ZK+yS5bAwCw==
+ dependencies:
+ https-proxy-agent "^5.0.0"
+ node-fetch "^2.6.7"
+ progress "^2.0.3"
+ proxy-from-env "^1.1.0"
+ which "^2.0.2"
+
"@sentry/vite-plugin@^0.6.1":
version "0.6.1"
resolved "https://registry.yarnpkg.com/@sentry/vite-plugin/-/vite-plugin-0.6.1.tgz#31eb744e8d87b1528eed8d41433647727a62e7c0"
@@ -4535,6 +5091,14 @@
dependencies:
"@sentry/bundler-plugin-core" "0.6.1"
+"@sentry/vite-plugin@^2.8.0":
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/@sentry/vite-plugin/-/vite-plugin-2.8.0.tgz#d19d2ebf07fcbf09bb585033d803b9967717e5a6"
+ integrity sha512-17++vXjfn0xEfE7W4FWdwoXdNNqGjXnuTvIgSLlhJvDCTcqWONDpA/TGXGLjbhQEmQ58wL4wQqmlyxoqMPlokQ==
+ dependencies:
+ "@sentry/bundler-plugin-core" "2.8.0"
+ unplugin "1.0.1"
+
"@sentry/webpack-plugin@1.19.0":
version "1.19.0"
resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-1.19.0.tgz#2b134318f1552ba7f3e3f9c83c71a202095f7a44"
@@ -4864,6 +5428,17 @@
"@types/babel__template" "*"
"@types/babel__traverse" "*"
+"@types/babel__core@^7.20.1":
+ version "7.20.2"
+ resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.2.tgz#215db4f4a35d710256579784a548907237728756"
+ integrity sha512-pNpr1T1xLUc2l3xJKuPtsEky3ybxN3m4fJkknfIpTCTfIZCDW57oAg+EfCgIIp2rvCe0Wn++/FfodDS4YXxBwA==
+ dependencies:
+ "@babel/parser" "^7.20.7"
+ "@babel/types" "^7.20.7"
+ "@types/babel__generator" "*"
+ "@types/babel__template" "*"
+ "@types/babel__traverse" "*"
+
"@types/babel__generator@*":
version "7.6.2"
resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.2.tgz#f3d71178e187858f7c45e30380f8f1b7415a12d8"
@@ -4957,6 +5532,13 @@
resolved "https://registry.yarnpkg.com/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz#2f98ede46acc0975de85c0b7b0ebe06041d24601"
integrity sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==
+"@types/debug@^4.0.0":
+ version "4.1.9"
+ resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.9.tgz#906996938bc672aaf2fb8c0d3733ae1dda05b005"
+ integrity sha512-8Hz50m2eoS56ldRlepxSBa6PWEVCtzUo/92HgLc2qTMnotJNIm7xP+UZhyWoYsyOdd5dxZ+NZLb24rsKyFs2ow==
+ dependencies:
+ "@types/ms" "*"
+
"@types/duplexify@^3.6.0":
version "3.6.0"
resolved "https://registry.yarnpkg.com/@types/duplexify/-/duplexify-3.6.0.tgz#dfc82b64bd3a2168f5bd26444af165bf0237dcd8"
@@ -5274,6 +5856,13 @@
dependencies:
"@types/node" "*"
+"@types/hast@^2.0.0":
+ version "2.3.6"
+ resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.6.tgz#bb8b05602112a26d22868acb70c4b20984ec7086"
+ integrity sha512-47rJE80oqPmFdVDCD7IheXBrVdwuBgsYwoczFvKmwfo2Mzsnt+V9OONsYauFmICb6lQPpCuXYJWejBNs4pDJRg==
+ dependencies:
+ "@types/unist" "^2"
+
"@types/history-4@npm:@types/history@4.7.8", "@types/history-5@npm:@types/history@4.7.8", "@types/history@*":
name "@types/history-4"
version "4.7.8"
@@ -5346,16 +5935,26 @@
"@types/parse5" "*"
"@types/tough-cookie" "*"
-"@types/json-schema@*", "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
+"@types/json-schema@*", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
version "7.0.11"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
+"@types/json-schema@^7.0.12":
+ version "7.0.13"
+ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.13.tgz#02c24f4363176d2d18fc8b70b9f3c54aba178a85"
+ integrity sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==
+
"@types/json5@^0.0.29":
version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
+"@types/json5@^0.0.30":
+ version "0.0.30"
+ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.30.tgz#44cb52f32a809734ca562e685c6473b5754a7818"
+ integrity sha512-sqm9g7mHlPY/43fcSNrCYfOeX9zkTTK+euO5E6+CVijSMm5tTjkVdwdqRkY3ljjIAf8679vps5jKUoJBCLsMDA==
+
"@types/long@^4.0.0", "@types/long@^4.0.1":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a"
@@ -5373,6 +5972,20 @@
dependencies:
"@types/node" "*"
+"@types/mdast@^3.0.0":
+ version "3.0.13"
+ resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.13.tgz#b7ba6e52d0faeb9c493e32c205f3831022be4e1b"
+ integrity sha512-HjiGiWedR0DVFkeNljpa6Lv4/IZU1+30VY5d747K7lBudFc3R0Ibr6yJ9lN3BE28VnZyDfLF/VB1Ql1ZIbKrmg==
+ dependencies:
+ "@types/unist" "^2"
+
+"@types/mdast@^4.0.0":
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-4.0.1.tgz#9c45e60a04e79f160dcefe6545d28ae536a6ed22"
+ integrity sha512-IlKct1rUTJ1T81d8OHzyop15kGv9A/ff7Gz7IJgrk6jDb4Udw77pCJ+vq8oxZf4Ghpm+616+i1s/LNg/Vh7d+g==
+ dependencies:
+ "@types/unist" "*"
+
"@types/mime@^1":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
@@ -5401,6 +6014,11 @@
"@types/bson" "*"
"@types/node" "*"
+"@types/ms@*":
+ version "0.7.32"
+ resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.32.tgz#f6cd08939ae3ad886fcc92ef7f0109dacddf61ab"
+ integrity sha512-xPSg0jm4mqgEkNhowKgZFBNtwoEwF6gJ4Dhww+GFpm3IgtNseHQZ5IqdNwnquZEoANxyDAKDRAdVo4Z72VvD/g==
+
"@types/mysql@2.15.21", "@types/mysql@^2.15.21":
version "2.15.21"
resolved "https://registry.yarnpkg.com/@types/mysql/-/mysql-2.15.21.tgz#7516cba7f9d077f980100c85fd500c8210bd5e45"
@@ -5408,6 +6026,13 @@
dependencies:
"@types/node" "*"
+"@types/nlcst@^1.0.0":
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/@types/nlcst/-/nlcst-1.0.2.tgz#dfcc9ef164e2d2a76ce7d249a9b909b7d0b7b616"
+ integrity sha512-ykxL/GDDUhqikjU0LIywZvEwb1NTYXTEWf+XgMSS2o6IXIakafPccxZmxgZcvJPZ3yFl2kdL1gJZz3U3iZF3QA==
+ dependencies:
+ "@types/unist" "^2"
+
"@types/node-fetch@^2.6.0":
version "2.6.2"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da"
@@ -5421,6 +6046,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.38.tgz#f8bb07c371ccb1903f3752872c89f44006132947"
integrity sha512-5jY9RhV7c0Z4Jy09G+NIDTsCZ5G0L5n+Z+p+Y7t5VJHM30bgwzSjVtlcBxqAj+6L/swIlvtOSzr8rBk/aNyV2g==
+"@types/node@20.8.2":
+ version "20.8.2"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.2.tgz#d76fb80d87d0d8abfe334fc6d292e83e5524efc4"
+ integrity sha512-Vvycsc9FQdwhxE3y3DzeIxuEJbWGDsnrxvMADzTDF/lcdR9/K+AQIeAghTQsHtotg/q0j3WEOYS/jQgSdWue3w==
+
"@types/node@^10.1.0", "@types/node@~10.17.0":
version "10.17.60"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b"
@@ -5456,6 +6086,11 @@
resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.0.tgz#38590dc2c3cf5717154064e3ee9b6947ee21b299"
integrity sha512-oPwPSj4a1wu9rsXTEGIJz91ISU725t0BmSnUhb57sI+M8XEmvUop84lzuiYdq0Y5M6xLY8DBPg0C2xEQKLyvBA==
+"@types/parse5@^6.0.0":
+ version "6.0.3"
+ resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.3.tgz#705bb349e789efa06f43f128cef51240753424cb"
+ integrity sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==
+
"@types/pg-pool@2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@types/pg-pool/-/pg-pool-2.0.3.tgz#3eb8df2933f617f219a53091ad4080c94ba1c959"
@@ -5571,6 +6206,11 @@
dependencies:
"@types/node" "*"
+"@types/resolve@^1.17.0":
+ version "1.20.3"
+ resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.3.tgz#066742d69a0bbba8c5d7d517f82e1140ddeb3c3c"
+ integrity sha512-NH5oErHOtHZYcjCtg69t26aXEk4BN2zLWqf7wnDZ+dpe0iR7Rds1SPGEItl3fca21oOe0n3OCnZ4W7jBxu7FOw==
+
"@types/rimraf@^2.0.2", "@types/rimraf@^2.0.3":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-2.0.4.tgz#403887b0b53c6100a6c35d2ab24f6ccc042fec46"
@@ -5602,6 +6242,11 @@
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91"
integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==
+"@types/semver@^7.5.0":
+ version "7.5.3"
+ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.3.tgz#9a726e116beb26c24f1ccd6850201e1246122e04"
+ integrity sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==
+
"@types/send@*":
version "0.17.1"
resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.1.tgz#ed4932b8a2a805f1fe362a70f4e62d0ac994e301"
@@ -5687,6 +6332,16 @@
dependencies:
source-map "^0.6.1"
+"@types/unist@*", "@types/unist@^3.0.0":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.0.tgz#988ae8af1e5239e89f9fbb1ade4c935f4eeedf9a"
+ integrity sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w==
+
+"@types/unist@^2", "@types/unist@^2.0.0":
+ version "2.0.8"
+ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.8.tgz#bb197b9639aa1a04cf464a617fe800cccd92ad5c"
+ integrity sha512-d0XxK3YTObnWVp6rZuev3c49+j4Lo8g4L1ZRm9z5L0xpoZycUPshHgczK5gsUMaZOstjVYYi09p5gYvUtfChYw==
+
"@types/webpack-sources@*":
version "3.2.0"
resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-3.2.0.tgz#16d759ba096c289034b26553d2df1bf45248d38b"
@@ -5758,17 +6413,6 @@
semver "^7.3.7"
tsutils "^3.21.0"
-"@typescript-eslint/experimental-utils@^2.19.2 || ^3.0.0":
- version "3.10.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz#e179ffc81a80ebcae2ea04e0332f8b251345a686"
- integrity sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==
- dependencies:
- "@types/json-schema" "^7.0.3"
- "@typescript-eslint/types" "3.10.1"
- "@typescript-eslint/typescript-estree" "3.10.1"
- eslint-scope "^5.0.0"
- eslint-utils "^2.0.0"
-
"@typescript-eslint/parser@^5.48.0":
version "5.48.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.48.0.tgz#02803355b23884a83e543755349809a50b7ed9ba"
@@ -5795,6 +6439,14 @@
"@typescript-eslint/types" "5.62.0"
"@typescript-eslint/visitor-keys" "5.62.0"
+"@typescript-eslint/scope-manager@6.7.4":
+ version "6.7.4"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.7.4.tgz#a484a17aa219e96044db40813429eb7214d7b386"
+ integrity sha512-SdGqSLUPTXAXi7c3Ob7peAGVnmMoGzZ361VswK2Mqf8UOYcODiYvs8rs5ILqEdfvX1lE7wEZbLyELCW+Yrql1A==
+ dependencies:
+ "@typescript-eslint/types" "6.7.4"
+ "@typescript-eslint/visitor-keys" "6.7.4"
+
"@typescript-eslint/type-utils@5.48.0":
version "5.48.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.48.0.tgz#40496dccfdc2daa14a565f8be80ad1ae3882d6d6"
@@ -5805,11 +6457,6 @@
debug "^4.3.4"
tsutils "^3.21.0"
-"@typescript-eslint/types@3.10.1":
- version "3.10.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727"
- integrity sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==
-
"@typescript-eslint/types@4.23.0":
version "4.23.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.23.0.tgz#da1654c8a5332f4d1645b2d9a1c64193cae3aa3b"
@@ -5825,19 +6472,10 @@
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f"
integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==
-"@typescript-eslint/typescript-estree@3.10.1":
- version "3.10.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz#fd0061cc38add4fad45136d654408569f365b853"
- integrity sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==
- dependencies:
- "@typescript-eslint/types" "3.10.1"
- "@typescript-eslint/visitor-keys" "3.10.1"
- debug "^4.1.1"
- glob "^7.1.6"
- is-glob "^4.0.1"
- lodash "^4.17.15"
- semver "^7.3.2"
- tsutils "^3.17.1"
+"@typescript-eslint/types@6.7.4":
+ version "6.7.4"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.7.4.tgz#5d358484d2be986980c039de68e9f1eb62ea7897"
+ integrity sha512-o9XWK2FLW6eSS/0r/tgjAGsYasLAnOWg7hvZ/dGYSSNjCh+49k5ocPN8OmG5aZcSJ8pclSOyVKP2x03Sj+RrCA==
"@typescript-eslint/typescript-estree@5.48.0":
version "5.48.0"
@@ -5865,6 +6503,19 @@
semver "^7.3.7"
tsutils "^3.21.0"
+"@typescript-eslint/typescript-estree@6.7.4":
+ version "6.7.4"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.4.tgz#f2baece09f7bb1df9296e32638b2e1130014ef1a"
+ integrity sha512-ty8b5qHKatlNYd9vmpHooQz3Vki3gG+3PchmtsA4TgrZBKWHNjWfkQid7K7xQogBqqc7/BhGazxMD5vr6Ha+iQ==
+ dependencies:
+ "@typescript-eslint/types" "6.7.4"
+ "@typescript-eslint/visitor-keys" "6.7.4"
+ debug "^4.3.4"
+ globby "^11.1.0"
+ is-glob "^4.0.3"
+ semver "^7.5.4"
+ ts-api-utils "^1.0.1"
+
"@typescript-eslint/typescript-estree@^4.8.2":
version "4.23.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.23.0.tgz#0753b292097523852428a6f5a1aa8ccc1aae6cd9"
@@ -5906,12 +6557,18 @@
eslint-scope "^5.1.1"
semver "^7.3.7"
-"@typescript-eslint/visitor-keys@3.10.1":
- version "3.10.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz#cd4274773e3eb63b2e870ac602274487ecd1e931"
- integrity sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==
+"@typescript-eslint/utils@^6.0.0":
+ version "6.7.4"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.7.4.tgz#2236f72b10e38277ee05ef06142522e1de470ff2"
+ integrity sha512-PRQAs+HUn85Qdk+khAxsVV+oULy3VkbH3hQ8hxLRJXWBEd7iI+GbQxH5SEUSH7kbEoTp6oT1bOwyga24ELALTA==
dependencies:
- eslint-visitor-keys "^1.1.0"
+ "@eslint-community/eslint-utils" "^4.4.0"
+ "@types/json-schema" "^7.0.12"
+ "@types/semver" "^7.5.0"
+ "@typescript-eslint/scope-manager" "6.7.4"
+ "@typescript-eslint/types" "6.7.4"
+ "@typescript-eslint/typescript-estree" "6.7.4"
+ semver "^7.5.4"
"@typescript-eslint/visitor-keys@4.23.0":
version "4.23.0"
@@ -5937,6 +6594,14 @@
"@typescript-eslint/types" "5.62.0"
eslint-visitor-keys "^3.3.0"
+"@typescript-eslint/visitor-keys@6.7.4":
+ version "6.7.4"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.4.tgz#80dfecf820fc67574012375859085f91a4dff043"
+ integrity sha512-pOW37DUhlTZbvph50x5zZCkFn3xzwkGtNoJHzIM3svpiSkJzwOYr/kVBaXmf+RAQiUDs1AHEZVNPg6UJCJpwRA==
+ dependencies:
+ "@typescript-eslint/types" "6.7.4"
+ eslint-visitor-keys "^3.4.1"
+
"@vitest/coverage-c8@^0.29.2":
version "0.29.2"
resolved "https://registry.yarnpkg.com/@vitest/coverage-c8/-/coverage-c8-0.29.2.tgz#30b81e32ff11c20e2f3ab78c84e21b4c6c08190c"
@@ -6484,6 +7149,11 @@ acorn@^8.0.4, acorn@^8.2.4, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.0, acorn@^8.7
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
+acorn@^8.10.0:
+ version "8.10.0"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5"
+ integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
+
add-stream@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa"
@@ -6628,7 +7298,7 @@ anser@1.4.9:
resolved "https://registry.yarnpkg.com/anser/-/anser-1.4.9.tgz#1f85423a5dcf8da4631a341665ff675b96845760"
integrity sha512-AI+BjTeGt2+WFk4eWcqbQ7snZpDBt8SaLlj0RT2h5xfdWaiy51OjYvqwMrNzJLGy8iOAL6nKDITWO+rd4MkYEA==
-ansi-align@^3.0.0:
+ansi-align@^3.0.0, ansi-align@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59"
integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==
@@ -6704,6 +7374,11 @@ ansi-regex@^6.0.1:
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a"
integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==
+ansi-sequence-parser@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz#e0aa1cdcbc8f8bb0b5bca625aac41f5f056973cf"
+ integrity sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==
+
ansi-styles@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
@@ -7024,6 +7699,11 @@ array-includes@^3.1.1, array-includes@^3.1.2, array-includes@^3.1.6:
get-intrinsic "^1.1.3"
is-string "^1.0.7"
+array-iterate@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/array-iterate/-/array-iterate-2.0.1.tgz#6efd43f8295b3fee06251d3d62ead4bd9805dd24"
+ integrity sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==
+
array-to-error@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array-to-error/-/array-to-error-1.1.1.tgz#d68812926d14097a205579a667eeaf1856a44c07"
@@ -7193,6 +7873,69 @@ astral-regex@^2.0.0:
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
+astro@^3.2.3:
+ version "3.2.3"
+ resolved "https://registry.yarnpkg.com/astro/-/astro-3.2.3.tgz#a6f14bf946555683ee1537a345e4a819a3aeff9b"
+ integrity sha512-1epnxQhTbfzgdmLP1yu51E8zjIOKYxZyA8hMTD4S2E+F5gMp/D81H4hekPbbq89GDxNJiHDRNZDHtS5vrU5E5w==
+ dependencies:
+ "@astrojs/compiler" "^2.1.0"
+ "@astrojs/internal-helpers" "0.2.1"
+ "@astrojs/markdown-remark" "3.2.1"
+ "@astrojs/telemetry" "3.0.3"
+ "@babel/core" "^7.22.10"
+ "@babel/generator" "^7.22.10"
+ "@babel/parser" "^7.22.10"
+ "@babel/plugin-transform-react-jsx" "^7.22.5"
+ "@babel/traverse" "^7.22.10"
+ "@babel/types" "^7.22.10"
+ "@types/babel__core" "^7.20.1"
+ acorn "^8.10.0"
+ boxen "^7.1.1"
+ chokidar "^3.5.3"
+ ci-info "^3.8.0"
+ clsx "^2.0.0"
+ common-ancestor-path "^1.0.1"
+ cookie "^0.5.0"
+ debug "^4.3.4"
+ devalue "^4.3.2"
+ diff "^5.1.0"
+ es-module-lexer "^1.3.0"
+ esbuild "^0.19.2"
+ estree-walker "^3.0.3"
+ execa "^8.0.1"
+ fast-glob "^3.3.1"
+ github-slugger "^2.0.0"
+ gray-matter "^4.0.3"
+ html-escaper "^3.0.3"
+ http-cache-semantics "^4.1.1"
+ js-yaml "^4.1.0"
+ kleur "^4.1.4"
+ magic-string "^0.30.3"
+ mime "^3.0.0"
+ ora "^7.0.1"
+ p-limit "^4.0.0"
+ path-to-regexp "^6.2.1"
+ preferred-pm "^3.1.2"
+ probe-image-size "^7.2.3"
+ prompts "^2.4.2"
+ rehype "^12.0.1"
+ resolve "^1.22.4"
+ semver "^7.5.4"
+ server-destroy "^1.0.1"
+ shiki "^0.14.3"
+ string-width "^6.1.0"
+ strip-ansi "^7.1.0"
+ tsconfig-resolver "^3.0.1"
+ unist-util-visit "^4.1.2"
+ vfile "^5.3.7"
+ vite "^4.4.9"
+ vitefu "^0.2.4"
+ which-pm "^2.1.1"
+ yargs-parser "^21.1.1"
+ zod "3.21.1"
+ optionalDependencies:
+ sharp "^0.32.5"
+
async-disk-cache@^1.2.1:
version "1.3.5"
resolved "https://registry.yarnpkg.com/async-disk-cache/-/async-disk-cache-1.3.5.tgz#cc6206ed79bb6982b878fc52e0505e4f52b62a02"
@@ -7372,6 +8115,11 @@ axios@^1.0.0:
form-data "^4.0.0"
proxy-from-env "^1.1.0"
+b4a@^1.6.4:
+ version "1.6.4"
+ resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.4.tgz#ef1c1422cae5ce6535ec191baeed7567443f36c9"
+ integrity sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==
+
babel-code-frame@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
@@ -8178,6 +8926,11 @@ backbone@^1.1.2:
dependencies:
underscore ">=1.8.3"
+bail@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d"
+ integrity sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==
+
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
@@ -8299,6 +9052,15 @@ bl@^4.0.3, bl@^4.1.0:
inherits "^2.0.4"
readable-stream "^3.4.0"
+bl@^5.0.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/bl/-/bl-5.1.0.tgz#183715f678c7188ecef9fe475d90209400624273"
+ integrity sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==
+ dependencies:
+ buffer "^6.0.3"
+ inherits "^2.0.4"
+ readable-stream "^3.4.0"
+
blank-object@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/blank-object/-/blank-object-1.0.2.tgz#f990793fbe9a8c8dd013fb3219420bec81d5f4b9"
@@ -8400,6 +9162,20 @@ boxen@^5.0.0:
widest-line "^3.1.0"
wrap-ansi "^7.0.0"
+boxen@^7.1.1:
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/boxen/-/boxen-7.1.1.tgz#f9ba525413c2fec9cdb88987d835c4f7cad9c8f4"
+ integrity sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==
+ dependencies:
+ ansi-align "^3.0.1"
+ camelcase "^7.0.1"
+ chalk "^5.2.0"
+ cli-boxes "^3.0.0"
+ string-width "^5.1.2"
+ type-fest "^2.13.0"
+ widest-line "^4.0.1"
+ wrap-ansi "^8.1.0"
+
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -9020,6 +9796,16 @@ browserslist@^4.16.1, browserslist@^4.21.5, browserslist@^4.6.4:
node-releases "^2.0.8"
update-browserslist-db "^1.0.10"
+browserslist@^4.21.9:
+ version "4.22.1"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.1.tgz#ba91958d1a59b87dab6fed8dfbcb3da5e2e9c619"
+ integrity sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==
+ dependencies:
+ caniuse-lite "^1.0.30001541"
+ electron-to-chromium "^1.4.535"
+ node-releases "^2.0.13"
+ update-browserslist-db "^1.0.13"
+
browserstack-local@^1.3.7:
version "1.4.8"
resolved "https://registry.yarnpkg.com/browserstack-local/-/browserstack-local-1.4.8.tgz#07f74a19b324cf2de69ffe65f9c2baa3a2dd9a0e"
@@ -9123,8 +9909,16 @@ buffer@^5.2.1, buffer@^5.5.0, buffer@^5.6.0:
base64-js "^1.3.1"
ieee754 "^1.1.13"
-builtin-modules@^3.1.0:
- version "3.2.0"
+buffer@^6.0.3:
+ version "6.0.3"
+ resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
+ integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
+ dependencies:
+ base64-js "^1.3.1"
+ ieee754 "^1.2.1"
+
+builtin-modules@^3.1.0:
+ version "3.2.0"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887"
integrity sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==
@@ -9463,6 +10257,11 @@ camelcase@^6.0.0, camelcase@^6.1.0, camelcase@^6.2.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
+camelcase@^7.0.1:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-7.0.1.tgz#f02e50af9fd7782bc8b88a3558c32fd3a388f048"
+ integrity sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==
+
can-symlink@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/can-symlink/-/can-symlink-1.0.0.tgz#97b607d8a84bb6c6e228b902d864ecb594b9d219"
@@ -9490,6 +10289,11 @@ caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001449:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001453.tgz#6d3a1501622bf424a3cee5ad9550e640b0de3de8"
integrity sha512-R9o/uySW38VViaTrOtwfbFEiBFUh7ST3uIG4OEymIG3/uKdHDO4xk/FaqfUw0d+irSUyFPy3dZszf9VvSTPnsA==
+caniuse-lite@^1.0.30001541:
+ version "1.0.30001546"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001546.tgz#10fdad03436cfe3cc632d3af7a99a0fb497407f0"
+ integrity sha512-zvtSJwuQFpewSyRrI3AsftF6rM0X80mZkChIt1spBGEvRglCrjTniXvinc8JKRoqTwXAgvqTImaN9igfSMtUBw==
+
canonical-path@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/canonical-path/-/canonical-path-1.0.0.tgz#fcb470c23958def85081856be7a86e904f180d1d"
@@ -9515,6 +10319,11 @@ caseless@~0.12.0:
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
+ccount@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5"
+ integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==
+
chai@^4.1.2:
version "4.3.4"
resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.4.tgz#b55e655b31e1eac7099be4c08c21964fce2e6c49"
@@ -9592,11 +10401,31 @@ chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
+chalk@^5.0.0, chalk@^5.2.0, chalk@^5.3.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385"
+ integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==
+
char-regex@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==
+character-entities-html4@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b"
+ integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==
+
+character-entities-legacy@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b"
+ integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==
+
+character-entities@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22"
+ integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==
+
chardet@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
@@ -9695,6 +10524,11 @@ ci-info@^3.6.1:
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91"
integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==
+ci-info@^3.8.0:
+ version "3.9.0"
+ resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4"
+ integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==
+
ci-job-number@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/ci-job-number/-/ci-job-number-1.2.2.tgz#f4e5918fcaeeda95b604f214be7d7d4a961fe0c0"
@@ -9787,6 +10621,11 @@ cli-boxes@^2.2.1:
resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f"
integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==
+cli-boxes@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-3.0.0.tgz#71a10c716feeba005e4504f36329ef0b17cf3145"
+ integrity sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==
+
cli-cursor@3.1.0, cli-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
@@ -9801,11 +10640,23 @@ cli-cursor@^2.1.0:
dependencies:
restore-cursor "^2.0.0"
+cli-cursor@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-4.0.0.tgz#3cecfe3734bf4fe02a8361cbdc0f6fe28c6a57ea"
+ integrity sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==
+ dependencies:
+ restore-cursor "^4.0.0"
+
cli-spinners@2.6.1, cli-spinners@^2.0.0, cli-spinners@^2.4.0, cli-spinners@^2.5.0:
version "2.6.1"
resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.1.tgz#adc954ebe281c37a6319bfa401e6dd2488ffb70d"
integrity sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==
+cli-spinners@^2.9.0:
+ version "2.9.1"
+ resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.1.tgz#9c0b9dad69a6d47cbb4333c14319b060ed395a35"
+ integrity sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==
+
cli-table3@^0.6.0:
version "0.6.3"
resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.3.tgz#61ab765aac156b52f222954ffc607a6f01dbeeb2"
@@ -9902,6 +10753,11 @@ clone@^2.1.2:
resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=
+clsx@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.0.0.tgz#12658f3fd98fafe62075595a5c30e43d18f3d00b"
+ integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==
+
cmd-shim@6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-6.0.1.tgz#a65878080548e1dca760b3aea1e21ed05194da9d"
@@ -9982,7 +10838,7 @@ color-string@^1.5.4:
color-name "^1.0.0"
simple-swizzle "^0.2.2"
-color-string@^1.6.0:
+color-string@^1.6.0, color-string@^1.9.0:
version "1.9.1"
resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4"
integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==
@@ -10011,6 +10867,14 @@ color@^3.1.3:
color-convert "^1.9.3"
color-string "^1.6.0"
+color@^4.2.3:
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a"
+ integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==
+ dependencies:
+ color-convert "^2.0.1"
+ color-string "^1.9.0"
+
colord@^2.9.1:
version "2.9.3"
resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43"
@@ -10064,6 +10928,11 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
+comma-separated-tokens@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee"
+ integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==
+
commander@10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.0.tgz#71797971162cd3cf65f0b9d24eb28f8d303acdf1"
@@ -10111,6 +10980,11 @@ commenting@1.1.0:
resolved "https://registry.yarnpkg.com/commenting/-/commenting-1.1.0.tgz#fae14345c6437b8554f30bc6aa6c1e1633033590"
integrity sha512-YeNK4tavZwtH7jEgK1ZINXzLKm6DZdEMfsaaieOsCAN0S8vsY7UeuO3Q7d/M018EFgE+IeUAuBOKkFccBZsUZA==
+common-ancestor-path@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz#4f7d2d1394d91b7abdf51871c62f71eadb0182a7"
+ integrity sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==
+
common-tags@^1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937"
@@ -10353,6 +11227,11 @@ convert-source-map@^0.3.3:
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-0.3.5.tgz#f1d802950af7dd2631a1febe0596550c86ab3190"
integrity sha1-8dgClQr33SYxof6+BZZVDIarMZA=
+convert-source-map@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
+ integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
+
convert-source-map@~1.1.0:
version "1.1.3"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.1.3.tgz#4829c877e9fe49b3161f3bf3673888e204699860"
@@ -11186,6 +12065,13 @@ decimal.js@^10.2.1, decimal.js@^10.3.1:
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783"
integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==
+decode-named-character-reference@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz#daabac9690874c394c81e4162a0304b35d824f0e"
+ integrity sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==
+ dependencies:
+ character-entities "^2.0.0"
+
decode-uri-component@^0.2.0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9"
@@ -11205,6 +12091,13 @@ decompress-response@^3.3.0:
dependencies:
mimic-response "^1.0.0"
+decompress-response@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc"
+ integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==
+ dependencies:
+ mimic-response "^3.1.0"
+
dedent@0.7.0, dedent@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
@@ -11380,6 +12273,11 @@ deprecation@^2.0.0, deprecation@^2.3.1:
resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919"
integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==
+dequal@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be"
+ integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
+
des.js@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843"
@@ -11415,6 +12313,11 @@ detect-indent@^6.0.0:
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.0.0.tgz#0abd0f549f69fc6659a254fe96786186b6f528fd"
integrity sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==
+detect-libc@^2.0.0, detect-libc@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.2.tgz#8ccf2ba9315350e1241b88d0ac3b0e1fbd99605d"
+ integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==
+
detect-newline@3.1.0, detect-newline@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
@@ -11517,6 +12420,11 @@ devalue@^4.3.0:
resolved "https://registry.yarnpkg.com/devalue/-/devalue-4.3.0.tgz#d86db8fee63a70317c2355be0d3d1b4d8f89a44e"
integrity sha512-n94yQo4LI3w7erwf84mhRUkUJfhLoCZiLyoOZ/QFsDbcWNZePrLwbQpvZBUG2TNxwV3VjCKPxkiiQA6pe3TrTA==
+devalue@^4.3.2:
+ version "4.3.2"
+ resolved "https://registry.yarnpkg.com/devalue/-/devalue-4.3.2.tgz#cc44e4cf3872ac5a78229fbce3b77e57032727b5"
+ integrity sha512-KqFl6pOgOW+Y6wJgu80rHpo2/3H07vr8ntR9rkkFIRETewbf5GaYYcakYfiKz89K+sLsuPkQIZaXDMjUObZwWg==
+
dezalgo@^1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456"
@@ -11550,7 +12458,7 @@ diff@^4.0.1:
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
-diff@^5.1.0:
+diff@^5.0.0, diff@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40"
integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==
@@ -11571,6 +12479,11 @@ dir-glob@^3.0.1:
dependencies:
path-type "^4.0.0"
+dlv@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79"
+ integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==
+
dns-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
@@ -11737,6 +12650,11 @@ dotenv@16.0.3:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07"
integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==
+dotenv@^16.3.1:
+ version "16.3.1"
+ resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e"
+ integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==
+
dotenv@~10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
@@ -11751,6 +12669,11 @@ downlevel-dts@~0.11.0:
shelljs "^0.8.3"
typescript next
+dset@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/dset/-/dset-3.1.2.tgz#89c436ca6450398396dc6538ea00abc0c54cd45a"
+ integrity sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q==
+
duplexer3@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
@@ -11836,6 +12759,11 @@ electron-to-chromium@^1.4.284:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.299.tgz#faa2069cd4879a73e540e533178db5c618768d41"
integrity sha512-lQ7ijJghH6pCGbfWXr6EY+KYCMaRSjgsY925r1p/TlpSfVM1VjHTcn1gAc15VM4uwti283X6QtjPTXdpoSGiZQ==
+electron-to-chromium@^1.4.535:
+ version "1.4.543"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.543.tgz#51116ffc9fba1ee93514d6a40d34676aa6d7d1c4"
+ integrity sha512-t2ZP4AcGE0iKCCQCBx/K2426crYdxD3YU6l0uK2EO3FZH0pbC4pFz/sZm2ruZsND6hQBTcDWWlo/MLpiOdif5g==
+
elliptic@^6.5.3:
version "6.5.4"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
@@ -12536,6 +13464,11 @@ emittery@^0.8.1:
resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860"
integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==
+emoji-regex@^10.2.1:
+ version "10.2.1"
+ resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.2.1.tgz#a41c330d957191efd3d9dfe6e1e8e1e9ab048b3f"
+ integrity sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA==
+
emoji-regex@^7.0.1:
version "7.0.3"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
@@ -12771,6 +13704,11 @@ es-module-lexer@^0.9.0:
resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19"
integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==
+es-module-lexer@^1.3.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.3.1.tgz#c1b0dd5ada807a3b3155315911f364dc4e909db1"
+ integrity sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==
+
es-shim-unscopables@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241"
@@ -12964,6 +13902,62 @@ esbuild@^0.16.14, esbuild@^0.16.3:
"@esbuild/win32-ia32" "0.16.17"
"@esbuild/win32-x64" "0.16.17"
+esbuild@^0.18.10:
+ version "0.18.20"
+ resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.20.tgz#4709f5a34801b43b799ab7d6d82f7284a9b7a7a6"
+ integrity sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==
+ optionalDependencies:
+ "@esbuild/android-arm" "0.18.20"
+ "@esbuild/android-arm64" "0.18.20"
+ "@esbuild/android-x64" "0.18.20"
+ "@esbuild/darwin-arm64" "0.18.20"
+ "@esbuild/darwin-x64" "0.18.20"
+ "@esbuild/freebsd-arm64" "0.18.20"
+ "@esbuild/freebsd-x64" "0.18.20"
+ "@esbuild/linux-arm" "0.18.20"
+ "@esbuild/linux-arm64" "0.18.20"
+ "@esbuild/linux-ia32" "0.18.20"
+ "@esbuild/linux-loong64" "0.18.20"
+ "@esbuild/linux-mips64el" "0.18.20"
+ "@esbuild/linux-ppc64" "0.18.20"
+ "@esbuild/linux-riscv64" "0.18.20"
+ "@esbuild/linux-s390x" "0.18.20"
+ "@esbuild/linux-x64" "0.18.20"
+ "@esbuild/netbsd-x64" "0.18.20"
+ "@esbuild/openbsd-x64" "0.18.20"
+ "@esbuild/sunos-x64" "0.18.20"
+ "@esbuild/win32-arm64" "0.18.20"
+ "@esbuild/win32-ia32" "0.18.20"
+ "@esbuild/win32-x64" "0.18.20"
+
+esbuild@^0.19.2:
+ version "0.19.4"
+ resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.4.tgz#cdf5c4c684956d550bc3c6d0c01dac7fef6c75b1"
+ integrity sha512-x7jL0tbRRpv4QUyuDMjONtWFciygUxWaUM1kMX2zWxI0X2YWOt7MSA0g4UdeSiHM8fcYVzpQhKYOycZwxTdZkA==
+ optionalDependencies:
+ "@esbuild/android-arm" "0.19.4"
+ "@esbuild/android-arm64" "0.19.4"
+ "@esbuild/android-x64" "0.19.4"
+ "@esbuild/darwin-arm64" "0.19.4"
+ "@esbuild/darwin-x64" "0.19.4"
+ "@esbuild/freebsd-arm64" "0.19.4"
+ "@esbuild/freebsd-x64" "0.19.4"
+ "@esbuild/linux-arm" "0.19.4"
+ "@esbuild/linux-arm64" "0.19.4"
+ "@esbuild/linux-ia32" "0.19.4"
+ "@esbuild/linux-loong64" "0.19.4"
+ "@esbuild/linux-mips64el" "0.19.4"
+ "@esbuild/linux-ppc64" "0.19.4"
+ "@esbuild/linux-riscv64" "0.19.4"
+ "@esbuild/linux-s390x" "0.19.4"
+ "@esbuild/linux-x64" "0.19.4"
+ "@esbuild/netbsd-x64" "0.19.4"
+ "@esbuild/openbsd-x64" "0.19.4"
+ "@esbuild/sunos-x64" "0.19.4"
+ "@esbuild/win32-arm64" "0.19.4"
+ "@esbuild/win32-ia32" "0.19.4"
+ "@esbuild/win32-x64" "0.19.4"
+
escalade@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
@@ -12994,6 +13988,11 @@ escape-string-regexp@^4.0.0:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+escape-string-regexp@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8"
+ integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==
+
escodegen@1.8.x:
version "1.8.1"
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018"
@@ -13041,14 +14040,14 @@ eslint-module-utils@^2.6.0:
debug "^2.6.9"
pkg-dir "^2.0.0"
-eslint-plugin-deprecation@^1.1.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-deprecation/-/eslint-plugin-deprecation-1.2.0.tgz#e12333a857986fc87fa2eff44c7425eba9653070"
- integrity sha512-SrZqomFYofRbxJ9dlAcu526/tiZoWoZgHdZWKHjrRT/uLfTtTTjdVf0gdy0AZxK8nH5ri0fukgwS28llUueitA==
+eslint-plugin-deprecation@^1.5.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-deprecation/-/eslint-plugin-deprecation-1.6.0.tgz#b12d0c5a9baf3bcde0752ff6337703c059a4ae23"
+ integrity sha512-rld+Vrneh/NXRtDB0vQifOvgUy0HJYoejaxWlVnsk/LK7iij2tCWQIFcCKG4uzQb+Ef86bDke39w1lh4wnon4Q==
dependencies:
- "@typescript-eslint/experimental-utils" "^2.19.2 || ^3.0.0"
- tslib "^1.10.0"
- tsutils "^3.0.0"
+ "@typescript-eslint/utils" "^6.0.0"
+ tslib "^2.3.1"
+ tsutils "^3.21.0"
eslint-plugin-ember@11.9.0:
version "11.9.0"
@@ -13169,7 +14168,7 @@ eslint-plugin-simple-import-sort@^5.0.3:
resolved "https://registry.yarnpkg.com/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-5.0.3.tgz#9ae258ddada6efffc55e47a134afbd279eb31fc6"
integrity sha512-1rf3AWiHeWNCQdAq0iXNnlccnH1UDnelGgrPbjBBHE8d2hXVtOudcmy0vTF4hri3iJ0MKz8jBhmH6lJ0ZWZLHQ==
-eslint-scope@5.1.1, eslint-scope@^5.0.0, eslint-scope@^5.1.1:
+eslint-scope@5.1.1, eslint-scope@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
@@ -13185,7 +14184,7 @@ eslint-scope@^4.0.3:
esrecurse "^4.1.0"
estraverse "^4.1.1"
-eslint-utils@^2.0.0, eslint-utils@^2.1.0:
+eslint-utils@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27"
integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==
@@ -13214,6 +14213,11 @@ eslint-visitor-keys@^3.3.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
+eslint-visitor-keys@^3.4.1:
+ version "3.4.3"
+ resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
+ integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
+
eslint@7.32.0:
version "7.32.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d"
@@ -13338,6 +14342,13 @@ estree-walker@^2.0.1, estree-walker@^2.0.2:
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
+estree-walker@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d"
+ integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==
+ dependencies:
+ "@types/estree" "^1.0.0"
+
esutils@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
@@ -13489,6 +14500,21 @@ execa@^5.0.0, execa@^5.1.1:
signal-exit "^3.0.3"
strip-final-newline "^2.0.0"
+execa@^8.0.1:
+ version "8.0.1"
+ resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c"
+ integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==
+ dependencies:
+ cross-spawn "^7.0.3"
+ get-stream "^8.0.1"
+ human-signals "^5.0.0"
+ is-stream "^3.0.0"
+ merge-stream "^2.0.0"
+ npm-run-path "^5.1.0"
+ onetime "^6.0.0"
+ signal-exit "^4.1.0"
+ strip-final-newline "^3.0.0"
+
exists-sync@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/exists-sync/-/exists-sync-0.1.0.tgz#318d545213d2b2a31499e92c35f74c94196a22f7"
@@ -13512,6 +14538,11 @@ expand-brackets@^2.1.4:
snapdragon "^0.8.1"
to-regex "^3.0.1"
+expand-template@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
+ integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
+
expand-tilde@^2.0.0, expand-tilde@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502"
@@ -13654,6 +14685,11 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
+fast-fifo@^1.1.0, fast-fifo@^1.2.0:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c"
+ integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==
+
fast-glob@3.2.7:
version "3.2.7"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1"
@@ -13676,6 +14712,17 @@ fast-glob@^3.0.3, fast-glob@^3.2.11, fast-glob@^3.2.12, fast-glob@^3.2.4, fast-g
merge2 "^1.3.0"
micromatch "^4.0.4"
+fast-glob@^3.3.1:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4"
+ integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==
+ dependencies:
+ "@nodelib/fs.stat" "^2.0.2"
+ "@nodelib/fs.walk" "^1.2.3"
+ glob-parent "^5.1.2"
+ merge2 "^1.3.0"
+ micromatch "^4.0.4"
+
fast-json-stable-stringify@2.1.0, fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
@@ -14004,6 +15051,14 @@ find-up@^6.3.0:
locate-path "^7.1.0"
path-exists "^5.0.0"
+find-yarn-workspace-root2@1.2.16:
+ version "1.2.16"
+ resolved "https://registry.yarnpkg.com/find-yarn-workspace-root2/-/find-yarn-workspace-root2-1.2.16.tgz#60287009dd2f324f59646bdb4b7610a6b301c2a9"
+ integrity sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==
+ dependencies:
+ micromatch "^4.0.2"
+ pkg-dir "^4.2.0"
+
find-yarn-workspace-root@^1.1.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-1.2.1.tgz#40eb8e6e7c2502ddfaa2577c176f221422f860db"
@@ -14619,6 +15674,11 @@ get-stream@^5.0.0, get-stream@^5.1.0:
dependencies:
pump "^3.0.0"
+get-stream@^8.0.1:
+ version "8.0.1"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2"
+ integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==
+
get-symbol-description@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6"
@@ -14696,6 +15756,16 @@ gitconfiglocal@^1.0.0:
dependencies:
ini "^1.3.2"
+github-from-package@0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
+ integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==
+
+github-slugger@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-2.0.0.tgz#52cf2f9279a21eb6c59dd385b410f0c0adda8f1a"
+ integrity sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==
+
glob-parent@5.1.2, glob-parent@^5.1.1, glob-parent@^5.1.2, glob-parent@~5.1.0, glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
@@ -15069,7 +16139,7 @@ got@^9.6.0:
to-readable-stream "^1.0.0"
url-parse-lax "^3.0.0"
-graceful-fs@4.2.11:
+graceful-fs@4.2.11, graceful-fs@^4.1.5:
version "4.2.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
@@ -15103,6 +16173,16 @@ graphviz@0.0.9:
dependencies:
temp "~0.4.0"
+gray-matter@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-4.0.3.tgz#e893c064825de73ea1f5f7d88c7a9f7274288798"
+ integrity sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==
+ dependencies:
+ js-yaml "^3.13.1"
+ kind-of "^6.0.2"
+ section-matter "^1.0.0"
+ strip-bom-string "^1.0.0"
+
growl@1.10.5:
version "1.10.5"
resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
@@ -15311,6 +16391,88 @@ hash.js@^1.0.0, hash.js@^1.0.3:
inherits "^2.0.3"
minimalistic-assert "^1.0.1"
+hast-util-from-parse5@^7.0.0:
+ version "7.1.2"
+ resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz#aecfef73e3ceafdfa4550716443e4eb7b02e22b0"
+ integrity sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==
+ dependencies:
+ "@types/hast" "^2.0.0"
+ "@types/unist" "^2.0.0"
+ hastscript "^7.0.0"
+ property-information "^6.0.0"
+ vfile "^5.0.0"
+ vfile-location "^4.0.0"
+ web-namespaces "^2.0.0"
+
+hast-util-parse-selector@^3.0.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz#25ab00ae9e75cbc62cf7a901f68a247eade659e2"
+ integrity sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==
+ dependencies:
+ "@types/hast" "^2.0.0"
+
+hast-util-raw@^7.0.0, hast-util-raw@^7.2.0:
+ version "7.2.3"
+ resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-7.2.3.tgz#dcb5b22a22073436dbdc4aa09660a644f4991d99"
+ integrity sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==
+ dependencies:
+ "@types/hast" "^2.0.0"
+ "@types/parse5" "^6.0.0"
+ hast-util-from-parse5 "^7.0.0"
+ hast-util-to-parse5 "^7.0.0"
+ html-void-elements "^2.0.0"
+ parse5 "^6.0.0"
+ unist-util-position "^4.0.0"
+ unist-util-visit "^4.0.0"
+ vfile "^5.0.0"
+ web-namespaces "^2.0.0"
+ zwitch "^2.0.0"
+
+hast-util-to-html@^8.0.0:
+ version "8.0.4"
+ resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-8.0.4.tgz#0269ef33fa3f6599b260a8dc94f733b8e39e41fc"
+ integrity sha512-4tpQTUOr9BMjtYyNlt0P50mH7xj0Ks2xpo8M943Vykljf99HW6EzulIoJP1N3eKOSScEHzyzi9dm7/cn0RfGwA==
+ dependencies:
+ "@types/hast" "^2.0.0"
+ "@types/unist" "^2.0.0"
+ ccount "^2.0.0"
+ comma-separated-tokens "^2.0.0"
+ hast-util-raw "^7.0.0"
+ hast-util-whitespace "^2.0.0"
+ html-void-elements "^2.0.0"
+ property-information "^6.0.0"
+ space-separated-tokens "^2.0.0"
+ stringify-entities "^4.0.0"
+ zwitch "^2.0.4"
+
+hast-util-to-parse5@^7.0.0:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz#c49391bf8f151973e0c9adcd116b561e8daf29f3"
+ integrity sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==
+ dependencies:
+ "@types/hast" "^2.0.0"
+ comma-separated-tokens "^2.0.0"
+ property-information "^6.0.0"
+ space-separated-tokens "^2.0.0"
+ web-namespaces "^2.0.0"
+ zwitch "^2.0.0"
+
+hast-util-whitespace@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz#0ec64e257e6fc216c7d14c8a1b74d27d650b4557"
+ integrity sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==
+
+hastscript@^7.0.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-7.2.0.tgz#0eafb7afb153d047077fa2a833dc9b7ec604d10b"
+ integrity sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==
+ dependencies:
+ "@types/hast" "^2.0.0"
+ comma-separated-tokens "^2.0.0"
+ hast-util-parse-selector "^3.0.0"
+ property-information "^6.0.0"
+ space-separated-tokens "^2.0.0"
+
hdr-histogram-js@^2.0.1:
version "2.0.3"
resolved "https://registry.yarnpkg.com/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz#0b860534655722b6e3f3e7dca7b78867cf43dcb5"
@@ -15546,6 +16708,11 @@ html-escaper@^2.0.0:
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
+html-escaper@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-3.0.3.tgz#4d336674652beb1dcbc29ef6b6ba7f6be6fdfed6"
+ integrity sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==
+
html-minifier-terser@^6.0.2:
version "6.1.0"
resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#bfc818934cc07918f6b3669f5774ecdfd48f32ab"
@@ -15559,6 +16726,11 @@ html-minifier-terser@^6.0.2:
relateurl "^0.2.7"
terser "^5.10.0"
+html-void-elements@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-2.0.1.tgz#29459b8b05c200b6c5ee98743c41b979d577549f"
+ integrity sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==
+
html-webpack-plugin@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz#c3911936f57681c1f9f4d8b68c158cd9dfe52f50"
@@ -15745,6 +16917,11 @@ human-signals@^2.1.0:
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
+human-signals@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28"
+ integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==
+
humanize-ms@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed"
@@ -15783,7 +16960,7 @@ ieee754@1.1.13:
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
-ieee754@^1.1.13, ieee754@^1.1.4:
+ieee754@^1.1.13, ieee754@^1.1.4, ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
@@ -15917,6 +17094,11 @@ import-local@^2.0.0:
pkg-dir "^3.0.0"
resolve-cwd "^2.0.0"
+import-meta-resolve@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/import-meta-resolve/-/import-meta-resolve-3.0.0.tgz#94a6aabc623874fbc2f3525ec1300db71c6cbc11"
+ integrity sha512-4IwhLhNNA8yy445rPjD/lWh++7hMDOml2eHtd58eG7h+qK3EryMuuRbsHGPikCoAgIkkDnckKfWSk2iDla/ejg==
+
imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
@@ -16233,7 +17415,7 @@ is-buffer@^1.1.5:
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
-is-buffer@~2.0.3:
+is-buffer@^2.0.0, is-buffer@~2.0.3:
version "2.0.5"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191"
integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==
@@ -16283,6 +17465,13 @@ is-core-module@^2.11.0, is-core-module@^2.12.1, is-core-module@^2.5.0:
dependencies:
has "^1.0.3"
+is-core-module@^2.13.0:
+ version "2.13.0"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db"
+ integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==
+ dependencies:
+ has "^1.0.3"
+
is-core-module@^2.2.0, is-core-module@^2.8.1, is-core-module@^2.9.0:
version "2.11.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144"
@@ -16337,6 +17526,11 @@ is-docker@^2.0.0, is-docker@^2.1.1:
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
+is-docker@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200"
+ integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==
+
is-extendable@^0.1.0, is-extendable@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
@@ -16410,6 +17604,13 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
dependencies:
is-extglob "^2.1.1"
+is-inside-container@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4"
+ integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==
+ dependencies:
+ is-docker "^3.0.0"
+
is-installed-globally@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520"
@@ -16423,6 +17624,11 @@ is-interactive@^1.0.0:
resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e"
integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==
+is-interactive@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-2.0.0.tgz#40c57614593826da1100ade6059778d597f16e90"
+ integrity sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==
+
is-lambda@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5"
@@ -16524,6 +17730,11 @@ is-plain-obj@^1.0.0, is-plain-obj@^1.1.0:
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4=
+is-plain-obj@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0"
+ integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==
+
is-plain-object@^2.0.3, is-plain-object@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
@@ -16610,6 +17821,11 @@ is-stream@^1.0.1, is-stream@^1.1.0:
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
+is-stream@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac"
+ integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==
+
is-string@^1.0.5, is-string@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd"
@@ -16666,6 +17882,11 @@ is-unicode-supported@^0.1.0:
resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
+is-unicode-supported@^1.1.0, is-unicode-supported@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz#d824984b616c292a2e198207d4a609983842f714"
+ integrity sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==
+
is-url@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52"
@@ -16705,6 +17926,13 @@ is-wsl@^2.1.0, is-wsl@^2.1.1, is-wsl@^2.2.0:
dependencies:
is-docker "^2.0.0"
+is-wsl@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-3.1.0.tgz#e1c657e39c10090afcbedec61720f6b924c3cbd2"
+ integrity sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==
+ dependencies:
+ is-inside-container "^1.0.0"
+
is-yarn-global@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232"
@@ -17439,7 +18667,7 @@ js-yaml@3.14.0:
argparse "^1.0.7"
esprima "^4.0.0"
-js-yaml@3.x, js-yaml@^3.10.0, js-yaml@^3.13.1, js-yaml@^3.2.5, js-yaml@^3.2.7:
+js-yaml@3.x, js-yaml@^3.10.0, js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.2.5, js-yaml@^3.2.7:
version "3.14.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
@@ -17644,7 +18872,7 @@ json5@^1.0.1:
dependencies:
minimist "^1.2.0"
-json5@^2.2.2:
+json5@^2.1.3, json5@^2.2.2, json5@^2.2.3:
version "2.2.3"
resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
@@ -17965,7 +19193,7 @@ kleur@^3.0.3:
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
-kleur@^4.1.5:
+kleur@^4.0.3, kleur@^4.1.4, kleur@^4.1.5:
version "4.1.5"
resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780"
integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==
@@ -18329,6 +19557,16 @@ load-json-file@^4.0.0:
pify "^3.0.0"
strip-bom "^3.0.0"
+load-yaml-file@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/load-yaml-file/-/load-yaml-file-0.2.0.tgz#af854edaf2bea89346c07549122753c07372f64d"
+ integrity sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==
+ dependencies:
+ graceful-fs "^4.1.5"
+ js-yaml "^3.13.0"
+ pify "^4.0.1"
+ strip-bom "^3.0.0"
+
loader-runner@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
@@ -18658,6 +19896,14 @@ log-symbols@^4.0.0, log-symbols@^4.1.0:
chalk "^4.1.0"
is-unicode-supported "^0.1.0"
+log-symbols@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-5.1.0.tgz#a20e3b9a5f53fac6aeb8e2bb22c07cf2c8f16d93"
+ integrity sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==
+ dependencies:
+ chalk "^5.0.0"
+ is-unicode-supported "^1.1.0"
+
log4js@^4.0.1:
version "4.5.1"
resolved "https://registry.yarnpkg.com/log4js/-/log4js-4.5.1.tgz#e543625e97d9e6f3e6e7c9fc196dd6ab2cae30b5"
@@ -18714,6 +19960,11 @@ long@^4.0.0:
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==
+longest-streak@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4"
+ integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==
+
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
@@ -18865,6 +20116,13 @@ magic-string@^0.30.0:
dependencies:
"@jridgewell/sourcemap-codec" "^1.4.13"
+magic-string@^0.30.3, magic-string@^0.30.4:
+ version "0.30.4"
+ resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.4.tgz#c2c683265fc18dda49b56fc7318d33ca0332c98c"
+ integrity sha512-Q/TKtsC5BPm0kGqgBIF9oXAs/xEf2vRKiIB4wCRQTJOQIByZ1d+NnUOotvJOvNpi5RNIgVOMC3pOuaP1ZTDlVg==
+ dependencies:
+ "@jridgewell/sourcemap-codec" "^1.4.15"
+
magicast@0.2.8:
version "0.2.8"
resolved "https://registry.yarnpkg.com/magicast/-/magicast-0.2.8.tgz#02b298c65fbc5b7d1fce52ef779c59caf68cc9cf"
@@ -19050,6 +20308,11 @@ markdown-it@^8.3.1:
mdurl "^1.0.1"
uc.micro "^1.0.5"
+markdown-table@^3.0.0:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.3.tgz#e6331d30e493127e031dd385488b5bd326e4a6bd"
+ integrity sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==
+
marked@^1.1.1:
version "1.2.9"
resolved "https://registry.yarnpkg.com/marked/-/marked-1.2.9.tgz#53786f8b05d4c01a2a5a76b7d1ec9943d29d72dc"
@@ -19084,6 +20347,153 @@ md5.js@^1.3.4:
inherits "^2.0.1"
safe-buffer "^5.1.2"
+mdast-util-definitions@^5.0.0:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz#9910abb60ac5d7115d6819b57ae0bcef07a3f7a7"
+ integrity sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+ "@types/unist" "^2.0.0"
+ unist-util-visit "^4.0.0"
+
+mdast-util-definitions@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz#c1bb706e5e76bb93f9a09dd7af174002ae69ac24"
+ integrity sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ "@types/unist" "^3.0.0"
+ unist-util-visit "^5.0.0"
+
+mdast-util-find-and-replace@^2.0.0:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.2.tgz#cc2b774f7f3630da4bd592f61966fecade8b99b1"
+ integrity sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+ escape-string-regexp "^5.0.0"
+ unist-util-is "^5.0.0"
+ unist-util-visit-parents "^5.0.0"
+
+mdast-util-from-markdown@^1.0.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz#9421a5a247f10d31d2faed2a30df5ec89ceafcf0"
+ integrity sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+ "@types/unist" "^2.0.0"
+ decode-named-character-reference "^1.0.0"
+ mdast-util-to-string "^3.1.0"
+ micromark "^3.0.0"
+ micromark-util-decode-numeric-character-reference "^1.0.0"
+ micromark-util-decode-string "^1.0.0"
+ micromark-util-normalize-identifier "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.0"
+ unist-util-stringify-position "^3.0.0"
+ uvu "^0.5.0"
+
+mdast-util-gfm-autolink-literal@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.3.tgz#67a13abe813d7eba350453a5333ae1bc0ec05c06"
+ integrity sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+ ccount "^2.0.0"
+ mdast-util-find-and-replace "^2.0.0"
+ micromark-util-character "^1.0.0"
+
+mdast-util-gfm-footnote@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.2.tgz#ce5e49b639c44de68d5bf5399877a14d5020424e"
+ integrity sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+ mdast-util-to-markdown "^1.3.0"
+ micromark-util-normalize-identifier "^1.0.0"
+
+mdast-util-gfm-strikethrough@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.3.tgz#5470eb105b483f7746b8805b9b989342085795b7"
+ integrity sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+ mdast-util-to-markdown "^1.3.0"
+
+mdast-util-gfm-table@^1.0.0:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.7.tgz#3552153a146379f0f9c4c1101b071d70bbed1a46"
+ integrity sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+ markdown-table "^3.0.0"
+ mdast-util-from-markdown "^1.0.0"
+ mdast-util-to-markdown "^1.3.0"
+
+mdast-util-gfm-task-list-item@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.2.tgz#b280fcf3b7be6fd0cc012bbe67a59831eb34097b"
+ integrity sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+ mdast-util-to-markdown "^1.3.0"
+
+mdast-util-gfm@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/mdast-util-gfm/-/mdast-util-gfm-2.0.2.tgz#e92f4d8717d74bdba6de57ed21cc8b9552e2d0b6"
+ integrity sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==
+ dependencies:
+ mdast-util-from-markdown "^1.0.0"
+ mdast-util-gfm-autolink-literal "^1.0.0"
+ mdast-util-gfm-footnote "^1.0.0"
+ mdast-util-gfm-strikethrough "^1.0.0"
+ mdast-util-gfm-table "^1.0.0"
+ mdast-util-gfm-task-list-item "^1.0.0"
+ mdast-util-to-markdown "^1.0.0"
+
+mdast-util-phrasing@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz#c7c21d0d435d7fb90956038f02e8702781f95463"
+ integrity sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+ unist-util-is "^5.0.0"
+
+mdast-util-to-hast@^12.1.0:
+ version "12.3.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz#045d2825fb04374e59970f5b3f279b5700f6fb49"
+ integrity sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==
+ dependencies:
+ "@types/hast" "^2.0.0"
+ "@types/mdast" "^3.0.0"
+ mdast-util-definitions "^5.0.0"
+ micromark-util-sanitize-uri "^1.1.0"
+ trim-lines "^3.0.0"
+ unist-util-generated "^2.0.0"
+ unist-util-position "^4.0.0"
+ unist-util-visit "^4.0.0"
+
+mdast-util-to-markdown@^1.0.0, mdast-util-to-markdown@^1.3.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz#c13343cb3fc98621911d33b5cd42e7d0731171c6"
+ integrity sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+ "@types/unist" "^2.0.0"
+ longest-streak "^3.0.0"
+ mdast-util-phrasing "^3.0.0"
+ mdast-util-to-string "^3.0.0"
+ micromark-util-decode-string "^1.0.0"
+ unist-util-visit "^4.0.0"
+ zwitch "^2.0.0"
+
+mdast-util-to-string@^3.0.0, mdast-util-to-string@^3.1.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz#66f7bb6324756741c5f47a53557f0cbf16b6f789"
+ integrity sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+
mdn-data@2.0.14:
version "2.0.14"
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
@@ -19225,64 +20635,337 @@ methods@~1.1.2:
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
-micromatch@^3.1.10, micromatch@^3.1.4:
- version "3.1.10"
- resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
- integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==
+micromark-core-commonmark@^1.0.0, micromark-core-commonmark@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz#1386628df59946b2d39fb2edfd10f3e8e0a75bb8"
+ integrity sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==
+ dependencies:
+ decode-named-character-reference "^1.0.0"
+ micromark-factory-destination "^1.0.0"
+ micromark-factory-label "^1.0.0"
+ micromark-factory-space "^1.0.0"
+ micromark-factory-title "^1.0.0"
+ micromark-factory-whitespace "^1.0.0"
+ micromark-util-character "^1.0.0"
+ micromark-util-chunked "^1.0.0"
+ micromark-util-classify-character "^1.0.0"
+ micromark-util-html-tag-name "^1.0.0"
+ micromark-util-normalize-identifier "^1.0.0"
+ micromark-util-resolve-all "^1.0.0"
+ micromark-util-subtokenize "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.1"
+ uvu "^0.5.0"
+
+micromark-extension-gfm-autolink-literal@^1.0.0:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.5.tgz#5853f0e579bbd8ef9e39a7c0f0f27c5a063a66e7"
+ integrity sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==
dependencies:
- arr-diff "^4.0.0"
- array-unique "^0.3.2"
- braces "^2.3.1"
- define-property "^2.0.2"
- extend-shallow "^3.0.2"
- extglob "^2.0.4"
- fragment-cache "^0.2.1"
- kind-of "^6.0.2"
- nanomatch "^1.2.9"
- object.pick "^1.3.0"
- regex-not "^1.0.0"
- snapdragon "^0.8.1"
- to-regex "^3.0.2"
+ micromark-util-character "^1.0.0"
+ micromark-util-sanitize-uri "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.0"
-micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5:
- version "4.0.5"
- resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
- integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
+micromark-extension-gfm-footnote@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.1.2.tgz#05e13034d68f95ca53c99679040bc88a6f92fe2e"
+ integrity sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==
+ dependencies:
+ micromark-core-commonmark "^1.0.0"
+ micromark-factory-space "^1.0.0"
+ micromark-util-character "^1.0.0"
+ micromark-util-normalize-identifier "^1.0.0"
+ micromark-util-sanitize-uri "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.0"
+ uvu "^0.5.0"
+
+micromark-extension-gfm-strikethrough@^1.0.0:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.7.tgz#c8212c9a616fa3bf47cb5c711da77f4fdc2f80af"
+ integrity sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==
dependencies:
- braces "^3.0.2"
- picomatch "^2.3.1"
+ micromark-util-chunked "^1.0.0"
+ micromark-util-classify-character "^1.0.0"
+ micromark-util-resolve-all "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.0"
+ uvu "^0.5.0"
-miller-rabin@^4.0.0:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
- integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==
+micromark-extension-gfm-table@^1.0.0:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.7.tgz#dcb46074b0c6254c3fc9cc1f6f5002c162968008"
+ integrity sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==
dependencies:
- bn.js "^4.0.0"
- brorand "^1.0.1"
+ micromark-factory-space "^1.0.0"
+ micromark-util-character "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.0"
+ uvu "^0.5.0"
-mime-db@1.52.0, "mime-db@>= 1.43.0 < 2":
- version "1.52.0"
- resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
- integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
+micromark-extension-gfm-tagfilter@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.2.tgz#aa7c4dd92dabbcb80f313ebaaa8eb3dac05f13a7"
+ integrity sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==
+ dependencies:
+ micromark-util-types "^1.0.0"
-mime-types@^2.1.12, mime-types@^2.1.18, mime-types@^2.1.26, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34:
- version "2.1.35"
- resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
- integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
+micromark-extension-gfm-task-list-item@^1.0.0:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.5.tgz#b52ce498dc4c69b6a9975abafc18f275b9dde9f4"
+ integrity sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==
dependencies:
- mime-db "1.52.0"
+ micromark-factory-space "^1.0.0"
+ micromark-util-character "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.0"
+ uvu "^0.5.0"
-mime@1.6.0, mime@^1.4.1:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
- integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
+micromark-extension-gfm@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/micromark-extension-gfm/-/micromark-extension-gfm-2.0.3.tgz#e517e8579949a5024a493e49204e884aa74f5acf"
+ integrity sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==
+ dependencies:
+ micromark-extension-gfm-autolink-literal "^1.0.0"
+ micromark-extension-gfm-footnote "^1.0.0"
+ micromark-extension-gfm-strikethrough "^1.0.0"
+ micromark-extension-gfm-table "^1.0.0"
+ micromark-extension-gfm-tagfilter "^1.0.0"
+ micromark-extension-gfm-task-list-item "^1.0.0"
+ micromark-util-combine-extensions "^1.0.0"
+ micromark-util-types "^1.0.0"
+
+micromark-factory-destination@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz#eb815957d83e6d44479b3df640f010edad667b9f"
+ integrity sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==
+ dependencies:
+ micromark-util-character "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.0"
-mime@^2.3.1, mime@^2.4.4, mime@^2.5.2:
- version "2.6.0"
- resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367"
- integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==
+micromark-factory-label@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz#cc95d5478269085cfa2a7282b3de26eb2e2dec68"
+ integrity sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==
+ dependencies:
+ micromark-util-character "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.0"
+ uvu "^0.5.0"
-mime@^3.0.0:
+micromark-factory-space@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz#c8f40b0640a0150751d3345ed885a080b0d15faf"
+ integrity sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==
+ dependencies:
+ micromark-util-character "^1.0.0"
+ micromark-util-types "^1.0.0"
+
+micromark-factory-title@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz#dd0fe951d7a0ac71bdc5ee13e5d1465ad7f50ea1"
+ integrity sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==
+ dependencies:
+ micromark-factory-space "^1.0.0"
+ micromark-util-character "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.0"
+
+micromark-factory-whitespace@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz#798fb7489f4c8abafa7ca77eed6b5745853c9705"
+ integrity sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==
+ dependencies:
+ micromark-factory-space "^1.0.0"
+ micromark-util-character "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.0"
+
+micromark-util-character@^1.0.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-1.2.0.tgz#4fedaa3646db249bc58caeb000eb3549a8ca5dcc"
+ integrity sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==
+ dependencies:
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.0"
+
+micromark-util-chunked@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz#37a24d33333c8c69a74ba12a14651fd9ea8a368b"
+ integrity sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==
+ dependencies:
+ micromark-util-symbol "^1.0.0"
+
+micromark-util-classify-character@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz#6a7f8c8838e8a120c8e3c4f2ae97a2bff9190e9d"
+ integrity sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==
+ dependencies:
+ micromark-util-character "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.0"
+
+micromark-util-combine-extensions@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz#192e2b3d6567660a85f735e54d8ea6e3952dbe84"
+ integrity sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==
+ dependencies:
+ micromark-util-chunked "^1.0.0"
+ micromark-util-types "^1.0.0"
+
+micromark-util-decode-numeric-character-reference@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz#b1e6e17009b1f20bc652a521309c5f22c85eb1c6"
+ integrity sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==
+ dependencies:
+ micromark-util-symbol "^1.0.0"
+
+micromark-util-decode-string@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz#dc12b078cba7a3ff690d0203f95b5d5537f2809c"
+ integrity sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==
+ dependencies:
+ decode-named-character-reference "^1.0.0"
+ micromark-util-character "^1.0.0"
+ micromark-util-decode-numeric-character-reference "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+
+micromark-util-encode@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz#92e4f565fd4ccb19e0dcae1afab9a173bbeb19a5"
+ integrity sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==
+
+micromark-util-html-tag-name@^1.0.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz#48fd7a25826f29d2f71479d3b4e83e94829b3588"
+ integrity sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==
+
+micromark-util-normalize-identifier@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz#7a73f824eb9f10d442b4d7f120fecb9b38ebf8b7"
+ integrity sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==
+ dependencies:
+ micromark-util-symbol "^1.0.0"
+
+micromark-util-resolve-all@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz#4652a591ee8c8fa06714c9b54cd6c8e693671188"
+ integrity sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==
+ dependencies:
+ micromark-util-types "^1.0.0"
+
+micromark-util-sanitize-uri@^1.0.0, micromark-util-sanitize-uri@^1.1.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz#613f738e4400c6eedbc53590c67b197e30d7f90d"
+ integrity sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==
+ dependencies:
+ micromark-util-character "^1.0.0"
+ micromark-util-encode "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+
+micromark-util-subtokenize@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz#941c74f93a93eaf687b9054aeb94642b0e92edb1"
+ integrity sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==
+ dependencies:
+ micromark-util-chunked "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.0"
+ uvu "^0.5.0"
+
+micromark-util-symbol@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz#813cd17837bdb912d069a12ebe3a44b6f7063142"
+ integrity sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==
+
+micromark-util-types@^1.0.0, micromark-util-types@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-1.1.0.tgz#e6676a8cae0bb86a2171c498167971886cb7e283"
+ integrity sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==
+
+micromark@^3.0.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/micromark/-/micromark-3.2.0.tgz#1af9fef3f995ea1ea4ac9c7e2f19c48fd5c006e9"
+ integrity sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==
+ dependencies:
+ "@types/debug" "^4.0.0"
+ debug "^4.0.0"
+ decode-named-character-reference "^1.0.0"
+ micromark-core-commonmark "^1.0.1"
+ micromark-factory-space "^1.0.0"
+ micromark-util-character "^1.0.0"
+ micromark-util-chunked "^1.0.0"
+ micromark-util-combine-extensions "^1.0.0"
+ micromark-util-decode-numeric-character-reference "^1.0.0"
+ micromark-util-encode "^1.0.0"
+ micromark-util-normalize-identifier "^1.0.0"
+ micromark-util-resolve-all "^1.0.0"
+ micromark-util-sanitize-uri "^1.0.0"
+ micromark-util-subtokenize "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.1"
+ uvu "^0.5.0"
+
+micromatch@^3.1.10, micromatch@^3.1.4:
+ version "3.1.10"
+ resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
+ integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==
+ dependencies:
+ arr-diff "^4.0.0"
+ array-unique "^0.3.2"
+ braces "^2.3.1"
+ define-property "^2.0.2"
+ extend-shallow "^3.0.2"
+ extglob "^2.0.4"
+ fragment-cache "^0.2.1"
+ kind-of "^6.0.2"
+ nanomatch "^1.2.9"
+ object.pick "^1.3.0"
+ regex-not "^1.0.0"
+ snapdragon "^0.8.1"
+ to-regex "^3.0.2"
+
+micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5:
+ version "4.0.5"
+ resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
+ integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
+ dependencies:
+ braces "^3.0.2"
+ picomatch "^2.3.1"
+
+miller-rabin@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
+ integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==
+ dependencies:
+ bn.js "^4.0.0"
+ brorand "^1.0.1"
+
+mime-db@1.52.0, "mime-db@>= 1.43.0 < 2":
+ version "1.52.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
+ integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
+
+mime-types@^2.1.12, mime-types@^2.1.18, mime-types@^2.1.26, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34:
+ version "2.1.35"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
+ integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
+ dependencies:
+ mime-db "1.52.0"
+
+mime@1.6.0, mime@^1.4.1:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
+ integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
+
+mime@^2.3.1, mime@^2.4.4, mime@^2.5.2:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367"
+ integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==
+
+mime@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7"
integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==
@@ -19307,11 +20990,21 @@ mimic-fn@^3.1.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-3.1.0.tgz#65755145bbf3e36954b949c16450427451d5ca74"
integrity sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==
+mimic-fn@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc"
+ integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==
+
mimic-response@^1.0.0, mimic-response@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
+mimic-response@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
+ integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
+
min-indent@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
@@ -19431,6 +21124,11 @@ minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18"
integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==
+minimist@^1.2.3:
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
+ integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
+
minipass-collect@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617"
@@ -19589,6 +21287,11 @@ mixin-deep@^1.2.0:
for-in "^1.0.2"
is-extendable "^1.0.1"
+mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
+ version "0.5.3"
+ resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
+ integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
+
mkdirp@0.5.4:
version "0.5.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.4.tgz#fd01504a6797ec5c9be81ff43d204961ed64a512"
@@ -19884,6 +21587,11 @@ nanomatch@^1.2.9:
snapdragon "^0.8.1"
to-regex "^3.0.1"
+napi-build-utils@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806"
+ integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==
+
native-request@^1.0.5:
version "1.1.0"
resolved "https://registry.yarnpkg.com/native-request/-/native-request-1.1.0.tgz#acdb30fe2eefa3e1bc8c54b3a6852e9c5c0d3cb0"
@@ -20103,6 +21811,13 @@ nise@^5.1.4:
just-extend "^4.0.2"
path-to-regexp "^1.7.0"
+nlcst-to-string@^3.0.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/nlcst-to-string/-/nlcst-to-string-3.1.1.tgz#83b90f2e1ee2081e14701317efc26d3bbadc806e"
+ integrity sha512-63mVyqaqt0cmn2VcI2aH6kxe1rLAmSROqHMA0i4qqg1tidkfExgpb0FGMikMCn86mw5dFtBtEANfmSSK7TjNHw==
+ dependencies:
+ "@types/nlcst" "^1.0.0"
+
no-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d"
@@ -20121,6 +21836,13 @@ nock@^13.0.4, nock@^13.0.5, nock@^13.1.0:
lodash.set "^4.3.2"
propagate "^2.0.0"
+node-abi@^3.3.0:
+ version "3.47.0"
+ resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.47.0.tgz#6cbfa2916805ae25c2b7156ca640131632eb05e8"
+ integrity sha512-2s6B2CWZM//kPgwnuI0KrYwNjfdByE25zvAaEpq9IH4zcNsarH8Ihu/UuX6XMPEogDAxkuUFeZn60pXNHAqn3A==
+ dependencies:
+ semver "^7.3.5"
+
node-abort-controller@^3.0.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548"
@@ -20131,6 +21853,11 @@ node-addon-api@^3.0.0, node-addon-api@^3.2.1:
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161"
integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==
+node-addon-api@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-6.1.0.tgz#ac8470034e58e67d0c6f1204a18ae6995d9c0d76"
+ integrity sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==
+
node-environment-flags@1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.5.tgz#fa930275f5bf5dae188d6192b24b4c8bbac3d76a"
@@ -20278,6 +22005,11 @@ node-releases@^1.1.69:
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.77.tgz#50b0cfede855dd374e7585bf228ff34e57c1c32e"
integrity sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ==
+node-releases@^2.0.13:
+ version "2.0.13"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d"
+ integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==
+
node-releases@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503"
@@ -20712,6 +22444,13 @@ npm-run-path@^4.0.0, npm-run-path@^4.0.1:
dependencies:
path-key "^3.0.0"
+npm-run-path@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.1.0.tgz#bc62f7f3f6952d9894bd08944ba011a6ee7b7e00"
+ integrity sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==
+ dependencies:
+ path-key "^4.0.0"
+
npmlog@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
@@ -20989,6 +22728,13 @@ onetime@^5.1.0, onetime@^5.1.2:
dependencies:
mimic-fn "^2.1.0"
+onetime@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4"
+ integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==
+ dependencies:
+ mimic-fn "^4.0.0"
+
open@7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/open/-/open-7.2.0.tgz#212959bd7b0ce2e8e3676adc76e3cf2f0a2498b4"
@@ -21020,6 +22766,15 @@ opener@^1.5.2:
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598"
integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==
+opentelemetry-instrumentation-fetch-node@1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/opentelemetry-instrumentation-fetch-node/-/opentelemetry-instrumentation-fetch-node-1.1.0.tgz#f51d79862390f3a694fa91c35c4383e037a04c11"
+ integrity sha512-mSEpyRfwv6t1L+VvqTw5rCzNr3bVTsGE4/dcZruhFWivXFKl8pqm6W0LWPxHrEvwufw1eK9VmUgalfY0jjMl8Q==
+ dependencies:
+ "@opentelemetry/api" "^1.6.0"
+ "@opentelemetry/instrumentation" "^0.43.0"
+ "@opentelemetry/semantic-conventions" "^1.17.0"
+
opn@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc"
@@ -21107,6 +22862,21 @@ ora@^3.4.0:
strip-ansi "^5.2.0"
wcwidth "^1.0.1"
+ora@^7.0.1:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/ora/-/ora-7.0.1.tgz#cdd530ecd865fe39e451a0e7697865669cb11930"
+ integrity sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==
+ dependencies:
+ chalk "^5.3.0"
+ cli-cursor "^4.0.0"
+ cli-spinners "^2.9.0"
+ is-interactive "^2.0.0"
+ is-unicode-supported "^1.3.0"
+ log-symbols "^5.1.0"
+ stdin-discarder "^0.1.0"
+ string-width "^6.1.0"
+ strip-ansi "^7.1.0"
+
original@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f"
@@ -21509,6 +23279,15 @@ parse-json@^5.0.0, parse-json@^5.2.0:
json-parse-even-better-errors "^2.3.0"
lines-and-columns "^1.1.6"
+parse-latin@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/parse-latin/-/parse-latin-5.0.1.tgz#f3b4fac54d06f6a0501cf8b8ecfafa4cbb4f2f47"
+ integrity sha512-b/K8ExXaWC9t34kKeDV8kGXBkXZ1HCSAZRYE7HR14eA1GlXX5L8iWhs8USJNhQU9q5ci413jCKF0gOyovvyRBg==
+ dependencies:
+ nlcst-to-string "^3.0.0"
+ unist-util-modify-children "^3.0.0"
+ unist-util-visit-children "^2.0.0"
+
parse-ms@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-2.1.0.tgz#348565a753d4391fa524029956b172cb7753097d"
@@ -21565,7 +23344,7 @@ parse5-sax-parser@^6.0.1:
dependencies:
parse5 "^6.0.1"
-parse5@6.0.1, parse5@^6.0.1:
+parse5@6.0.1, parse5@^6.0.0, parse5@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
@@ -21645,6 +23424,11 @@ path-key@^3.0.0, path-key@^3.1.0:
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
+path-key@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18"
+ integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==
+
path-parse@^1.0.6, path-parse@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
@@ -21703,6 +23487,11 @@ path-to-regexp@^1.5.3, path-to-regexp@^1.7.0:
dependencies:
isarray "0.0.1"
+path-to-regexp@^6.2.1:
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.1.tgz#d54934d6798eb9e5ef14e7af7962c945906918e5"
+ integrity sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==
+
path-type@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
@@ -23001,6 +24790,15 @@ postcss@^8.1.10, postcss@^8.1.7, postcss@^8.2.15, postcss@^8.2.4, postcss@^8.3.5
picocolors "^1.0.0"
source-map-js "^1.0.2"
+postcss@^8.4.27:
+ version "8.4.31"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
+ integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
+ dependencies:
+ nanoid "^3.3.6"
+ picocolors "^1.0.0"
+ source-map-js "^1.0.2"
+
postgres-array@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e"
@@ -23050,6 +24848,24 @@ postgres-range@^1.1.1:
resolved "https://registry.yarnpkg.com/postgres-range/-/postgres-range-1.1.3.tgz#9ccd7b01ca2789eb3c2e0888b3184225fa859f76"
integrity sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g==
+prebuild-install@^7.1.1:
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45"
+ integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==
+ dependencies:
+ detect-libc "^2.0.0"
+ expand-template "^2.0.3"
+ github-from-package "0.0.0"
+ minimist "^1.2.3"
+ mkdirp-classic "^0.5.3"
+ napi-build-utils "^1.0.1"
+ node-abi "^3.3.0"
+ pump "^3.0.0"
+ rc "^1.2.7"
+ simple-get "^4.0.0"
+ tar-fs "^2.0.0"
+ tunnel-agent "^0.6.0"
+
precinct@^7.0.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/precinct/-/precinct-7.1.0.tgz#a0311e0b59029647eaf57c2d30b8efa9c85d129a"
@@ -23069,6 +24885,16 @@ precinct@^7.0.0:
module-definition "^3.3.1"
node-source-walk "^4.2.0"
+preferred-pm@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/preferred-pm/-/preferred-pm-3.1.2.tgz#aedb70550734a574dffcbf2ce82642bd1753bdd6"
+ integrity sha512-nk7dKrcW8hfCZ4H6klWcdRknBOXWzNQByJ0oJyX97BOupsYD+FzLS4hflgEu/uPUEHZCuRfMxzCBsuWd7OzT8Q==
+ dependencies:
+ find-up "^5.0.0"
+ find-yarn-workspace-root2 "1.2.16"
+ path-exists "^4.0.0"
+ which-pm "2.0.0"
+
prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@@ -23151,11 +24977,25 @@ printf@^0.6.1:
resolved "https://registry.yarnpkg.com/printf/-/printf-0.6.1.tgz#b9afa3d3b55b7f2e8b1715272479fc756ed88650"
integrity sha512-is0ctgGdPJ5951KulgfzvHGwJtZ5ck8l042vRkV6jrkpBzTmb/lueTqguWHy2JfVA+RY6gFVlaZgUS0j7S/dsw==
+prismjs@^1.29.0:
+ version "1.29.0"
+ resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12"
+ integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==
+
private@^0.1.6, private@^0.1.8:
version "0.1.8"
resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==
+probe-image-size@^7.2.3:
+ version "7.2.3"
+ resolved "https://registry.yarnpkg.com/probe-image-size/-/probe-image-size-7.2.3.tgz#d49c64be540ec8edea538f6f585f65a9b3ab4309"
+ integrity sha512-HubhG4Rb2UH8YtV4ba0Vp5bQ7L78RTONYu/ujmCu5nBI8wGv24s4E9xSKBi0N1MowRpxk76pFCpJtW0KPzOK0w==
+ dependencies:
+ lodash.merge "^4.6.2"
+ needle "^2.5.2"
+ stream-parser "~0.3.1"
+
proc-log@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-2.0.1.tgz#8f3f69a1f608de27878f91f5c688b225391cb685"
@@ -23241,6 +25081,14 @@ prompts@^2.0.1:
kleur "^3.0.3"
sisteransi "^1.0.5"
+prompts@^2.4.2:
+ version "2.4.2"
+ resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069"
+ integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==
+ dependencies:
+ kleur "^3.0.3"
+ sisteransi "^1.0.5"
+
promzard@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/promzard/-/promzard-1.0.0.tgz#3246f8e6c9895a77c0549cefb65828ac0f6c006b"
@@ -23280,6 +25128,11 @@ proper-lockfile@^4.1.2:
retry "^0.12.0"
signal-exit "^3.0.2"
+property-information@^6.0.0:
+ version "6.3.0"
+ resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.3.0.tgz#ba4a06ec6b4e1e90577df9931286953cdf4282c3"
+ integrity sha512-gVNZ74nqhRMiIUYWGQdosYetaKc83x8oT41a0LlV3AAFCAZwCpg4vmGkq8t34+cUhp3cnM4XDiU/7xlgK7HGrg==
+
protobufjs@^6.10.2, protobufjs@^6.8.6:
version "6.11.4"
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.4.tgz#29a412c38bf70d89e537b6d02d904a6f448173aa"
@@ -23469,6 +25322,11 @@ queue-microtask@^1.2.2:
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
+queue-tick@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/queue-tick/-/queue-tick-1.0.1.tgz#f6f07ac82c1fd60f82e098b417a80e52f1f4c142"
+ integrity sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==
+
quick-lru@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
@@ -24123,6 +25981,44 @@ regjsparser@^0.9.1:
dependencies:
jsesc "~0.5.0"
+rehype-parse@^8.0.0:
+ version "8.0.5"
+ resolved "https://registry.yarnpkg.com/rehype-parse/-/rehype-parse-8.0.5.tgz#ccffc21e08e288c7846614f8dc1dc23d603a4a80"
+ integrity sha512-Ds3RglaY/+clEX2U2mHflt7NlMA72KspZ0JLUJgBBLpRddBcEw3H8uYZQliQriku22NZpYMfjDdSgHcjxue24A==
+ dependencies:
+ "@types/hast" "^2.0.0"
+ hast-util-from-parse5 "^7.0.0"
+ parse5 "^6.0.0"
+ unified "^10.0.0"
+
+rehype-raw@^6.1.1:
+ version "6.1.1"
+ resolved "https://registry.yarnpkg.com/rehype-raw/-/rehype-raw-6.1.1.tgz#81bbef3793bd7abacc6bf8335879d1b6c868c9d4"
+ integrity sha512-d6AKtisSRtDRX4aSPsJGTfnzrX2ZkHQLE5kiUuGOeEoLpbEulFF4hj0mLPbsa+7vmguDKOVVEQdHKDSwoaIDsQ==
+ dependencies:
+ "@types/hast" "^2.0.0"
+ hast-util-raw "^7.2.0"
+ unified "^10.0.0"
+
+rehype-stringify@^9.0.0, rehype-stringify@^9.0.4:
+ version "9.0.4"
+ resolved "https://registry.yarnpkg.com/rehype-stringify/-/rehype-stringify-9.0.4.tgz#31dbb9de6f5034c6964760a1b1083218059c4343"
+ integrity sha512-Uk5xu1YKdqobe5XpSskwPvo1XeHUUucWEQSl8hTrXt5selvca1e8K1EZ37E6YoZ4BT8BCqCdVfQW7OfHfthtVQ==
+ dependencies:
+ "@types/hast" "^2.0.0"
+ hast-util-to-html "^8.0.0"
+ unified "^10.0.0"
+
+rehype@^12.0.1:
+ version "12.0.1"
+ resolved "https://registry.yarnpkg.com/rehype/-/rehype-12.0.1.tgz#68a317662576dcaa2565a3952e149d6900096bf6"
+ integrity sha512-ey6kAqwLM3X6QnMDILJthGvG1m1ULROS9NT4uG9IDCuv08SFyLlreSuvOa//DgEvbXx62DS6elGVqusWhRUbgw==
+ dependencies:
+ "@types/hast" "^2.0.0"
+ rehype-parse "^8.0.0"
+ rehype-stringify "^9.0.0"
+ unified "^10.0.0"
+
relateurl@^0.2.7:
version "0.2.7"
resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
@@ -24139,6 +26035,44 @@ remap-istanbul@^0.13.0:
source-map "0.6.1"
through2 "3.0.0"
+remark-gfm@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/remark-gfm/-/remark-gfm-3.0.1.tgz#0b180f095e3036545e9dddac0e8df3fa5cfee54f"
+ integrity sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+ mdast-util-gfm "^2.0.0"
+ micromark-extension-gfm "^2.0.0"
+ unified "^10.0.0"
+
+remark-parse@^10.0.2:
+ version "10.0.2"
+ resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-10.0.2.tgz#ca241fde8751c2158933f031a4e3efbaeb8bc262"
+ integrity sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+ mdast-util-from-markdown "^1.0.0"
+ unified "^10.0.0"
+
+remark-rehype@^10.1.0:
+ version "10.1.0"
+ resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-10.1.0.tgz#32dc99d2034c27ecaf2e0150d22a6dcccd9a6279"
+ integrity sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==
+ dependencies:
+ "@types/hast" "^2.0.0"
+ "@types/mdast" "^3.0.0"
+ mdast-util-to-hast "^12.1.0"
+ unified "^10.0.0"
+
+remark-smartypants@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/remark-smartypants/-/remark-smartypants-2.0.0.tgz#836cff43ec139b2e5ec9e488d80596ed677d1cb2"
+ integrity sha512-Rc0VDmr/yhnMQIz8n2ACYXlfw/P/XZev884QU1I5u+5DgJls32o97Vc1RbK3pfumLsJomS2yy8eT4Fxj/2MDVA==
+ dependencies:
+ retext "^8.1.0"
+ retext-smartypants "^5.1.0"
+ unist-util-visit "^4.1.0"
+
remote-git-tags@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/remote-git-tags/-/remote-git-tags-3.0.0.tgz#424f8ec2cdea00bb5af1784a49190f25e16983c3"
@@ -24448,6 +26382,15 @@ resolve@^1.22.2:
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
+resolve@^1.22.4:
+ version "1.22.6"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.6.tgz#dd209739eca3aef739c626fea1b4f3c506195362"
+ integrity sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==
+ dependencies:
+ is-core-module "^2.13.0"
+ path-parse "^1.0.7"
+ supports-preserve-symlinks-flag "^1.0.0"
+
resolve@^2.0.0-next.3:
version "2.0.0-next.3"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.3.tgz#d41016293d4a8586a39ca5d9b5f15cbea1f55e46"
@@ -24479,11 +26422,58 @@ restore-cursor@^3.1.0:
onetime "^5.1.0"
signal-exit "^3.0.2"
+restore-cursor@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-4.0.0.tgz#519560a4318975096def6e609d44100edaa4ccb9"
+ integrity sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==
+ dependencies:
+ onetime "^5.1.0"
+ signal-exit "^3.0.2"
+
ret@~0.1.10:
version "0.1.15"
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
+retext-latin@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/retext-latin/-/retext-latin-3.1.0.tgz#72b0176af2c69a373fd0d37eadd3924418bb3a89"
+ integrity sha512-5MrD1tuebzO8ppsja5eEu+ZbBeUNCjoEarn70tkXOS7Bdsdf6tNahsv2bY0Z8VooFF6cw7/6S+d3yI/TMlMVVQ==
+ dependencies:
+ "@types/nlcst" "^1.0.0"
+ parse-latin "^5.0.0"
+ unherit "^3.0.0"
+ unified "^10.0.0"
+
+retext-smartypants@^5.1.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/retext-smartypants/-/retext-smartypants-5.2.0.tgz#da9cb79cc60f36aa33a20a462dfc663bec0068b4"
+ integrity sha512-Do8oM+SsjrbzT2UNIKgheP0hgUQTDDQYyZaIY3kfq0pdFzoPk+ZClYJ+OERNXveog4xf1pZL4PfRxNoVL7a/jw==
+ dependencies:
+ "@types/nlcst" "^1.0.0"
+ nlcst-to-string "^3.0.0"
+ unified "^10.0.0"
+ unist-util-visit "^4.0.0"
+
+retext-stringify@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/retext-stringify/-/retext-stringify-3.1.0.tgz#46ed45e077bfc4a8334977f6c2d6611e1d36263a"
+ integrity sha512-767TLOaoXFXyOnjx/EggXlb37ZD2u4P1n0GJqVdpipqACsQP+20W+BNpMYrlJkq7hxffnFk+jc6mAK9qrbuB8w==
+ dependencies:
+ "@types/nlcst" "^1.0.0"
+ nlcst-to-string "^3.0.0"
+ unified "^10.0.0"
+
+retext@^8.1.0:
+ version "8.1.0"
+ resolved "https://registry.yarnpkg.com/retext/-/retext-8.1.0.tgz#c43437fb84cd46285ad240a9279142e239bada8d"
+ integrity sha512-N9/Kq7YTn6ZpzfiGW45WfEGJqFf1IM1q8OsRa1CGzIebCJBNCANDRmOrholiDRGKo/We7ofKR4SEvcGAWEMD3Q==
+ dependencies:
+ "@types/nlcst" "^1.0.0"
+ retext-latin "^3.0.0"
+ retext-stringify "^3.0.0"
+ unified "^10.0.0"
+
retry-request@^4.0.0, retry-request@^4.1.1:
version "4.1.3"
resolved "https://registry.yarnpkg.com/retry-request/-/retry-request-4.1.3.tgz#d5f74daf261372cff58d08b0a1979b4d7cab0fde"
@@ -24602,6 +26592,15 @@ rollup-plugin-cleanup@3.2.1:
js-cleanup "^1.2.0"
rollup-pluginutils "^2.8.2"
+rollup-plugin-dts@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/rollup-plugin-dts/-/rollup-plugin-dts-6.1.0.tgz#56e9c5548dac717213c6a4aa9df523faf04f75ae"
+ integrity sha512-ijSCPICkRMDKDLBK9torss07+8dl9UpY9z1N/zTeA1cIqdzMlpkV3MOOC7zukyvQfDyxa1s3Dl2+DeiP/G6DOw==
+ dependencies:
+ magic-string "^0.30.4"
+ optionalDependencies:
+ "@babel/code-frame" "^7.22.13"
+
rollup-plugin-license@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/rollup-plugin-license/-/rollup-plugin-license-2.6.1.tgz#20f15cc37950f362f8eefdc6e3a2e659d0cad9eb"
@@ -24670,6 +26669,13 @@ rollup@^3.10.0, rollup@^3.20.2, rollup@^3.7.0:
optionalDependencies:
fsevents "~2.3.2"
+rollup@^3.27.1:
+ version "3.29.4"
+ resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.29.4.tgz#4d70c0f9834146df8705bfb69a9a19c9e1109981"
+ integrity sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==
+ optionalDependencies:
+ fsevents "~2.3.2"
+
rsvp@^3.0.14, rsvp@^3.0.17, rsvp@^3.0.18, rsvp@^3.0.21, rsvp@^3.0.6, rsvp@^3.1.0:
version "3.6.2"
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a"
@@ -24739,7 +26745,7 @@ rxjs@^7.5.5:
dependencies:
tslib "^2.1.0"
-sade@^1.8.1:
+sade@^1.7.3, sade@^1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701"
integrity sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==
@@ -24958,6 +26964,14 @@ schema-utils@^4.0.0:
ajv-formats "^2.1.1"
ajv-keywords "^5.0.0"
+section-matter@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/section-matter/-/section-matter-1.0.0.tgz#e9041953506780ec01d59f292a19c7b850b84167"
+ integrity sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==
+ dependencies:
+ extend-shallow "^2.0.1"
+ kind-of "^6.0.0"
+
select-hose@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
@@ -25020,14 +27034,14 @@ semver@7.5.3:
dependencies:
lru-cache "^6.0.0"
-semver@7.x, semver@^7.0.0, semver@^7.1.1, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.1, semver@^7.5.2, semver@^7.5.3:
+semver@7.x, semver@^7.0.0, semver@^7.1.1, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.1, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4:
version "7.5.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
dependencies:
lru-cache "^6.0.0"
-semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
+semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0, semver@^6.3.1:
version "6.3.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
@@ -25088,6 +27102,11 @@ serve-static@1.15.0:
parseurl "~1.3.3"
send "0.18.0"
+server-destroy@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/server-destroy/-/server-destroy-1.0.1.tgz#f13bf928e42b9c3e79383e61cc3998b5d14e6cdd"
+ integrity sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==
+
set-blocking@^2.0.0, set-blocking@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
@@ -25143,6 +27162,20 @@ shallow-clone@^3.0.0:
dependencies:
kind-of "^6.0.2"
+sharp@^0.32.5:
+ version "0.32.6"
+ resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.32.6.tgz#6ad30c0b7cd910df65d5f355f774aa4fce45732a"
+ integrity sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==
+ dependencies:
+ color "^4.2.3"
+ detect-libc "^2.0.2"
+ node-addon-api "^6.1.0"
+ prebuild-install "^7.1.1"
+ semver "^7.5.4"
+ simple-get "^4.0.1"
+ tar-fs "^3.0.4"
+ tunnel-agent "^0.6.0"
+
shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
@@ -25186,6 +27219,16 @@ shellwords@^0.1.1:
resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==
+shiki@^0.14.3:
+ version "0.14.4"
+ resolved "https://registry.yarnpkg.com/shiki/-/shiki-0.14.4.tgz#2454969b466a5f75067d0f2fa0d7426d32881b20"
+ integrity sha512-IXCRip2IQzKwxArNNq1S+On4KPML3Yyn8Zzs/xRgcgOWIr8ntIK3IKzjFPfjy/7kt9ZMjc+FItfqHRBg8b6tNQ==
+ dependencies:
+ ansi-sequence-parser "^1.1.0"
+ jsonc-parser "^3.2.0"
+ vscode-oniguruma "^1.7.0"
+ vscode-textmate "^8.0.0"
+
shimmer@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337"
@@ -25215,6 +27258,11 @@ signal-exit@^4.0.1:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.0.2.tgz#ff55bb1d9ff2114c13b400688fa544ac63c36967"
integrity sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==
+signal-exit@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
+ integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
+
sigstore@^1.3.0, sigstore@^1.4.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/sigstore/-/sigstore-1.6.0.tgz#887a4007c6ee83f3ef3fd844be1a0840e849c301"
@@ -25232,6 +27280,20 @@ silent-error@^1.0.0, silent-error@^1.0.1, silent-error@^1.1.0, silent-error@^1.1
dependencies:
debug "^2.2.0"
+simple-concat@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
+ integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
+
+simple-get@^4.0.0, simple-get@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543"
+ integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==
+ dependencies:
+ decompress-response "^6.0.0"
+ once "^1.3.1"
+ simple-concat "^1.0.0"
+
simple-git@^3.16.0:
version "3.16.1"
resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-3.16.1.tgz#b67f18cbd3c68bbc4b9177ed49256afe51f12d47"
@@ -25648,11 +27710,16 @@ source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, sourc
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
-source-map@0.7.3, source-map@^0.7.3, source-map@~0.7.2:
+source-map@0.7.3, source-map@^0.7.3:
version "0.7.3"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
+source-map@0.7.4, source-map@~0.7.2:
+ version "0.7.4"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656"
+ integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==
+
source-map@0.8.0-beta.0:
version "0.8.0-beta.0"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.8.0-beta.0.tgz#d4c1bb42c3f7ee925f005927ba10709e0d1d1f11"
@@ -25694,6 +27761,11 @@ sourcemap-validator@^1.1.0:
lodash.template "^4.5.0"
source-map "~0.1.x"
+space-separated-tokens@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz#1ecd9d2350a3844572c3f4a312bceb018348859f"
+ integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==
+
sparse-bitfield@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11"
@@ -25947,6 +28019,13 @@ std-env@^3.3.1:
resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.3.2.tgz#af27343b001616015534292178327b202b9ee955"
integrity sha512-uUZI65yrV2Qva5gqE0+A7uVAvO40iPo6jGhs7s8keRfHCmtg+uB2X6EiLGCI9IgL1J17xGhvoOqSz79lzICPTA==
+stdin-discarder@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/stdin-discarder/-/stdin-discarder-0.1.0.tgz#22b3e400393a8e28ebf53f9958f3880622efde21"
+ integrity sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==
+ dependencies:
+ bl "^5.0.0"
+
stream-browserify@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f"
@@ -26006,7 +28085,7 @@ stream-http@^2.7.2:
to-arraybuffer "^1.0.0"
xtend "^4.0.0"
-stream-parser@^0.3.1:
+stream-parser@^0.3.1, stream-parser@~0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/stream-parser/-/stream-parser-0.3.1.tgz#1618548694420021a1182ff0af1911c129761773"
integrity sha1-FhhUhpRCACGhGC/wrxkRwSl2F3M=
@@ -26048,6 +28127,14 @@ streamsearch@^1.1.0:
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
+streamx@^2.15.0:
+ version "2.15.1"
+ resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.15.1.tgz#396ad286d8bc3eeef8f5cea3f029e81237c024c6"
+ integrity sha512-fQMzy2O/Q47rgwErk/eGeLu/roaFWV0jVsogDmrszM9uIw8L5OA+t+V93MgYlufNptfjmYR1tOMWhei/Eh7TQA==
+ dependencies:
+ fast-fifo "^1.1.0"
+ queue-tick "^1.0.1"
+
strict-uri-encode@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
@@ -26120,6 +28207,15 @@ string-width@^5.0.0, string-width@^5.0.1, string-width@^5.1.2:
emoji-regex "^9.2.2"
strip-ansi "^7.0.1"
+string-width@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-6.1.0.tgz#96488d6ed23f9ad5d82d13522af9e4c4c3fd7518"
+ integrity sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==
+ dependencies:
+ eastasianwidth "^0.2.0"
+ emoji-regex "^10.2.1"
+ strip-ansi "^7.0.1"
+
string.prototype.matchall@^4.0.5, string.prototype.matchall@^4.0.6, string.prototype.matchall@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz#3bf85722021816dcd1bf38bb714915887ca79fd3"
@@ -26180,6 +28276,14 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
+stringify-entities@^4.0.0:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-4.0.3.tgz#cfabd7039d22ad30f3cc435b0ca2c1574fc88ef8"
+ integrity sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==
+ dependencies:
+ character-entities-html4 "^2.0.0"
+ character-entities-legacy "^3.0.0"
+
stringify-object@^3.2.1:
version "3.3.0"
resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629"
@@ -26231,6 +28335,18 @@ strip-ansi@^7.0.1:
dependencies:
ansi-regex "^6.0.1"
+strip-ansi@^7.1.0:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
+ integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==
+ dependencies:
+ ansi-regex "^6.0.1"
+
+strip-bom-string@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92"
+ integrity sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==
+
strip-bom@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
@@ -26258,6 +28374,11 @@ strip-final-newline@^2.0.0:
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
+strip-final-newline@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd"
+ integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==
+
strip-indent@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2"
@@ -26614,6 +28735,25 @@ tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0:
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
+tar-fs@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784"
+ integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==
+ dependencies:
+ chownr "^1.1.1"
+ mkdirp-classic "^0.5.2"
+ pump "^3.0.0"
+ tar-stream "^2.1.4"
+
+tar-fs@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-3.0.4.tgz#a21dc60a2d5d9f55e0089ccd78124f1d3771dbbf"
+ integrity sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==
+ dependencies:
+ mkdirp-classic "^0.5.2"
+ pump "^3.0.0"
+ tar-stream "^3.1.5"
+
tar-stream@^2.1.4, tar-stream@~2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
@@ -26625,6 +28765,15 @@ tar-stream@^2.1.4, tar-stream@~2.2.0:
inherits "^2.0.3"
readable-stream "^3.1.1"
+tar-stream@^3.1.5:
+ version "3.1.6"
+ resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.1.6.tgz#6520607b55a06f4a2e2e04db360ba7d338cc5bab"
+ integrity sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==
+ dependencies:
+ b4a "^1.6.4"
+ fast-fifo "^1.2.0"
+ streamx "^2.15.0"
+
tar@6.1.11:
version "6.1.11"
resolved "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621"
@@ -27197,6 +29346,11 @@ tree-sync@^2.0.0, tree-sync@^2.1.0:
quick-temp "^0.1.5"
walk-sync "^0.3.3"
+trim-lines@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338"
+ integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==
+
trim-newlines@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
@@ -27217,6 +29371,16 @@ triple-beam@^1.3.0:
resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9"
integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==
+trough@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/trough/-/trough-2.1.0.tgz#0f7b511a4fde65a46f18477ab38849b22c554876"
+ integrity sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==
+
+ts-api-utils@^1.0.1:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331"
+ integrity sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==
+
ts-interface-checker@^0.1.9:
version "0.1.13"
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
@@ -27279,6 +29443,18 @@ tsconfig-paths@^4.1.2:
minimist "^1.2.6"
strip-bom "^3.0.0"
+tsconfig-resolver@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/tsconfig-resolver/-/tsconfig-resolver-3.0.1.tgz#c9e62e328ecfbeaae4a4f1131a92cdbed12350c4"
+ integrity sha512-ZHqlstlQF449v8glscGRXzL6l2dZvASPCdXJRWG4gHEZlUVx2Jtmr+a2zeVG4LCsKhDXKRj5R3h0C/98UcVAQg==
+ dependencies:
+ "@types/json5" "^0.0.30"
+ "@types/resolve" "^1.17.0"
+ json5 "^2.1.3"
+ resolve "^1.17.0"
+ strip-bom "^4.0.0"
+ type-fest "^0.13.1"
+
tslib@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.1.tgz#410eb0d113e5b6356490eec749603725b021b43e"
@@ -27304,7 +29480,7 @@ tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.2.tgz#1b6f07185c881557b0ffa84b111a0106989e8338"
integrity sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==
-tsutils@^3.0.0, tsutils@^3.17.1, tsutils@^3.21.0:
+tsutils@^3.17.1, tsutils@^3.21.0:
version "3.21.0"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==
@@ -27366,6 +29542,11 @@ type-fest@^0.11.0:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1"
integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==
+type-fest@^0.13.1:
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934"
+ integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==
+
type-fest@^0.18.0:
version "0.18.1"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f"
@@ -27401,7 +29582,7 @@ type-fest@^0.8.1:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
-type-fest@^2.3.3:
+type-fest@^2.13.0, type-fest@^2.3.3:
version "2.19.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b"
integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==
@@ -27570,6 +29751,11 @@ undici@^5.21.0:
dependencies:
busboy "^1.6.0"
+unherit@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/unherit/-/unherit-3.0.1.tgz#65b98bb7cb58cee755d7ec699a49e9e8ff172e23"
+ integrity sha512-akOOQ/Yln8a2sgcLj4U0Jmx0R5jpIg2IUyRrWOzmEbjBtGzBdHtSeFKgoEcoH4KYIG/Pb8GQ/BwtYm0GCq1Sqg==
+
unicode-canonical-property-names-ecmascript@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc"
@@ -27598,6 +29784,19 @@ unicode-property-aliases-ecmascript@^2.0.0:
resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz#0a36cb9a585c4f6abd51ad1deddb285c165297c8"
integrity sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==
+unified@^10.0.0, unified@^10.1.2:
+ version "10.1.2"
+ resolved "https://registry.yarnpkg.com/unified/-/unified-10.1.2.tgz#b1d64e55dafe1f0b98bb6c719881103ecf6c86df"
+ integrity sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==
+ dependencies:
+ "@types/unist" "^2.0.0"
+ bail "^2.0.0"
+ extend "^3.0.0"
+ is-buffer "^2.0.0"
+ is-plain-obj "^4.0.0"
+ trough "^2.0.0"
+ vfile "^5.0.0"
+
union-value@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847"
@@ -27667,6 +29866,88 @@ unique-string@^2.0.0:
dependencies:
crypto-random-string "^2.0.0"
+unist-util-generated@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-2.0.1.tgz#e37c50af35d3ed185ac6ceacb6ca0afb28a85cae"
+ integrity sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==
+
+unist-util-is@^5.0.0:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-5.2.1.tgz#b74960e145c18dcb6226bc57933597f5486deae9"
+ integrity sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==
+ dependencies:
+ "@types/unist" "^2.0.0"
+
+unist-util-is@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-6.0.0.tgz#b775956486aff107a9ded971d996c173374be424"
+ integrity sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==
+ dependencies:
+ "@types/unist" "^3.0.0"
+
+unist-util-modify-children@^3.0.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/unist-util-modify-children/-/unist-util-modify-children-3.1.1.tgz#c4018b86441aa3b54b3edff1151d0aa062384c82"
+ integrity sha512-yXi4Lm+TG5VG+qvokP6tpnk+r1EPwyYL04JWDxLvgvPV40jANh7nm3udk65OOWquvbMDe+PL9+LmkxDpTv/7BA==
+ dependencies:
+ "@types/unist" "^2.0.0"
+ array-iterate "^2.0.0"
+
+unist-util-position@^4.0.0:
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-4.0.4.tgz#93f6d8c7d6b373d9b825844645877c127455f037"
+ integrity sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==
+ dependencies:
+ "@types/unist" "^2.0.0"
+
+unist-util-stringify-position@^3.0.0:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz#03ad3348210c2d930772d64b489580c13a7db39d"
+ integrity sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==
+ dependencies:
+ "@types/unist" "^2.0.0"
+
+unist-util-visit-children@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/unist-util-visit-children/-/unist-util-visit-children-2.0.2.tgz#0f00a5caff567074568da2d89c54b5ee4a8c5440"
+ integrity sha512-+LWpMFqyUwLGpsQxpumsQ9o9DG2VGLFrpz+rpVXYIEdPy57GSy5HioC0g3bg/8WP9oCLlapQtklOzQ8uLS496Q==
+ dependencies:
+ "@types/unist" "^2.0.0"
+
+unist-util-visit-parents@^5.0.0, unist-util-visit-parents@^5.1.1:
+ version "5.1.3"
+ resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz#b4520811b0ca34285633785045df7a8d6776cfeb"
+ integrity sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==
+ dependencies:
+ "@types/unist" "^2.0.0"
+ unist-util-is "^5.0.0"
+
+unist-util-visit-parents@^6.0.0:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz#4d5f85755c3b8f0dc69e21eca5d6d82d22162815"
+ integrity sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ unist-util-is "^6.0.0"
+
+unist-util-visit@^4.0.0, unist-util-visit@^4.1.0, unist-util-visit@^4.1.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-4.1.2.tgz#125a42d1eb876283715a3cb5cceaa531828c72e2"
+ integrity sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==
+ dependencies:
+ "@types/unist" "^2.0.0"
+ unist-util-is "^5.0.0"
+ unist-util-visit-parents "^5.1.1"
+
+unist-util-visit@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz#a7de1f31f72ffd3519ea71814cccf5fd6a9217d6"
+ integrity sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ unist-util-is "^6.0.0"
+ unist-util-visit-parents "^6.0.0"
+
universal-analytics@0.4.23:
version "0.4.23"
resolved "https://registry.yarnpkg.com/universal-analytics/-/universal-analytics-0.4.23.tgz#d915e676850c25c4156762471bdd7cf2eaaca8ac"
@@ -27744,6 +30025,14 @@ update-browserslist-db@^1.0.10, update-browserslist-db@^1.0.9:
escalade "^3.1.1"
picocolors "^1.0.0"
+update-browserslist-db@^1.0.13:
+ version "1.0.13"
+ resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4"
+ integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==
+ dependencies:
+ escalade "^3.1.1"
+ picocolors "^1.0.0"
+
update-notifier@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-5.1.0.tgz#4ab0d7c7f36a231dd7316cf7729313f0214d9ad9"
@@ -27945,6 +30234,16 @@ uuid@^9.0.0:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
+uvu@^0.5.0:
+ version "0.5.6"
+ resolved "https://registry.yarnpkg.com/uvu/-/uvu-0.5.6.tgz#2754ca20bcb0bb59b64e9985e84d2e81058502df"
+ integrity sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==
+ dependencies:
+ dequal "^2.0.0"
+ diff "^5.0.0"
+ kleur "^4.0.3"
+ sade "^1.7.3"
+
v8-compile-cache-lib@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
@@ -28057,6 +30356,32 @@ verror@1.10.0:
core-util-is "1.0.2"
extsprintf "^1.2.0"
+vfile-location@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-4.1.0.tgz#69df82fb9ef0a38d0d02b90dd84620e120050dd0"
+ integrity sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==
+ dependencies:
+ "@types/unist" "^2.0.0"
+ vfile "^5.0.0"
+
+vfile-message@^3.0.0:
+ version "3.1.4"
+ resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-3.1.4.tgz#15a50816ae7d7c2d1fa87090a7f9f96612b59dea"
+ integrity sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==
+ dependencies:
+ "@types/unist" "^2.0.0"
+ unist-util-stringify-position "^3.0.0"
+
+vfile@^5.0.0, vfile@^5.3.7:
+ version "5.3.7"
+ resolved "https://registry.yarnpkg.com/vfile/-/vfile-5.3.7.tgz#de0677e6683e3380fafc46544cfe603118826ab7"
+ integrity sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==
+ dependencies:
+ "@types/unist" "^2.0.0"
+ is-buffer "^2.0.0"
+ unist-util-stringify-position "^3.0.0"
+ vfile-message "^3.0.0"
+
vite-node@0.29.2:
version "0.29.2"
resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-0.29.2.tgz#463626197e248971774075faf3d6896c29cf8062"
@@ -28093,6 +30418,17 @@ vite@4.0.5:
optionalDependencies:
fsevents "~2.3.2"
+vite@^4.4.9:
+ version "4.4.11"
+ resolved "https://registry.yarnpkg.com/vite/-/vite-4.4.11.tgz#babdb055b08c69cfc4c468072a2e6c9ca62102b0"
+ integrity sha512-ksNZJlkcU9b0lBwAGZGGaZHCMqHsc8OpgtoYhsQ4/I2v5cnpmmmqe5pM4nv/4Hn6G/2GhTdj0DhZh2e+Er1q5A==
+ dependencies:
+ esbuild "^0.18.10"
+ postcss "^8.4.27"
+ rollup "^3.27.1"
+ optionalDependencies:
+ fsevents "~2.3.2"
+
vitefu@^0.2.4:
version "0.2.4"
resolved "https://registry.yarnpkg.com/vitefu/-/vitefu-0.2.4.tgz#212dc1a9d0254afe65e579351bed4e25d81e0b35"
@@ -28143,6 +30479,16 @@ void-elements@^2.0.0:
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=
+vscode-oniguruma@^1.7.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz#439bfad8fe71abd7798338d1cd3dc53a8beea94b"
+ integrity sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==
+
+vscode-textmate@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-8.0.0.tgz#2c7a3b1163ef0441097e0b5d6389cd5504b59e5d"
+ integrity sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==
+
vue@~3.2.41:
version "3.2.45"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.45.tgz#94a116784447eb7dbd892167784619fef379b3c8"
@@ -28305,6 +30651,11 @@ web-encoding@1.1.5:
optionalDependencies:
"@zxing/text-encoding" "0.9.0"
+web-namespaces@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-2.0.1.tgz#1010ff7c650eccb2592cebeeaf9a1b253fd40692"
+ integrity sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==
+
web-streams-polyfill@^3.1.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6"
@@ -28576,7 +30927,7 @@ webpack@5.50.0:
watchpack "^2.2.0"
webpack-sources "^3.2.0"
-webpack@^4.30.0, webpack@^4.44.1:
+webpack@^4.44.1:
version "4.46.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.46.0.tgz#bf9b4404ea20a073605e0a011d188d77cb6ad542"
integrity sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==
@@ -28605,6 +30956,35 @@ webpack@^4.30.0, webpack@^4.44.1:
watchpack "^1.7.4"
webpack-sources "^1.4.1"
+webpack@^4.47.0:
+ version "4.47.0"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.47.0.tgz#8b8a02152d7076aeb03b61b47dad2eeed9810ebc"
+ integrity sha512-td7fYwgLSrky3fI1EuU5cneU4+pbH6GgOfuKNS1tNPcfdGinGELAqsb/BP4nnvZyKSG2i/xFGU7+n2PvZA8HJQ==
+ dependencies:
+ "@webassemblyjs/ast" "1.9.0"
+ "@webassemblyjs/helper-module-context" "1.9.0"
+ "@webassemblyjs/wasm-edit" "1.9.0"
+ "@webassemblyjs/wasm-parser" "1.9.0"
+ acorn "^6.4.1"
+ ajv "^6.10.2"
+ ajv-keywords "^3.4.1"
+ chrome-trace-event "^1.0.2"
+ enhanced-resolve "^4.5.0"
+ eslint-scope "^4.0.3"
+ json-parse-better-errors "^1.0.2"
+ loader-runner "^2.4.0"
+ loader-utils "^1.2.3"
+ memory-fs "^0.4.1"
+ micromatch "^3.1.10"
+ mkdirp "^0.5.3"
+ neo-async "^2.6.1"
+ node-libs-browser "^2.2.1"
+ schema-utils "^1.0.0"
+ tapable "^1.1.3"
+ terser-webpack-plugin "^1.4.3"
+ watchpack "^1.7.4"
+ webpack-sources "^1.4.1"
+
webpack@^5.52.0, webpack@~5.74.0:
version "5.74.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980"
@@ -28749,6 +31129,27 @@ which-module@^2.0.0:
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
+which-pm-runs@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.1.0.tgz#35ccf7b1a0fce87bd8b92a478c9d045785d3bf35"
+ integrity sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==
+
+which-pm@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/which-pm/-/which-pm-2.0.0.tgz#8245609ecfe64bf751d0eef2f376d83bf1ddb7ae"
+ integrity sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w==
+ dependencies:
+ load-yaml-file "^0.2.0"
+ path-exists "^4.0.0"
+
+which-pm@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/which-pm/-/which-pm-2.1.1.tgz#0be2b70c67e94a32e87b9768a94a7f0954f2dcfa"
+ integrity sha512-xzzxNw2wMaoCWXiGE8IJ9wuPMU+EYhFksjHxrRT8kMT5SnocBPRg69YAMtyV4D12fP582RA+k3P8H9J5EMdIxQ==
+ dependencies:
+ load-yaml-file "^0.2.0"
+ path-exists "^4.0.0"
+
which-typed-array@^1.1.2:
version "1.1.4"
resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.4.tgz#8fcb7d3ee5adf2d771066fba7cf37e32fe8711ff"
@@ -28812,6 +31213,13 @@ widest-line@^3.1.0:
dependencies:
string-width "^4.0.0"
+widest-line@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-4.0.1.tgz#a0fc673aaba1ea6f0a0d35b3c2795c9a9cc2ebf2"
+ integrity sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==
+ dependencies:
+ string-width "^5.0.1"
+
wildcard@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec"
@@ -29234,9 +31642,19 @@ yocto-queue@^1.0.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251"
integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==
+zod@3.21.1:
+ version "3.21.1"
+ resolved "https://registry.yarnpkg.com/zod/-/zod-3.21.1.tgz#ac5bb7cf68876281ebd02f95ac4bb9a080370282"
+ integrity sha512-+dTu2m6gmCbO9Ahm4ZBDapx2O6ZY9QSPXst2WXjcznPMwf2YNpn3RevLx4KkZp1OPW/ouFcoBtBzFz/LeY69oA==
+
zone.js@^0.11.8, zone.js@~0.11.4:
version "0.11.8"
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.11.8.tgz#40dea9adc1ad007b5effb2bfed17f350f1f46a21"
integrity sha512-82bctBg2hKcEJ21humWIkXRlLBBmrc3nN7DFh5LGGhcyycO2S7FN8NmdvlcKaGFDNVL4/9kFLmwmInTavdJERA==
dependencies:
tslib "^2.3.0"
+
+zwitch@^2.0.0, zwitch@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7"
+ integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==