diff --git a/.craft.yml b/.craft.yml
index e8bbfaf3b3b5..39a52c69437a 100644
--- a/.craft.yml
+++ b/.craft.yml
@@ -167,6 +167,8 @@ targets:
onlyIfPresent: /^sentry-angular-ivy-\d.*\.tgz$/
'npm:@sentry/angular':
onlyIfPresent: /^sentry-angular-\d.*\.tgz$/
+ 'npm:@sentry/astro':
+ onlyIfPresent: /^sentry-astro-\d.*\.tgz$/
'npm:@sentry/wasm':
onlyIfPresent: /^sentry-wasm-\d.*\.tgz$/
'npm:@sentry/nextjs':
diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml
index 50793411c277..c7e01af332a2 100644
--- a/.github/ISSUE_TEMPLATE/bug.yml
+++ b/.github/ISSUE_TEMPLATE/bug.yml
@@ -30,6 +30,7 @@ body:
If you're using the CDN bundles, please specify the exact bundle (e.g. `bundle.tracing.min.js`) in your SDK
setup.
options:
+ - '@sentry/astro'
- '@sentry/browser'
- '@sentry/angular'
- '@sentry/angular-ivy'
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 3191f92adefb..e233d1dec828 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -848,6 +848,7 @@ jobs:
'create-next-app',
'create-remix-app',
'create-remix-app-v2',
+ 'debug-id-sourcemaps',
'nextjs-app-dir',
'react-create-hash-router',
'react-router-6-use-routes',
diff --git a/.github/workflows/issue-package-label.yml b/.github/workflows/issue-package-label.yml
index f50679e5f112..1c496c927762 100644
--- a/.github/workflows/issue-package-label.yml
+++ b/.github/workflows/issue-package-label.yml
@@ -29,6 +29,9 @@ jobs:
# Note: Since this is handled as a regex, and JSON parse wrangles slashes /, we just use `.` instead
map: |
{
+ "@sentry.astro": {
+ "label": "Package: Astro"
+ },
"@sentry.browser": {
"label": "Package: Browser"
},
diff --git a/.size-limit.js b/.size-limit.js
index be8fa57b4934..4bcda6b45e4a 100644
--- a/.size-limit.js
+++ b/.size-limit.js
@@ -27,7 +27,7 @@ module.exports = [
name: '@sentry/browser (incl. Tracing, Replay) - ES6 CDN Bundle (gzipped)',
path: 'packages/browser/build/bundles/bundle.tracing.replay.min.js',
gzip: true,
- limit: '80 KB',
+ limit: '90 KB',
},
{
name: '@sentry/browser (incl. Tracing) - ES6 CDN Bundle (gzipped)',
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 58bd0ab2a355..d053038e9465 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,19 @@
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
+## 7.74.1
+
+- chore(astro): Add `astro-integration` keyword (#9265)
+- fix(core): Narrow filters for health check transactions (#9257)
+- fix(nextjs): Fix HMR by inserting new entrypoints at the end (#9267)
+- fix(nextjs): Fix resolution of request async storage module (#9259)
+- fix(node-experimental): Guard against missing `fetch` (#9275)
+- fix(remix): Update `defer` injection logic. (#9242)
+- fix(tracing-internal): Parameterize express middleware parameters (#8668)
+- fix(utils): Move Node specific ANR impl. out of utils (#9258)
+
+Work in this release contributed by @LubomirIgonda1. Thank you for your contribution!
+
## 7.74.0
### Important Changes
diff --git a/README.md b/README.md
index 66502992eca1..2f48be33350b 100644
--- a/README.md
+++ b/README.md
@@ -39,11 +39,13 @@ For each major JavaScript platform, there is a specific high-level SDK that prov
package. Please refer to the README and instructions of those SDKs for more detailed information:
- [`@sentry/browser`](https://github.com/getsentry/sentry-javascript/tree/master/packages/browser): SDK for Browsers
+- [`@sentry/bun`](https://github.com/getsentry/sentry-javascript/tree/master/packages/bun): SDK for Bun
- [`@sentry/node`](https://github.com/getsentry/sentry-javascript/tree/master/packages/node): SDK for Node including
integrations for Express
- [`@sentry/angular`](https://github.com/getsentry/sentry-javascript/tree/master/packages/angular): Browser SDK for Angular
- [`@sentry/angular-ivy`](https://github.com/getsentry/sentry-javascript/tree/master/packages/angular-ivy): Browser SDK for
Angular with native support for Angular's Ivy rendering engine.
+- [`@sentry/astro`](https://github.com/getsentry/sentry-javascript/tree/master/packages/astro): SDK for Astro
- [`@sentry/ember`](https://github.com/getsentry/sentry-javascript/tree/master/packages/ember): Browser SDK for Ember
- [`@sentry/react`](https://github.com/getsentry/sentry-javascript/tree/master/packages/react): Browser SDK for React
- [`@sentry/svelte`](https://github.com/getsentry/sentry-javascript/tree/master/packages/svelte): Browser SDK for Svelte
diff --git a/package.json b/package.json
index b5cbf9ddc38e..364c97a53a75 100644
--- a/package.json
+++ b/package.json
@@ -50,6 +50,7 @@
"packages/ember",
"packages/eslint-config-sdk",
"packages/eslint-plugin-sdk",
+ "packages/feedback",
"packages/gatsby",
"packages/hub",
"packages/integrations",
@@ -82,7 +83,7 @@
"@rollup/plugin-replace": "^3.0.1",
"@rollup/plugin-sucrase": "^4.0.3",
"@rollup/plugin-typescript": "^8.3.1",
- "@size-limit/preset-small-lib": "^4.5.5",
+ "@size-limit/preset-small-lib": "~9.0.0",
"@strictsoftware/typedoc-plugin-monorepo": "^0.3.1",
"@types/chai": "^4.1.3",
"@types/jest": "^27.4.1",
@@ -118,7 +119,7 @@
"rollup-plugin-license": "^2.6.1",
"rollup-plugin-terser": "^7.0.2",
"sinon": "^7.3.2",
- "size-limit": "^4.5.5",
+ "size-limit": "~9.0.0",
"ts-jest": "^27.1.4",
"ts-node": "10.9.1",
"tslib": "2.4.1",
diff --git a/packages/astro/package.json b/packages/astro/package.json
index aac0e6a88d00..91cac9038689 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -7,6 +7,7 @@
"keywords": [
"withastro",
"astro-component",
+ "astro-integration",
"sentry",
"apm"
],
diff --git a/packages/core/src/integrations/inboundfilters.ts b/packages/core/src/integrations/inboundfilters.ts
index 9c6e8c9a546a..20721d9dbcec 100644
--- a/packages/core/src/integrations/inboundfilters.ts
+++ b/packages/core/src/integrations/inboundfilters.ts
@@ -6,11 +6,11 @@ import { getEventDescription, logger, stringMatchesSomePattern } from '@sentry/u
const DEFAULT_IGNORE_ERRORS = [/^Script error\.?$/, /^Javascript error: Script error\.? on line 0$/];
const DEFAULT_IGNORE_TRANSACTIONS = [
- /^.*healthcheck.*$/,
- /^.*healthy.*$/,
- /^.*live.*$/,
- /^.*ready.*$/,
- /^.*heartbeat.*$/,
+ /^.*\/healthcheck$/,
+ /^.*\/healthy$/,
+ /^.*\/live$/,
+ /^.*\/ready$/,
+ /^.*\/heartbeat$/,
/^.*\/health$/,
/^.*\/healthz$/,
];
diff --git a/packages/core/test/lib/integrations/inboundfilters.test.ts b/packages/core/test/lib/integrations/inboundfilters.test.ts
index 8e42c5bc29ee..dad4606a7c80 100644
--- a/packages/core/test/lib/integrations/inboundfilters.test.ts
+++ b/packages/core/test/lib/integrations/inboundfilters.test.ts
@@ -428,6 +428,18 @@ describe('InboundFilters', () => {
expect(eventProcessor(TRANSACTION_EVENT_HEALTH_2, {})).toBe(TRANSACTION_EVENT_HEALTH_2);
expect(eventProcessor(TRANSACTION_EVENT_HEALTH_3, {})).toBe(TRANSACTION_EVENT_HEALTH_3);
});
+
+ it.each(['/delivery', '/already', '/healthysnacks'])(
+ "doesn't filter out transactions that have similar names to health check ones (%s)",
+ transaction => {
+ const eventProcessor = createInboundFiltersEventProcessor();
+ const evt: Event = {
+ transaction,
+ type: 'transaction',
+ };
+ expect(eventProcessor(evt, {})).toBe(evt);
+ },
+ );
});
describe('denyUrls/allowUrls', () => {
diff --git a/packages/deno/rollup.config.js b/packages/deno/rollup.config.js
index 48123037a596..236501153f8b 100644
--- a/packages/deno/rollup.config.js
+++ b/packages/deno/rollup.config.js
@@ -1,9 +1,12 @@
+// @ts-check
import nodeResolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import sucrase from '@rollup/plugin-sucrase';
+import { defineConfig } from 'rollup';
-export default {
+export default defineConfig({
input: ['src/index.ts'],
+ treeshake: 'smallest',
output: {
dir: 'build',
sourcemap: true,
@@ -21,4 +24,4 @@ export default {
commonjs(),
sucrase({ transforms: ['typescript'] }),
],
-};
+});
diff --git a/packages/e2e-tests/test-applications/debug-id-sourcemaps/.gitignore b/packages/e2e-tests/test-applications/debug-id-sourcemaps/.gitignore
new file mode 100644
index 000000000000..1521c8b7652b
--- /dev/null
+++ b/packages/e2e-tests/test-applications/debug-id-sourcemaps/.gitignore
@@ -0,0 +1 @@
+dist
diff --git a/packages/e2e-tests/test-applications/debug-id-sourcemaps/.npmrc b/packages/e2e-tests/test-applications/debug-id-sourcemaps/.npmrc
new file mode 100644
index 000000000000..c6b3ef9b3eaa
--- /dev/null
+++ b/packages/e2e-tests/test-applications/debug-id-sourcemaps/.npmrc
@@ -0,0 +1,2 @@
+@sentry:registry=http://localhost:4873
+@sentry-internal:registry=http://localhost:4873
diff --git a/packages/e2e-tests/test-applications/debug-id-sourcemaps/package.json b/packages/e2e-tests/test-applications/debug-id-sourcemaps/package.json
new file mode 100644
index 000000000000..b261ace67fb8
--- /dev/null
+++ b/packages/e2e-tests/test-applications/debug-id-sourcemaps/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "debug-id-sourcemaps",
+ "version": "1.0.0",
+ "private": true,
+ "scripts": {
+ "build": "rollup --config rollup.config.mjs",
+ "test": "vitest run",
+ "clean": "npx rimraf node_modules,pnpm-lock.yaml",
+ "test:build": "pnpm install && pnpm build",
+ "test:assert": "pnpm test"
+ },
+ "dependencies": {
+ "@sentry/node": "latest || *"
+ },
+ "devDependencies": {
+ "rollup": "^4.0.2",
+ "vitest": "^0.34.6",
+ "@sentry/rollup-plugin": "2.8.0"
+ },
+ "volta": {
+ "extends": "../../package.json"
+ }
+}
diff --git a/packages/e2e-tests/test-applications/debug-id-sourcemaps/rollup.config.mjs b/packages/e2e-tests/test-applications/debug-id-sourcemaps/rollup.config.mjs
new file mode 100644
index 000000000000..47273c79ea9d
--- /dev/null
+++ b/packages/e2e-tests/test-applications/debug-id-sourcemaps/rollup.config.mjs
@@ -0,0 +1,20 @@
+import { defineConfig } from 'rollup';
+import { sentryRollupPlugin } from '@sentry/rollup-plugin';
+
+export default defineConfig({
+ input: './src/app.js',
+ external: ['@sentry/node'],
+ plugins: [
+ sentryRollupPlugin({
+ org: process.env.E2E_TEST_SENTRY_ORG_SLUG,
+ project: process.env.E2E_TEST_SENTRY_TEST_PROJECT,
+ authToken: process.env.E2E_TEST_AUTH_TOKEN,
+ }),
+ ],
+ output: {
+ file: './dist/app.js',
+ compact: true,
+ format: 'cjs',
+ sourcemap: 'hidden',
+ },
+});
diff --git a/packages/e2e-tests/test-applications/debug-id-sourcemaps/src/app.js b/packages/e2e-tests/test-applications/debug-id-sourcemaps/src/app.js
new file mode 100644
index 000000000000..68a4aae16d85
--- /dev/null
+++ b/packages/e2e-tests/test-applications/debug-id-sourcemaps/src/app.js
@@ -0,0 +1,10 @@
+import * as Sentry from '@sentry/node';
+
+Sentry.init({
+ environment: 'qa', // dynamic sampling bias to keep transactions
+ dsn: process.env.E2E_TEST_DSN,
+});
+
+const eventId = Sentry.captureException(new Error('Sentry Debug ID E2E Test Error'));
+
+process.stdout.write(eventId);
diff --git a/packages/e2e-tests/test-applications/debug-id-sourcemaps/tests/__snapshots__/server.test.ts.snap b/packages/e2e-tests/test-applications/debug-id-sourcemaps/tests/__snapshots__/server.test.ts.snap
new file mode 100644
index 000000000000..8b157beba9da
--- /dev/null
+++ b/packages/e2e-tests/test-applications/debug-id-sourcemaps/tests/__snapshots__/server.test.ts.snap
@@ -0,0 +1,20 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`Find symbolicated event on sentry 1`] = `
+{
+ "colno": 41,
+ "contextLine": "const eventId = Sentry.captureException(new Error('Sentry Debug ID E2E Test Error'));",
+ "lineno": 8,
+ "postContext": [
+ "",
+ "process.stdout.write(eventId);",
+ ],
+ "preContext": [
+ "Sentry.init({",
+ " environment: 'qa', // dynamic sampling bias to keep transactions",
+ " dsn: process.env.E2E_TEST_DSN,",
+ "});",
+ "",
+ ],
+}
+`;
diff --git a/packages/e2e-tests/test-applications/debug-id-sourcemaps/tests/server.test.ts b/packages/e2e-tests/test-applications/debug-id-sourcemaps/tests/server.test.ts
new file mode 100644
index 000000000000..df41adb615c8
--- /dev/null
+++ b/packages/e2e-tests/test-applications/debug-id-sourcemaps/tests/server.test.ts
@@ -0,0 +1,53 @@
+import { test } from 'vitest';
+import childProcess from 'child_process';
+import path from 'path';
+
+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(
+ 'Find symbolicated event on sentry',
+ async ({ expect }) => {
+ const eventId = childProcess.execSync(`node ${path.join(__dirname, '..', 'dist', 'app.js')}`, {
+ encoding: 'utf-8',
+ });
+
+ console.log(`Polling for error eventId: ${eventId}`);
+
+ let timedOut = false;
+ setTimeout(() => {
+ timedOut = true;
+ }, EVENT_POLLING_TIMEOUT);
+
+ while (!timedOut) {
+ await new Promise(resolve => setTimeout(resolve, 2000)); // poll every two seconds
+ const response = await fetch(
+ `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${eventId}/json/`,
+ { headers: { Authorization: `Bearer ${authToken}` } },
+ );
+
+ // Only allow ok responses or 404
+ if (!response.ok) {
+ expect(response.status).toBe(404);
+ continue;
+ }
+
+ const eventPayload = await response.json();
+ const frames = eventPayload.exception?.values?.[0]?.stacktrace?.frames;
+ const topFrame = frames[frames.length - 1];
+ expect({
+ preContext: topFrame?.pre_context,
+ contextLine: topFrame?.context_line,
+ postContext: topFrame?.post_context,
+ lineno: topFrame?.lineno,
+ colno: topFrame?.colno,
+ }).toMatchSnapshot();
+ return;
+ }
+
+ throw new Error('Test timed out');
+ },
+ { timeout: EVENT_POLLING_TIMEOUT },
+);
diff --git a/packages/feedback/.eslintignore b/packages/feedback/.eslintignore
new file mode 100644
index 000000000000..b38db2f296ff
--- /dev/null
+++ b/packages/feedback/.eslintignore
@@ -0,0 +1,2 @@
+node_modules/
+build/
diff --git a/packages/feedback/.eslintrc.js b/packages/feedback/.eslintrc.js
new file mode 100644
index 000000000000..cf9985e769c0
--- /dev/null
+++ b/packages/feedback/.eslintrc.js
@@ -0,0 +1,36 @@
+// Note: All paths are relative to the directory in which eslint is being run, rather than the directory where this file
+// lives
+
+// ESLint config docs: https://eslint.org/docs/user-guide/configuring/
+
+module.exports = {
+ extends: ['../../.eslintrc.js'],
+ overrides: [
+ {
+ files: ['jest.setup.ts', 'jest.config.ts'],
+ parserOptions: {
+ project: ['tsconfig.test.json'],
+ },
+ rules: {
+ 'no-console': 'off',
+ },
+ },
+ {
+ files: ['test/**/*.ts'],
+
+ rules: {
+ // most of these errors come from `new Promise(process.nextTick)`
+ '@typescript-eslint/unbound-method': 'off',
+ // TODO: decide if we want to enable this again after the migration
+ // We can take the freedom to be a bit more lenient with tests
+ '@typescript-eslint/no-floating-promises': 'off',
+ },
+ },
+ {
+ files: ['src/types/deprecated.ts'],
+ rules: {
+ '@typescript-eslint/naming-convention': 'off',
+ },
+ },
+ ],
+};
diff --git a/packages/feedback/.gitignore b/packages/feedback/.gitignore
new file mode 100644
index 000000000000..363d3467c6fa
--- /dev/null
+++ b/packages/feedback/.gitignore
@@ -0,0 +1,4 @@
+node_modules
+/*.tgz
+.eslintcache
+build
diff --git a/packages/feedback/LICENSE b/packages/feedback/LICENSE
new file mode 100644
index 000000000000..d11896ba1181
--- /dev/null
+++ b/packages/feedback/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/feedback/README.md b/packages/feedback/README.md
new file mode 100644
index 000000000000..799cea6b59b8
--- /dev/null
+++ b/packages/feedback/README.md
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+# Sentry Integration for Feedback
+
+This SDK is **considered experimental and in an alpha state**. It may experience breaking changes, and may be discontinued at any time. Please reach out on
+[GitHub](https://github.com/getsentry/sentry-javascript/issues/new/choose) if you have any feedback/concerns.
+
+## Pre-requisites
+
+`@sentry/feedback` currently can only be used by browsers with [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM) support.
+
+## Installation
+
+Feedback can be imported from `@sentry/browser`, or a respective SDK package like `@sentry/react` or `@sentry/vue`.
+You don't need to install anything in order to use Feedback. The minimum version that includes Feedback is <>.
+
+For details on using Feedback when using Sentry via the CDN bundles, see [CDN bundle](#loading-feedback-as-a-cdn-bundle).
+
+## Setup
+
+To set up the integration, add the following to your Sentry initialization. Several options are supported and passable via the integration constructor.
+See the [configuration section](#configuration) below for more details.
+
+```javascript
+import * as Sentry from '@sentry/browser';
+// or e.g. import * as Sentry from '@sentry/react';
+
+Sentry.init({
+ dsn: '__DSN__',
+ integrations: [
+ new Sentry.Feedback({
+ // Additional SDK configuration goes in here, for example:
+ // See below for all available options
+ })
+ ],
+ // ...
+});
+```
+
+### Lazy loading Feedback
+
+Feedback will start automatically when you add the integration.
+If you do not want to start Feedback immediately (e.g. if you want to lazy-load it),
+you can also use `addIntegration` to load it later:
+
+```js
+import * as Sentry from "@sentry/react";
+import { BrowserClient } from "@sentry/browser";
+
+Sentry.init({
+ // Do not load it initially
+ integrations: []
+});
+
+// Sometime later
+const { Feedback } = await import('@sentry/browser');
+const client = Sentry.getCurrentHub().getClient();
+
+// Client can be undefined
+client?.addIntegration(new Feedback());
+```
+
+### Identifying Users
+
+If you have only followed the above instructions to setup session feedbacks, you will only see IP addresses in Sentry's UI. In order to associate a user identity to a session feedback, use [`setUser`](https://docs.sentry.io/platforms/javascript/enriching-events/identify-user/).
+
+```javascript
+import * as Sentry from "@sentry/browser";
+
+Sentry.setUser({ email: "jane.doe@example.com" });
+```
+
+## Loading Feedback as a CDN Bundle
+
+As an alternative to the NPM package, you can use Feedback as a CDN bundle.
+Please refer to the [Feedback installation guide](https://docs.sentry.io/platforms/javascript/session-feedback/#install) for CDN bundle instructions.
+
+
+## Configuration
+
+### General Integration Configuration
+
+The following options can be configured as options to the integration, in `new Feedback({})`:
+
+| key | type | default | description |
+| --------- | ------- | ------- | ----------- |
+| tbd | boolean | `true` | tbd |
+
+
+
+## Manually Sending Feedback Data
+
+Connect your own feedback UI to Sentry's You can use `feedback.flush()` to immediately send all currently captured feedback data.
+When Feedback is currently in buffering mode, this will send up to the last 60 seconds of feedback data,
+and also continue sending afterwards, similar to when an error happens & is recorded.
diff --git a/packages/feedback/jest.config.js b/packages/feedback/jest.config.js
new file mode 100644
index 000000000000..24f49ab59a4c
--- /dev/null
+++ b/packages/feedback/jest.config.js
@@ -0,0 +1 @@
+module.exports = require('../../jest/jest.config.js');
diff --git a/packages/feedback/package.json b/packages/feedback/package.json
new file mode 100644
index 000000000000..23e2d3c81f09
--- /dev/null
+++ b/packages/feedback/package.json
@@ -0,0 +1,61 @@
+{
+ "name": "@sentry-internal/feedback",
+ "version": "7.70.0",
+ "description": "Sentry SDK integration for user feedback",
+ "repository": "git://github.com/getsentry/sentry-javascript.git",
+ "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/feedback",
+ "author": "Sentry",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "main": "build/npm/cjs/index.js",
+ "module": "build/npm/esm/index.js",
+ "types": "build/npm/types/index.d.ts",
+ "typesVersions": {
+ "<4.9": {
+ "build/npm/types/index.d.ts": [
+ "build/npm/types-ts3.8/index.d.ts"
+ ]
+ }
+ },
+ "publishConfig": {
+ "access": "public"
+ },
+ "dependencies": {
+ "@sentry/core": "7.70.0",
+ "@sentry/types": "7.70.0",
+ "@sentry/utils": "7.70.0",
+ "tslib": "^2.4.1 || ^1.9.3"
+ },
+ "scripts": {
+ "build": "run-p build:transpile build:types build:bundle",
+ "build:transpile": "rollup -c rollup.npm.config.js",
+ "build:bundle": "rollup -c rollup.bundle.config.js",
+ "build:dev": "run-p build:transpile build:types",
+ "build:types": "run-s build:types:core build:types:downlevel",
+ "build:types:core": "tsc -p tsconfig.types.json",
+ "build:types:downlevel": "yarn downlevel-dts build/npm/types build/npm/types-ts3.8 --to ts3.8",
+ "build:watch": "run-p build:transpile:watch build:bundle:watch build:types:watch",
+ "build:dev:watch": "run-p build:transpile:watch build:types:watch",
+ "build:transpile:watch": "yarn build:transpile --watch",
+ "build:bundle:watch": "yarn build:bundle --watch",
+ "build:types:watch": "tsc -p tsconfig.types.json --watch",
+ "build:tarball": "ts-node ../../scripts/prepack.ts --bundles && npm pack ./build/npm",
+ "circularDepCheck": "madge --circular src/index.ts",
+ "clean": "rimraf build sentry-replay-*.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": "jest",
+ "test:watch": "jest --watch",
+ "yalc:publish": "ts-node ../../scripts/prepack.ts --bundles && yalc publish ./build/npm --push"
+ },
+ "volta": {
+ "extends": "../../package.json"
+ },
+ "sideEffects": false
+}
diff --git a/packages/feedback/rollup.bundle.config.js b/packages/feedback/rollup.bundle.config.js
new file mode 100644
index 000000000000..185b38249dc0
--- /dev/null
+++ b/packages/feedback/rollup.bundle.config.js
@@ -0,0 +1,13 @@
+import { makeBaseBundleConfig, makeBundleConfigVariants } from '../../rollup/index.js';
+
+const baseBundleConfig = makeBaseBundleConfig({
+ bundleType: 'addon',
+ entrypoints: ['src/index.ts'],
+ jsVersion: 'es6',
+ licenseTitle: '@sentry-internal/feedback',
+ outputFileBase: () => 'bundles/feedback',
+});
+
+const builds = makeBundleConfigVariants(baseBundleConfig);
+
+export default builds;
diff --git a/packages/feedback/rollup.npm.config.js b/packages/feedback/rollup.npm.config.js
new file mode 100644
index 000000000000..e823e7b18863
--- /dev/null
+++ b/packages/feedback/rollup.npm.config.js
@@ -0,0 +1,16 @@
+import { makeBaseNPMConfig, makeNPMConfigVariants } from '../../rollup/index';
+
+export default makeNPMConfigVariants(
+ makeBaseNPMConfig({
+ hasBundles: true,
+ packageSpecificConfig: {
+ output: {
+ // set exports to 'named' or 'auto' so that rollup doesn't warn
+ exports: 'named',
+ // set preserveModules to false because for feedback we actually want
+ // to bundle everything into one file.
+ preserveModules: false,
+ },
+ },
+ }),
+);
diff --git a/packages/feedback/src/index.ts b/packages/feedback/src/index.ts
new file mode 100644
index 000000000000..834e9dcce670
--- /dev/null
+++ b/packages/feedback/src/index.ts
@@ -0,0 +1 @@
+export { sendFeedbackRequest } from './util/sendFeedbackRequest';
diff --git a/packages/feedback/src/types/index.ts b/packages/feedback/src/types/index.ts
new file mode 100644
index 000000000000..01a12814c88b
--- /dev/null
+++ b/packages/feedback/src/types/index.ts
@@ -0,0 +1,29 @@
+import type { Event, Primitive } from '@sentry/types';
+
+export type SentryTags = { [key: string]: Primitive } | undefined;
+
+/**
+ * NOTE: These types are still considered Beta and subject to change.
+ * @hidden
+ */
+export interface FeedbackEvent extends Event {
+ feedback: {
+ message: string;
+ url: string;
+ contact_email?: string;
+ name?: string;
+ replay_id?: string;
+ };
+ // TODO: Add this event type to Event
+ // type: 'feedback_event';
+}
+
+export interface SendFeedbackData {
+ feedback: {
+ message: string;
+ url: string;
+ email?: string;
+ replay_id?: string;
+ name?: string;
+ };
+}
diff --git a/packages/feedback/src/util/prepareFeedbackEvent.ts b/packages/feedback/src/util/prepareFeedbackEvent.ts
new file mode 100644
index 000000000000..6d32506d3be4
--- /dev/null
+++ b/packages/feedback/src/util/prepareFeedbackEvent.ts
@@ -0,0 +1,52 @@
+import type { Scope } from '@sentry/core';
+import { prepareEvent } from '@sentry/core';
+import type { Client } from '@sentry/types';
+
+import type { FeedbackEvent } from '../types';
+
+interface PrepareFeedbackEventParams {
+ client: Client;
+ event: FeedbackEvent;
+ scope: Scope;
+}
+/**
+ * Prepare a feedback event & enrich it with the SDK metadata.
+ */
+export async function prepareFeedbackEvent({
+ client,
+ scope,
+ event,
+}: PrepareFeedbackEventParams): Promise {
+ const eventHint = { integrations: undefined };
+ if (client.emit) {
+ client.emit('preprocessEvent', event, eventHint);
+ }
+
+ const preparedEvent = (await prepareEvent(
+ client.getOptions(),
+ event,
+ { integrations: undefined },
+ scope,
+ )) as FeedbackEvent | null;
+
+ // If e.g. a global event processor returned null
+ if (!preparedEvent) {
+ return null;
+ }
+
+ // This normally happens in browser client "_prepareEvent"
+ // but since we do not use this private method from the client, but rather the plain import
+ // we need to do this manually.
+ preparedEvent.platform = preparedEvent.platform || 'javascript';
+
+ // extract the SDK name because `client._prepareEvent` doesn't add it to the event
+ const metadata = client.getSdkMetadata && client.getSdkMetadata();
+ const { name, version } = (metadata && metadata.sdk) || {};
+
+ preparedEvent.sdk = {
+ ...preparedEvent.sdk,
+ name: name || 'sentry.javascript.unknown',
+ version: version || '0.0.0',
+ };
+ return preparedEvent;
+}
diff --git a/packages/feedback/src/util/sendFeedbackRequest.ts b/packages/feedback/src/util/sendFeedbackRequest.ts
new file mode 100644
index 000000000000..626457d6122b
--- /dev/null
+++ b/packages/feedback/src/util/sendFeedbackRequest.ts
@@ -0,0 +1,113 @@
+import { getCurrentHub } from '@sentry/core';
+import { dsnToString } from '@sentry/utils';
+
+import type { SendFeedbackData } from '../types';
+import { prepareFeedbackEvent } from './prepareFeedbackEvent';
+
+/**
+ * Send feedback using `fetch()`
+ */
+export async function sendFeedbackRequest({
+ feedback: { message, email, name, replay_id, url },
+}: SendFeedbackData): Promise {
+ const hub = getCurrentHub();
+
+ if (!hub) {
+ return null;
+ }
+
+ const client = hub.getClient();
+ const scope = hub.getScope();
+ const transport = client && client.getTransport();
+ const dsn = client && client.getDsn();
+
+ if (!client || !transport || !dsn) {
+ return null;
+ }
+
+ const baseEvent = {
+ feedback: {
+ contact_email: email,
+ name,
+ message,
+ replay_id,
+ url,
+ },
+ // type: 'feedback_event',
+ };
+
+ const feedbackEvent = await prepareFeedbackEvent({
+ scope,
+ client,
+ event: baseEvent,
+ });
+
+ if (!feedbackEvent) {
+ // Taken from baseclient's `_processEvent` method, where this is handled for errors/transactions
+ // client.recordDroppedEvent('event_processor', 'feedback', baseEvent);
+ return null;
+ }
+
+ /*
+ For reference, the fully built event looks something like this:
+ {
+ "data": {
+ "dist": "abc123",
+ "environment": "production",
+ "feedback": {
+ "contact_email": "colton.allen@sentry.io",
+ "message": "I really like this user-feedback feature!",
+ "replay_id": "ec3b4dc8b79f417596f7a1aa4fcca5d2",
+ "url": "https://docs.sentry.io/platforms/javascript/"
+ },
+ "id": "1ffe0775ac0f4417aed9de36d9f6f8dc",
+ "platform": "javascript",
+ "release": "version@1.3",
+ "request": {
+ "headers": {
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
+ }
+ },
+ "sdk": {
+ "name": "sentry.javascript.react",
+ "version": "6.18.1"
+ },
+ "tags": {
+ "key": "value"
+ },
+ "timestamp": "2023-08-31T14:10:34.954048",
+ "user": {
+ "email": "username@example.com",
+ "id": "123",
+ "ip_address": "127.0.0.1",
+ "name": "user",
+ "username": "user2270129"
+ }
+ }
+ }
+ */
+
+ // Prevent this data (which, if it exists, was used in earlier steps in the processing pipeline) from being sent to
+ // sentry. (Note: Our use of this property comes and goes with whatever we might be debugging, whatever hacks we may
+ // have temporarily added, etc. Even if we don't happen to be using it at some point in the future, let's not get rid
+ // of this `delete`, lest we miss putting it back in the next time the property is in use.)
+ delete feedbackEvent.sdkProcessingMetadata;
+
+ try {
+ const path = 'https://sentry.io/api/0/feedback/';
+ const response = await fetch(path, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `DSN ${dsnToString(dsn)}`,
+ },
+ body: JSON.stringify(feedbackEvent),
+ });
+ if (!response.ok) {
+ return null;
+ }
+ return response;
+ } catch (err) {
+ return null;
+ }
+}
diff --git a/packages/feedback/test/index.ts b/packages/feedback/test/index.ts
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/packages/feedback/test/unit/util/prepareFeedbackEvent.test.ts b/packages/feedback/test/unit/util/prepareFeedbackEvent.test.ts
new file mode 100644
index 000000000000..cee0d99f0b5c
--- /dev/null
+++ b/packages/feedback/test/unit/util/prepareFeedbackEvent.test.ts
@@ -0,0 +1,87 @@
+import type { Hub, Scope } from '@sentry/core';
+import { getCurrentHub } from '@sentry/core';
+import type { Client } from '@sentry/types';
+
+import type { FeedbackEvent } from '../../../src/types';
+import { prepareFeedbackEvent } from '../../../src/util/prepareFeedbackEvent';
+import { getDefaultClientOptions, TestClient } from '../../utils/TestClient';
+
+describe('Unit | util | prepareFeedbackEvent', () => {
+ let hub: Hub;
+ let client: Client;
+ let scope: Scope;
+
+ beforeEach(() => {
+ hub = getCurrentHub();
+ client = new TestClient(getDefaultClientOptions());
+ hub.bindClient(client);
+
+ client = hub.getClient()!;
+ scope = hub.getScope()!;
+
+ jest.spyOn(client, 'getSdkMetadata').mockImplementation(() => {
+ return {
+ sdk: {
+ name: 'sentry.javascript.testSdk',
+ version: '1.0.0',
+ },
+ };
+ });
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('works', async () => {
+ expect(client).toBeDefined();
+ expect(scope).toBeDefined();
+
+ const replayId = 'replay-ID';
+ const event: FeedbackEvent = {
+ timestamp: 1670837008.634,
+ event_id: 'feedback-ID',
+ feedback: {
+ contact_email: 'test@test.com',
+ message: 'looks great!',
+ replay_id: replayId,
+ url: 'https://sentry.io/',
+ },
+ contexts: {
+ replay: {
+ error_sample_rate: 1.0,
+ session_sample_rate: 0.1,
+ },
+ },
+ };
+
+ const feedbackEvent = await prepareFeedbackEvent({ scope, client, event });
+
+ expect(client.getSdkMetadata).toHaveBeenCalledTimes(1);
+
+ expect(feedbackEvent).toEqual({
+ timestamp: 1670837008.634,
+ event_id: 'feedback-ID',
+ feedback: {
+ contact_email: 'test@test.com',
+ message: 'looks great!',
+ replay_id: replayId,
+ url: 'https://sentry.io/',
+ },
+ platform: 'javascript',
+ environment: 'production',
+ contexts: {
+ replay: {
+ error_sample_rate: 1.0,
+ session_sample_rate: 0.1,
+ },
+ },
+ sdk: {
+ name: 'sentry.javascript.testSdk',
+ version: '1.0.0',
+ },
+ sdkProcessingMetadata: expect.any(Object),
+ breadcrumbs: undefined,
+ });
+ });
+});
diff --git a/packages/feedback/test/utils/TestClient.ts b/packages/feedback/test/utils/TestClient.ts
new file mode 100644
index 000000000000..ad39b82084a9
--- /dev/null
+++ b/packages/feedback/test/utils/TestClient.ts
@@ -0,0 +1,44 @@
+import { BaseClient, createTransport, initAndBind } from '@sentry/core';
+import type { BrowserClientReplayOptions, ClientOptions, Event, SeverityLevel } from '@sentry/types';
+import { resolvedSyncPromise } from '@sentry/utils';
+
+export interface TestClientOptions extends ClientOptions, BrowserClientReplayOptions {}
+
+export class TestClient extends BaseClient {
+ public constructor(options: TestClientOptions) {
+ super(options);
+ }
+
+ public eventFromException(exception: any): PromiseLike {
+ return resolvedSyncPromise({
+ exception: {
+ values: [
+ {
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
+ type: exception.name,
+ value: exception.message,
+ /* eslint-enable @typescript-eslint/no-unsafe-member-access */
+ },
+ ],
+ },
+ });
+ }
+
+ public eventFromMessage(message: string, level: SeverityLevel = 'info'): PromiseLike {
+ return resolvedSyncPromise({ message, level });
+ }
+}
+
+export function init(options: TestClientOptions): void {
+ initAndBind(TestClient, options);
+}
+
+export function getDefaultClientOptions(options: Partial = {}): ClientOptions {
+ return {
+ integrations: [],
+ dsn: 'https://username@domain/123',
+ transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => resolvedSyncPromise({})),
+ stackParser: () => [],
+ ...options,
+ };
+}
diff --git a/packages/feedback/tsconfig.json b/packages/feedback/tsconfig.json
new file mode 100644
index 000000000000..f8f54556da93
--- /dev/null
+++ b/packages/feedback/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "module": "esnext"
+ },
+ "include": ["src/**/*.ts"]
+}
diff --git a/packages/feedback/tsconfig.test.json b/packages/feedback/tsconfig.test.json
new file mode 100644
index 000000000000..ad87caa06c48
--- /dev/null
+++ b/packages/feedback/tsconfig.test.json
@@ -0,0 +1,15 @@
+{
+ "extends": "./tsconfig.json",
+
+ "include": ["test/**/*.ts", "jest.config.ts", "jest.setup.ts"],
+
+ "compilerOptions": {
+ "types": ["node", "jest"],
+ "esModuleInterop": true,
+ "allowJs": true,
+ "noImplicitAny": true,
+ "noImplicitThis": false,
+ "strictNullChecks": true,
+ "strictPropertyInitialization": false
+ }
+}
diff --git a/packages/feedback/tsconfig.types.json b/packages/feedback/tsconfig.types.json
new file mode 100644
index 000000000000..374fd9bc9364
--- /dev/null
+++ b/packages/feedback/tsconfig.types.json
@@ -0,0 +1,10 @@
+{
+ "extends": "./tsconfig.json",
+
+ "compilerOptions": {
+ "declaration": true,
+ "declarationMap": true,
+ "emitDeclarationOnly": true,
+ "outDir": "build/npm/types"
+ }
+}
diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json
index 13237cb5d69d..0aef2f1e380a 100644
--- a/packages/nextjs/package.json
+++ b/packages/nextjs/package.json
@@ -34,11 +34,13 @@
"@sentry/vercel-edge": "7.74.0",
"@sentry/webpack-plugin": "1.20.0",
"chalk": "3.0.0",
+ "resolve": "1.22.8",
"rollup": "2.78.0",
"stacktrace-parser": "^0.1.10",
"tslib": "^2.4.1 || ^1.9.3"
},
"devDependencies": {
+ "@types/resolve": "1.20.3",
"@types/webpack": "^4.41.31",
"eslint-plugin-react": "^7.31.11",
"next": "10.1.3"
diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts
index ffe7091fa74a..2594623fb5bb 100644
--- a/packages/nextjs/src/config/webpack.ts
+++ b/packages/nextjs/src/config/webpack.ts
@@ -6,6 +6,7 @@ import type SentryCliPlugin from '@sentry/webpack-plugin';
import * as chalk from 'chalk';
import * as fs from 'fs';
import * as path from 'path';
+import { sync as resolveSync } from 'resolve';
import type { VercelCronsConfig } from '../common/types';
// Note: If you need to import a type from Webpack, do it in `types.ts` and export it from there. Otherwise, our
@@ -126,7 +127,10 @@ export function constructWebpackConfigFunction(
pageExtensionRegex,
excludeServerRoutes: userSentryOptions.excludeServerRoutes,
sentryConfigFilePath: getUserConfigFilePath(projectDir, runtime),
- nextjsRequestAsyncStorageModulePath: getRequestAsyncStorageModuleLocation(rawNewConfig.resolve?.modules),
+ nextjsRequestAsyncStorageModulePath: getRequestAsyncStorageModuleLocation(
+ projectDir,
+ rawNewConfig.resolve?.modules,
+ ),
};
const normalizeLoaderResourcePath = (resourcePath: string): string => {
@@ -478,7 +482,7 @@ async function addSentryToEntryProperty(
// we know is that it won't have gotten *simpler* in form, so we only need to worry about the object and function
// options. See https://webpack.js.org/configuration/entry-context/#entry.
- const { isServer, dir: projectDir, nextRuntime } = buildContext;
+ const { isServer, dir: projectDir, nextRuntime, dev: isDevMode } = buildContext;
const runtime = isServer ? (buildContext.nextRuntime === 'edge' ? 'edge' : 'node') : 'browser';
const newEntryProperty =
@@ -498,7 +502,7 @@ async function addSentryToEntryProperty(
// inject into all entry points which might contain user's code
for (const entryPointName in newEntryProperty) {
if (shouldAddSentryToEntryPoint(entryPointName, runtime)) {
- addFilesToExistingEntryPoint(newEntryProperty, entryPointName, filesToInject);
+ addFilesToExistingEntryPoint(newEntryProperty, entryPointName, filesToInject, isDevMode);
} else {
if (
isServer &&
@@ -566,31 +570,44 @@ export function getUserConfigFilePath(projectDir: string, platform: 'server' | '
*
* @param entryProperty The existing `entry` config object
* @param entryPointName The key where the file should be injected
- * @param filepaths An array of paths to the injected files
+ * @param filesToInsert An array of paths to the injected files
*/
function addFilesToExistingEntryPoint(
entryProperty: EntryPropertyObject,
entryPointName: string,
- filepaths: string[],
+ filesToInsert: string[],
+ isDevMode: boolean,
): void {
+ // BIG FAT NOTE: Order of insertion seems to matter here. If we insert the new files before the `currentEntrypoint`s,
+ // the Next.js dev server breaks. Because we generally still want the SDK to be initialized as early as possible we
+ // still keep it at the start of the entrypoints if we are not in dev mode.
+
// can be a string, array of strings, or object whose `import` property is one of those two
const currentEntryPoint = entryProperty[entryPointName];
let newEntryPoint = currentEntryPoint;
- if (typeof currentEntryPoint === 'string') {
- newEntryPoint = [...filepaths, currentEntryPoint];
- } else if (Array.isArray(currentEntryPoint)) {
- newEntryPoint = [...filepaths, ...currentEntryPoint];
+ if (typeof currentEntryPoint === 'string' || Array.isArray(currentEntryPoint)) {
+ newEntryPoint = arrayify(currentEntryPoint);
+
+ if (isDevMode) {
+ // Inserting at beginning breaks dev mode so we insert at the end
+ newEntryPoint.push(...filesToInsert);
+ } else {
+ // In other modes we insert at the beginning so that the SDK initializes as early as possible
+ newEntryPoint.unshift(...filesToInsert);
+ }
}
// descriptor object (webpack 5+)
else if (typeof currentEntryPoint === 'object' && 'import' in currentEntryPoint) {
const currentImportValue = currentEntryPoint.import;
- let newImportValue;
+ const newImportValue = arrayify(currentImportValue);
- if (typeof currentImportValue === 'string') {
- newImportValue = [...filepaths, currentImportValue];
+ if (isDevMode) {
+ // Inserting at beginning breaks dev mode so we insert at the end
+ newImportValue.push(...filesToInsert);
} else {
- newImportValue = [...filepaths, ...currentImportValue];
+ // In other modes we insert at the beginning so that the SDK initializes as early as possible
+ newImportValue.unshift(...filesToInsert);
}
newEntryPoint = {
@@ -977,39 +994,47 @@ function addValueInjectionLoader(
);
}
+function resolveNextPackageDirFromDirectory(basedir: string): string | undefined {
+ try {
+ return path.dirname(resolveSync('next/package.json', { basedir }));
+ } catch {
+ // Should not happen in theory
+ return undefined;
+ }
+}
+
+const POTENTIAL_REQUEST_ASNYC_STORAGE_LOCATIONS = [
+ // Original location of RequestAsyncStorage
+ // https://github.com/vercel/next.js/blob/46151dd68b417e7850146d00354f89930d10b43b/packages/next/src/client/components/request-async-storage.ts
+ 'next/dist/client/components/request-async-storage.js',
+ // Introduced in Next.js 13.4.20
+ // https://github.com/vercel/next.js/blob/e1bc270830f2fc2df3542d4ef4c61b916c802df3/packages/next/src/client/components/request-async-storage.external.ts
+ 'next/dist/client/components/request-async-storage.external.js',
+];
+
function getRequestAsyncStorageModuleLocation(
+ webpackContextDir: string,
webpackResolvableModuleLocations: string[] | undefined,
): string | undefined {
if (webpackResolvableModuleLocations === undefined) {
return undefined;
}
- const absoluteWebpackResolvableModuleLocations = webpackResolvableModuleLocations.map(m => path.resolve(m));
- const moduleIsWebpackResolvable = (moduleId: string): boolean => {
- let requireResolveLocation: string;
- try {
- // This will throw if the location is not resolvable at all.
- // We provide a `paths` filter in order to maximally limit the potential locations to the locations webpack would check.
- requireResolveLocation = require.resolve(moduleId, { paths: webpackResolvableModuleLocations });
- } catch {
- return false;
- }
-
- // Since the require.resolve approach still looks in "global" node_modules locations like for example "/user/lib/node"
- // we further need to filter by locations that start with the locations that webpack would check for.
- return absoluteWebpackResolvableModuleLocations.some(resolvableModuleLocation =>
- requireResolveLocation.startsWith(resolvableModuleLocation),
- );
- };
+ const absoluteWebpackResolvableModuleLocations = webpackResolvableModuleLocations.map(loc =>
+ path.resolve(webpackContextDir, loc),
+ );
- const potentialRequestAsyncStorageLocations = [
- // Original location of RequestAsyncStorage
- // https://github.com/vercel/next.js/blob/46151dd68b417e7850146d00354f89930d10b43b/packages/next/src/client/components/request-async-storage.ts
- 'next/dist/client/components/request-async-storage',
- // Introduced in Next.js 13.4.20
- // https://github.com/vercel/next.js/blob/e1bc270830f2fc2df3542d4ef4c61b916c802df3/packages/next/src/client/components/request-async-storage.external.ts
- 'next/dist/client/components/request-async-storage.external',
- ];
+ for (const webpackResolvableLocation of absoluteWebpackResolvableModuleLocations) {
+ const nextPackageDir = resolveNextPackageDirFromDirectory(webpackResolvableLocation);
+ if (nextPackageDir) {
+ const asyncLocalStorageLocation = POTENTIAL_REQUEST_ASNYC_STORAGE_LOCATIONS.find(loc =>
+ fs.existsSync(path.join(nextPackageDir, '..', loc)),
+ );
+ if (asyncLocalStorageLocation) {
+ return asyncLocalStorageLocation;
+ }
+ }
+ }
- return potentialRequestAsyncStorageLocations.find(potentialLocation => moduleIsWebpackResolvable(potentialLocation));
+ return undefined;
}
diff --git a/packages/node-experimental/src/integrations/node-fetch.ts b/packages/node-experimental/src/integrations/node-fetch.ts
index 73c1eaab27ae..9afd70be62e7 100644
--- a/packages/node-experimental/src/integrations/node-fetch.ts
+++ b/packages/node-experimental/src/integrations/node-fetch.ts
@@ -1,8 +1,8 @@
import type { Span } from '@opentelemetry/api';
import { SpanKind } from '@opentelemetry/api';
-import { registerInstrumentations } from '@opentelemetry/instrumentation';
+import type { Instrumentation } from '@opentelemetry/instrumentation';
import { hasTracingEnabled } from '@sentry/core';
-import type { EventProcessor, Hub, Integration } from '@sentry/types';
+import type { Integration } from '@sentry/types';
import { FetchInstrumentation } from 'opentelemetry-instrumentation-fetch-node';
import { OTEL_ATTR_ORIGIN } from '../constants';
@@ -10,6 +10,7 @@ import type { NodeExperimentalClient } from '../sdk/client';
import { getCurrentHub } from '../sdk/hub';
import { getRequestSpanData } from '../utils/getRequestSpanData';
import { getSpanKind } from '../utils/getSpanKind';
+import { NodePerformanceIntegration } from './NodePerformanceIntegration';
interface NodeFetchOptions {
/**
@@ -31,7 +32,7 @@ interface NodeFetchOptions {
* * Create breadcrumbs for outgoing requests
* * Create spans for outgoing requests
*/
-export class NodeFetch implements Integration {
+export class NodeFetch extends NodePerformanceIntegration implements Integration {
/**
* @inheritDoc
*/
@@ -47,7 +48,6 @@ export class NodeFetch implements Integration {
*/
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;
@@ -56,6 +56,8 @@ export class NodeFetch implements Integration {
* @inheritDoc
*/
public constructor(options: NodeFetchOptions = {}) {
+ super(options);
+
this.name = NodeFetch.id;
this._breadcrumbs = typeof options.breadcrumbs === 'undefined' ? true : options.breadcrumbs;
this._spans = typeof options.spans === 'undefined' ? undefined : options.spans;
@@ -64,14 +66,23 @@ export class NodeFetch implements Integration {
this.shouldCreateSpansForRequests = false;
}
+ /** @inheritDoc */
+ public setupInstrumentation(): void | Instrumentation[] {
+ return [
+ new FetchInstrumentation({
+ onRequest: ({ span }: { span: Span }) => {
+ this._updateSpan(span);
+ this._addRequestBreadcrumb(span);
+ },
+ }),
+ ];
+ }
+
/**
* @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;
- }
+ public setupOnce(): void {
+ super.setupOnce();
const client = getCurrentHub().getClient();
const clientOptions = client?.getOptions();
@@ -79,18 +90,6 @@ export class NodeFetch implements Integration {
// 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);
- },
- }),
- ],
- });
}
/**
diff --git a/packages/node-integration-tests/suites/express/multiple-routers/complex-router/server.ts b/packages/node-integration-tests/suites/express/multiple-routers/complex-router/server.ts
new file mode 100644
index 000000000000..6083d1a85f91
--- /dev/null
+++ b/packages/node-integration-tests/suites/express/multiple-routers/complex-router/server.ts
@@ -0,0 +1,35 @@
+import * as Sentry from '@sentry/node';
+import * as Tracing from '@sentry/tracing';
+import express from 'express';
+
+const app = express();
+
+Sentry.init({
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ release: '1.0',
+ // eslint-disable-next-line deprecation/deprecation
+ integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })],
+ tracesSampleRate: 1.0,
+});
+
+app.use(Sentry.Handlers.requestHandler());
+app.use(Sentry.Handlers.tracingHandler());
+
+const APIv1 = express.Router();
+
+APIv1.use(
+ '/users/:userId',
+ APIv1.get('/posts/:postId', (_req, res) => {
+ Sentry.captureMessage('Custom Message');
+ return res.send('Success');
+ }),
+);
+
+const router = express.Router();
+
+app.use('/api', router);
+app.use('/api/api/v1', APIv1.use('/sub-router', APIv1));
+
+app.use(Sentry.Handlers.errorHandler());
+
+export default app;
diff --git a/packages/node-integration-tests/suites/express/multiple-routers/complex-router/test.ts b/packages/node-integration-tests/suites/express/multiple-routers/complex-router/test.ts
new file mode 100644
index 000000000000..b8079bfdc0ac
--- /dev/null
+++ b/packages/node-integration-tests/suites/express/multiple-routers/complex-router/test.ts
@@ -0,0 +1,27 @@
+import { assertSentryEvent, TestEnv } from '../../../../utils/index';
+
+test('should construct correct url with multiple parameterized routers, when param is also contain in middle layer route and express used multiple middlewares with route', async () => {
+ const env = await TestEnv.init(__dirname, `${__dirname}/server.ts`);
+ const event = await env.getEnvelopeRequest({
+ url: env.url.replace('test', 'api/api/v1/sub-router/users/123/posts/456'),
+ envelopeType: 'transaction',
+ });
+ // parse node.js major version
+ const [major] = process.versions.node.split('.').map(Number);
+ // Split test result base on major node version because regex d flag is support from node 16+
+ if (major >= 16) {
+ assertSentryEvent(event[2] as any, {
+ transaction: 'GET /api/api/v1/sub-router/users/:userId/posts/:postId',
+ transaction_info: {
+ source: 'route',
+ },
+ });
+ } else {
+ assertSentryEvent(event[2] as any, {
+ transaction: 'GET /api/api/v1/sub-router/users/123/posts/:postId',
+ transaction_info: {
+ source: 'route',
+ },
+ });
+ }
+});
diff --git a/packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/server.ts b/packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/server.ts
new file mode 100644
index 000000000000..19cb3c544bde
--- /dev/null
+++ b/packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/server.ts
@@ -0,0 +1,35 @@
+import * as Sentry from '@sentry/node';
+import * as Tracing from '@sentry/tracing';
+import express from 'express';
+
+const app = express();
+
+Sentry.init({
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ release: '1.0',
+ // eslint-disable-next-line deprecation/deprecation
+ integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })],
+ tracesSampleRate: 1.0,
+});
+
+app.use(Sentry.Handlers.requestHandler());
+app.use(Sentry.Handlers.tracingHandler());
+
+const APIv1 = express.Router();
+
+APIv1.use(
+ '/users/:userId',
+ APIv1.get('/posts/:postId', (_req, res) => {
+ Sentry.captureMessage('Custom Message');
+ return res.send('Success');
+ }),
+);
+
+const root = express.Router();
+
+app.use('/api/v1', APIv1);
+app.use('/api', root);
+
+app.use(Sentry.Handlers.errorHandler());
+
+export default app;
diff --git a/packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/test.ts b/packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/test.ts
new file mode 100644
index 000000000000..3527cce5c3a6
--- /dev/null
+++ b/packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/test.ts
@@ -0,0 +1,28 @@
+import { assertSentryEvent, TestEnv } from '../../../../utils/index';
+
+test('should construct correct url with multiple parameterized routers, when param is also contain in middle layer route', async () => {
+ const env = await TestEnv.init(__dirname, `${__dirname}/server.ts`);
+ const event = await env.getEnvelopeRequest({
+ url: env.url.replace('test', 'api/v1/users/123/posts/456'),
+ envelopeType: 'transaction',
+ });
+
+ // parse node.js major version
+ const [major] = process.versions.node.split('.').map(Number);
+ // Split test result base on major node version because regex d flag is support from node 16+
+ if (major >= 16) {
+ assertSentryEvent(event[2] as any, {
+ transaction: 'GET /api/v1/users/:userId/posts/:postId',
+ transaction_info: {
+ source: 'route',
+ },
+ });
+ } else {
+ assertSentryEvent(event[2] as any, {
+ transaction: 'GET /api/v1/users/123/posts/:postId',
+ transaction_info: {
+ source: 'route',
+ },
+ });
+ }
+});
diff --git a/packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-caught.js b/packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-caught.js
index 823fa966aa2a..e04783b9460a 100644
--- a/packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-caught.js
+++ b/packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-caught.js
@@ -4,7 +4,6 @@ const Sentry = require('@sentry/node');
Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
includeLocalVariables: true,
- integrations: [new Sentry.Integrations.LocalVariables({ captureAllExceptions: true })],
beforeSend: event => {
// eslint-disable-next-line no-console
console.log(JSON.stringify(event));
diff --git a/packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-caught.mjs b/packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-caught.mjs
index dfb46f29326e..ea2c1eb2ff68 100644
--- a/packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-caught.mjs
+++ b/packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-caught.mjs
@@ -4,7 +4,6 @@ import * as Sentry from '@sentry/node';
Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
includeLocalVariables: true,
- integrations: [new Sentry.Integrations.LocalVariables({ captureAllExceptions: true })],
beforeSend: event => {
// eslint-disable-next-line no-console
console.log(JSON.stringify(event));
diff --git a/packages/node/src/anr/index.ts b/packages/node/src/anr/index.ts
index 1d4d61b78b55..9c6827c38259 100644
--- a/packages/node/src/anr/index.ts
+++ b/packages/node/src/anr/index.ts
@@ -138,6 +138,20 @@ function startChildProcess(options: Options): void {
}
}
+function createHrTimer(): { getTimeMs: () => number; reset: () => void } {
+ let lastPoll = process.hrtime();
+
+ return {
+ getTimeMs: (): number => {
+ const [seconds, nanoSeconds] = process.hrtime(lastPoll);
+ return Math.floor(seconds * 1e3 + nanoSeconds / 1e6);
+ },
+ reset: (): void => {
+ lastPoll = process.hrtime();
+ },
+ };
+}
+
function handleChildProcess(options: Options): void {
function log(message: string): void {
logger.log(`[ANR child process] ${message}`);
@@ -182,7 +196,7 @@ function handleChildProcess(options: Options): void {
}
}
- const { poll } = watchdogTimer(options.pollInterval, options.anrThreshold, watchdogTimeout);
+ const { poll } = watchdogTimer(createHrTimer, options.pollInterval, options.anrThreshold, watchdogTimeout);
process.on('message', () => {
poll();
diff --git a/packages/node/src/requestdata.ts b/packages/node/src/requestdata.ts
index bc07fcf92f8b..e746db088a95 100644
--- a/packages/node/src/requestdata.ts
+++ b/packages/node/src/requestdata.ts
@@ -109,7 +109,9 @@ function extractTransaction(req: PolymorphicRequest, type: boolean | Transaction
}
case 'methodPath':
default: {
- return extractPathForTransaction(req, { path: true, method: true })[0];
+ // if exist _reconstructedRoute return that path instead of route.path
+ const customRoute = req._reconstructedRoute ? req._reconstructedRoute : undefined;
+ return extractPathForTransaction(req, { path: true, method: true, customRoute })[0];
}
}
}
diff --git a/packages/remix/src/utils/instrumentServer.ts b/packages/remix/src/utils/instrumentServer.ts
index a80e795fb9a6..935e24124b49 100644
--- a/packages/remix/src/utils/instrumentServer.ts
+++ b/packages/remix/src/utils/instrumentServer.ts
@@ -259,11 +259,11 @@ function makeWrappedRootLoader(remixVersion: number) {
const traceAndBaggage = getTraceAndBaggage();
if (isDeferredData(res)) {
- return {
- ...res.data,
- ...traceAndBaggage,
- remixVersion,
- };
+ res.data['sentryTrace'] = traceAndBaggage.sentryTrace;
+ res.data['sentryBaggage'] = traceAndBaggage.sentryBaggage;
+ res.data['remixVersion'] = remixVersion;
+
+ return res;
}
if (isResponse(res)) {
diff --git a/packages/remix/test/integration/app_v1/routes/loader-defer-response/$id.tsx b/packages/remix/test/integration/app_v1/routes/loader-defer-response/$id.tsx
new file mode 100644
index 000000000000..f0c19208a85f
--- /dev/null
+++ b/packages/remix/test/integration/app_v1/routes/loader-defer-response/$id.tsx
@@ -0,0 +1,2 @@
+export * from '../../../common/routes/loader-defer-response.$id';
+export { default } from '../../../common/routes/loader-defer-response.$id';
diff --git a/packages/remix/test/integration/app_v1/routes/loader-defer-response/index.tsx b/packages/remix/test/integration/app_v1/routes/loader-defer-response/index.tsx
deleted file mode 100644
index fd3a7b3f898d..000000000000
--- a/packages/remix/test/integration/app_v1/routes/loader-defer-response/index.tsx
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from '../../../common/routes/loader-defer-response';
-export { default } from '../../../common/routes/loader-defer-response';
diff --git a/packages/remix/test/integration/app_v2/routes/loader-defer-response.$id.tsx b/packages/remix/test/integration/app_v2/routes/loader-defer-response.$id.tsx
new file mode 100644
index 000000000000..69499e594ccc
--- /dev/null
+++ b/packages/remix/test/integration/app_v2/routes/loader-defer-response.$id.tsx
@@ -0,0 +1,2 @@
+export * from '../../common/routes/loader-defer-response.$id';
+export { default } from '../../common/routes/loader-defer-response.$id';
diff --git a/packages/remix/test/integration/app_v2/routes/loader-defer-response.tsx b/packages/remix/test/integration/app_v2/routes/loader-defer-response.tsx
deleted file mode 100644
index 38415a9a3781..000000000000
--- a/packages/remix/test/integration/app_v2/routes/loader-defer-response.tsx
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from '../../common/routes/loader-defer-response';
-export { default } from '../../common/routes/loader-defer-response';
diff --git a/packages/remix/test/integration/common/routes/loader-defer-response.tsx b/packages/remix/test/integration/common/routes/loader-defer-response.$id.tsx
similarity index 83%
rename from packages/remix/test/integration/common/routes/loader-defer-response.tsx
rename to packages/remix/test/integration/common/routes/loader-defer-response.$id.tsx
index b55d8dfede47..ad7060888c19 100644
--- a/packages/remix/test/integration/common/routes/loader-defer-response.tsx
+++ b/packages/remix/test/integration/common/routes/loader-defer-response.$id.tsx
@@ -14,7 +14,7 @@ export default function LoaderJSONResponse() {
return (
-
{data && data.id ? data.id : 'Not Found'}
+ {data && data.id ? data.id : 'Not Found'}
);
}
diff --git a/packages/remix/test/integration/test/client/deferred-response.test.ts b/packages/remix/test/integration/test/client/deferred-response.test.ts
new file mode 100644
index 000000000000..c239565428da
--- /dev/null
+++ b/packages/remix/test/integration/test/client/deferred-response.test.ts
@@ -0,0 +1,9 @@
+import { test, expect } from '@playwright/test';
+
+test('should receive correct data from instrumented defer response', async ({ page }) => {
+ await page.goto('/loader-defer-response/98765');
+
+ const renderedId = await page.waitForSelector('#data-render');
+
+ expect(await renderedId?.textContent()).toBe('98765');
+});
diff --git a/packages/remix/test/integration/test/server/loader.test.ts b/packages/remix/test/integration/test/server/loader.test.ts
index c3be005f0c1b..e45c5be33e8c 100644
--- a/packages/remix/test/integration/test/server/loader.test.ts
+++ b/packages/remix/test/integration/test/server/loader.test.ts
@@ -201,12 +201,12 @@ describe.each(['builtin', 'express'])('Remix API Loaders with adapter = %s', ada
it('correctly instruments a deferred loader', async () => {
const env = await RemixTestEnv.init(adapter);
- const url = `${env.url}/loader-defer-response`;
+ const url = `${env.url}/loader-defer-response/123123`;
const envelope = await env.getEnvelopeRequest({ url, envelopeType: 'transaction' });
const transaction = envelope[2];
assertSentryTransaction(transaction, {
- transaction: useV2 ? 'routes/loader-defer-response' : 'root',
+ transaction: useV2 ? 'routes/loader-defer-response.$id' : 'routes/loader-defer-response/$id',
transaction_info: {
source: 'route',
},
@@ -217,11 +217,11 @@ describe.each(['builtin', 'express'])('Remix API Loaders with adapter = %s', ada
op: 'function.remix.loader',
},
{
- description: 'routes/loader-defer-response',
+ description: 'routes/loader-defer-response.$id',
op: 'function.remix.loader',
},
{
- description: 'routes/loader-defer-response',
+ description: 'routes/loader-defer-response.$id',
op: 'function.remix.document_request',
},
]
@@ -231,11 +231,11 @@ describe.each(['builtin', 'express'])('Remix API Loaders with adapter = %s', ada
op: 'function.remix.loader',
},
{
- description: 'routes/loader-defer-response/index',
+ description: 'routes/loader-defer-response/$id',
op: 'function.remix.loader',
},
{
- description: 'root',
+ description: 'routes/loader-defer-response/$id',
op: 'function.remix.document_request',
},
],
diff --git a/packages/replay/.eslintignore b/packages/replay/.eslintignore
index 3a347e0e1879..c76c6c2d64d1 100644
--- a/packages/replay/.eslintignore
+++ b/packages/replay/.eslintignore
@@ -1,3 +1,6 @@
node_modules/
build/
-
+demo/build/
+# TODO: Check if we can re-introduce linting in demo
+demo
+metrics
diff --git a/packages/tracing-internal/src/node/integrations/express.ts b/packages/tracing-internal/src/node/integrations/express.ts
index 047236381269..4327f11c5ea7 100644
--- a/packages/tracing-internal/src/node/integrations/express.ts
+++ b/packages/tracing-internal/src/node/integrations/express.ts
@@ -64,6 +64,8 @@ type Layer = {
handle_request: (req: PatchedRequest, res: ExpressResponse, next: () => void) => void;
route?: { path: RouteType | RouteType[] };
path?: string;
+ regexp?: RegExp;
+ keys?: { name: string; offset: number; optional: boolean }[];
};
type RouteType = string | RegExp;
@@ -318,7 +320,24 @@ function instrumentRouter(appOrRouter: ExpressRouter): void {
}
// Otherwise, the hardcoded path (i.e. a partial route without params) is stored in layer.path
- const partialRoute = layerRoutePath || layer.path || '';
+ let partialRoute;
+
+ if (layerRoutePath) {
+ partialRoute = layerRoutePath;
+ } else {
+ /**
+ * prevent duplicate segment in _reconstructedRoute param if router match multiple routes before final path
+ * example:
+ * original url: /api/v1/1234
+ * prevent: /api/api/v1/:userId
+ * router structure
+ * /api -> middleware
+ * /api/v1 -> middleware
+ * /1234 -> endpoint with param :userId
+ * final _reconstructedRoute is /api/v1/:userId
+ */
+ partialRoute = preventDuplicateSegments(req.originalUrl, req._reconstructedRoute, layer.path) || '';
+ }
// Normalize the partial route so that it doesn't contain leading or trailing slashes
// and exclude empty or '*' wildcard routes.
@@ -370,6 +389,79 @@ type LayerRoutePathInfo = {
numExtraSegments: number;
};
+/**
+ * Recreate layer.route.path from layer.regexp and layer.keys.
+ * Works until express.js used package path-to-regexp@0.1.7
+ * or until layer.keys contain offset attribute
+ *
+ * @param layer the layer to extract the stringified route from
+ *
+ * @returns string in layer.route.path structure 'router/:pathParam' or undefined
+ */
+export const extractOriginalRoute = (
+ path?: Layer['path'],
+ regexp?: Layer['regexp'],
+ keys?: Layer['keys'],
+): string | undefined => {
+ if (!path || !regexp || !keys || Object.keys(keys).length === 0 || !keys[0]?.offset) {
+ return undefined;
+ }
+
+ const orderedKeys = keys.sort((a, b) => a.offset - b.offset);
+
+ // add d flag for getting indices from regexp result
+ const pathRegex = new RegExp(regexp, `${regexp.flags}d`);
+ /**
+ * use custom type cause of TS error with missing indices in RegExpExecArray
+ */
+ const execResult = pathRegex.exec(path) as (RegExpExecArray & { indices: [number, number][] }) | null;
+
+ if (!execResult || !execResult.indices) {
+ return undefined;
+ }
+ /**
+ * remove first match from regex cause contain whole layer.path
+ */
+ const [, ...paramIndices] = execResult.indices;
+
+ if (paramIndices.length !== orderedKeys.length) {
+ return undefined;
+ }
+ let resultPath = path;
+ let indexShift = 0;
+
+ /**
+ * iterate param matches from regexp.exec
+ */
+ paramIndices.forEach(([startOffset, endOffset], index: number) => {
+ /**
+ * isolate part before param
+ */
+ const substr1 = resultPath.substring(0, startOffset - indexShift);
+ /**
+ * define paramName as replacement in format :pathParam
+ */
+ const replacement = `:${orderedKeys[index].name}`;
+
+ /**
+ * isolate part after param
+ */
+ const substr2 = resultPath.substring(endOffset - indexShift);
+
+ /**
+ * recreate original path but with param replacement
+ */
+ resultPath = substr1 + replacement + substr2;
+
+ /**
+ * calculate new index shift after resultPath was modified
+ */
+ indexShift = indexShift + (endOffset - startOffset - replacement.length);
+ });
+
+ return resultPath;
+};
+
/**
* Extracts and stringifies the layer's route which can either be a string with parameters (`users/:id`),
* a RegEx (`/test/`) or an array of strings and regexes (`['/path1', /\/path[2-5]/, /path/:id]`). Additionally
@@ -382,11 +474,24 @@ type LayerRoutePathInfo = {
* if the route was an array (defaults to 0).
*/
function getLayerRoutePathInfo(layer: Layer): LayerRoutePathInfo {
- const lrp = layer.route?.path;
+ let lrp = layer.route?.path;
const isRegex = isRegExp(lrp);
const isArray = Array.isArray(lrp);
+ if (!lrp) {
+ // parse node.js major version
+ const [major] = process.versions.node.split('.').map(Number);
+
+ // allow call extractOriginalRoute only if node version support Regex d flag, node 16+
+ if (major >= 16) {
+ /**
+ * If lrp does not exist try to recreate original layer path from route regexp
+ */
+ lrp = extractOriginalRoute(layer.path, layer.regexp, layer.keys);
+ }
+ }
+
if (!lrp) {
return { isRegex, isArray, numExtraSegments: 0 };
}
@@ -424,3 +529,28 @@ function getLayerRoutePathString(isArray: boolean, lrp?: RouteType | RouteType[]
}
return lrp && lrp.toString();
}
+
+/**
+ * remove duplicate segment contain in layerPath against reconstructedRoute,
+ * and return only unique segment that can be added into reconstructedRoute
+ */
+export function preventDuplicateSegments(
+ originalUrl?: string,
+ reconstructedRoute?: string,
+ layerPath?: string,
+): string | undefined {
+ const originalUrlSplit = originalUrl?.split('/').filter(v => !!v);
+ let tempCounter = 0;
+ const currentOffset = reconstructedRoute?.split('/').filter(v => !!v).length || 0;
+ const result = layerPath
+ ?.split('/')
+ .filter(segment => {
+ if (originalUrlSplit?.[currentOffset + tempCounter] === segment) {
+ tempCounter += 1;
+ return true;
+ }
+ return false;
+ })
+ .join('/');
+ return result;
+}
diff --git a/packages/tracing-internal/test/node/express.test.ts b/packages/tracing-internal/test/node/express.test.ts
new file mode 100644
index 000000000000..e9f4df236b33
--- /dev/null
+++ b/packages/tracing-internal/test/node/express.test.ts
@@ -0,0 +1,91 @@
+import { extractOriginalRoute, preventDuplicateSegments } from '../../src/node/integrations/express';
+
+/**
+ * prevent duplicate segment in _reconstructedRoute param if router match multiple routes before final path
+ * example:
+ * original url: /api/v1/1234
+ * prevent: /api/api/v1/:userId
+ * router structure
+ * /api -> middleware
+ * /api/v1 -> middleware
+ * /1234 -> endpoint with param :userId
+ * final _reconstructedRoute is /api/v1/:userId
+ */
+describe('unit Test for preventDuplicateSegments', () => {
+ it('should return api segment', () => {
+ const originalUrl = '/api/v1/1234';
+ const reconstructedRoute = '';
+ const layerPath = '/api';
+ const result = preventDuplicateSegments(originalUrl, reconstructedRoute, layerPath);
+ expect(result).toBe('api');
+ });
+
+ it('should prevent duplicate segment api', () => {
+ const originalUrl = '/api/v1/1234';
+ const reconstructedRoute = '/api';
+ const layerPath = '/api/v1';
+ const result = preventDuplicateSegments(originalUrl, reconstructedRoute, layerPath);
+ expect(result).toBe('v1');
+ });
+
+ it('should prevent duplicate segment v1', () => {
+ const originalUrl = '/api/v1/1234';
+ const reconstructedRoute = '/api/v1';
+ const layerPath = '/v1/1234';
+ const result1 = preventDuplicateSegments(originalUrl, reconstructedRoute, layerPath);
+ expect(result1).toBe('1234');
+ });
+});
+describe('preventDuplicateSegments should handle empty input gracefully', () => {
+ it('Empty input values', () => {
+ expect(preventDuplicateSegments()).toBeUndefined();
+ });
+
+ it('Empty originalUrl', () => {
+ expect(preventDuplicateSegments('', '/api/v1/1234', '/api/api/v1/1234')).toBe('');
+ });
+
+ it('Empty reconstructedRoute', () => {
+ expect(preventDuplicateSegments('/api/v1/1234', '', '/api/api/v1/1234')).toBe('api/v1/1234');
+ });
+
+ it('Empty layerPath', () => {
+ expect(preventDuplicateSegments('/api/v1/1234', '/api/v1/1234', '')).toBe('');
+ });
+});
+
+// parse node.js major version
+const [major] = process.versions.node.split('.').map(Number);
+// Test this funciton only if node is 16+ because regex d flag is support from node 16+
+if (major >= 16) {
+ describe('extractOriginalRoute', () => {
+ it('should return undefined if path, regexp, or keys are missing', () => {
+ expect(extractOriginalRoute('/example')).toBeUndefined();
+ expect(extractOriginalRoute('/example', /test/)).toBeUndefined();
+ });
+
+ it('should return undefined if keys do not contain an offset property', () => {
+ const path = '/example';
+ const regex = /example/;
+ const key = { name: 'param1', offset: 0, optional: false };
+ expect(extractOriginalRoute(path, regex, [key])).toBeUndefined();
+ });
+
+ it('should return the original route path when valid inputs are provided', () => {
+ const path = '/router/123';
+ const regex = /^\/router\/(\d+)$/;
+ const keys = [{ name: 'pathParam', offset: 8, optional: false }];
+ expect(extractOriginalRoute(path, regex, keys)).toBe('/router/:pathParam');
+ });
+
+ it('should handle multiple parameters in the route', () => {
+ const path = '/user/42/profile/username';
+ const regex = /^\/user\/(\d+)\/profile\/(\w+)$/;
+ const keys = [
+ { name: 'userId', offset: 6, optional: false },
+ { name: 'username', offset: 17, optional: false },
+ ];
+ expect(extractOriginalRoute(path, regex, keys)).toBe('/user/:userId/profile/:username');
+ });
+ });
+}
diff --git a/packages/types/src/polymorphics.ts b/packages/types/src/polymorphics.ts
index e6fcac8a682f..88abf7770b2c 100644
--- a/packages/types/src/polymorphics.ts
+++ b/packages/types/src/polymorphics.ts
@@ -75,4 +75,5 @@ type ExpressRequest = NodeRequest & {
user?: {
[key: string]: any;
};
+ _reconstructedRoute?: string;
};
diff --git a/packages/utils/src/anr.ts b/packages/utils/src/anr.ts
index b007c1355cb7..89990c3414f7 100644
--- a/packages/utils/src/anr.ts
+++ b/packages/utils/src/anr.ts
@@ -10,6 +10,8 @@ type WatchdogReturn = {
enabled: (state: boolean) => void;
};
+type CreateTimerImpl = () => { getTimeMs: () => number; reset: () => void };
+
/**
* A node.js watchdog timer
* @param pollInterval The interval that we expect to get polled at
@@ -17,14 +19,18 @@ type WatchdogReturn = {
* @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();
+export function watchdogTimer(
+ createTimer: CreateTimerImpl,
+ pollInterval: number,
+ anrThreshold: number,
+ callback: () => void,
+): WatchdogReturn {
+ const timer = createTimer();
let triggered = false;
let enabled = true;
setInterval(() => {
- const [seconds, nanoSeconds] = process.hrtime(lastPoll);
- const diffMs = Math.floor(seconds * 1e3 + nanoSeconds / 1e6);
+ const diffMs = timer.getTimeMs();
if (triggered === false && diffMs > pollInterval + anrThreshold) {
triggered = true;
@@ -40,7 +46,7 @@ export function watchdogTimer(pollInterval: number, anrThreshold: number, callba
return {
poll: () => {
- lastPoll = process.hrtime();
+ timer.reset();
},
enabled: (state: boolean) => {
enabled = state;
diff --git a/scripts/node-unit-tests.ts b/scripts/node-unit-tests.ts
index 8824cee77d66..fc0f855fd5d4 100644
--- a/scripts/node-unit-tests.ts
+++ b/scripts/node-unit-tests.ts
@@ -20,6 +20,7 @@ const DEFAULT_SKIP_TESTS_PACKAGES = [
'@sentry/angular',
'@sentry/svelte',
'@sentry/replay',
+ '@sentry-internal/feedback',
'@sentry/wasm',
'@sentry/bun',
'@sentry/deno',
diff --git a/yarn.lock b/yarn.lock
index f64756e09026..e4e1855b1d18 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -759,7 +759,7 @@
jsesc "^2.5.1"
source-map "^0.5.0"
-"@babel/generator@^7.11.0", "@babel/generator@^7.20.1", "@babel/generator@^7.20.2", "@babel/generator@^7.7.2":
+"@babel/generator@^7.11.0", "@babel/generator@^7.20.2", "@babel/generator@^7.7.2":
version "7.20.4"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.4.tgz#4d9f8f0c30be75fd90a0562099a26e5839602ab8"
integrity sha512-luCf7yk/cm7yab6CAW1aiFnmEfBJplb/JojV56MYEK7ziWfGmFlTfmL9Ehwfy4gFhbjBfWO1wj7/TuSbVNEEtA==
@@ -1232,12 +1232,12 @@
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":
+"@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.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"
integrity sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==
-"@babel/parser@^7.14.5", "@babel/parser@^7.14.8", "@babel/parser@^7.20.13", "@babel/parser@^7.20.7":
+"@babel/parser@^7.14.5", "@babel/parser@^7.14.8", "@babel/parser@^7.20.7":
version "7.20.15"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.15.tgz#eec9f36d8eaf0948bb88c87a46784b5ee9fd0c89"
integrity sha512-DI4a1oZuf8wC+oAJA9RW6ga3Zbe8RZFt7kD9i4qAspz3I/yHet1VvC3DiSy/fsUvv5pvJuNPh0LPOdCcqinDPg==
@@ -2411,42 +2411,10 @@
"@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"
- integrity sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==
- dependencies:
- "@babel/code-frame" "^7.18.6"
- "@babel/generator" "^7.20.1"
- "@babel/helper-environment-visitor" "^7.18.9"
- "@babel/helper-function-name" "^7.19.0"
- "@babel/helper-hoist-variables" "^7.18.6"
- "@babel/helper-split-export-declaration" "^7.18.6"
- "@babel/parser" "^7.20.1"
- "@babel/types" "^7.20.0"
- debug "^4.1.0"
- globals "^11.1.0"
-
-"@babel/traverse@^7.14.8", "@babel/traverse@^7.20.10", "@babel/traverse@^7.20.12", "@babel/traverse@^7.20.13", "@babel/traverse@^7.20.7":
- version "7.20.13"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.13.tgz#817c1ba13d11accca89478bd5481b2d168d07473"
- integrity sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ==
- dependencies:
- "@babel/code-frame" "^7.18.6"
- "@babel/generator" "^7.20.7"
- "@babel/helper-environment-visitor" "^7.18.9"
- "@babel/helper-function-name" "^7.19.0"
- "@babel/helper-hoist-variables" "^7.18.6"
- "@babel/helper-split-export-declaration" "^7.18.6"
- "@babel/parser" "^7.20.13"
- "@babel/types" "^7.20.7"
- 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==
+"@babel/traverse@^7.11.0", "@babel/traverse@^7.13.0", "@babel/traverse@^7.14.8", "@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.20.1", "@babel/traverse@^7.20.10", "@babel/traverse@^7.20.12", "@babel/traverse@^7.20.13", "@babel/traverse@^7.20.7", "@babel/traverse@^7.22.10", "@babel/traverse@^7.23.0", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2":
+ version "7.23.2"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8"
+ integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==
dependencies:
"@babel/code-frame" "^7.22.13"
"@babel/generator" "^7.23.0"
@@ -3076,6 +3044,11 @@
minimatch "^3.0.4"
strip-json-comments "^3.1.1"
+"@fastify/busboy@^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.0.0.tgz#f22824caff3ae506b18207bad4126dbc6ccdb6b8"
+ integrity sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==
+
"@gar/promisify@^1.0.1", "@gar/promisify@^1.1.3":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
@@ -4620,11 +4593,6 @@
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1"
integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==
-"@polka/url@^1.0.0-next.9":
- version "1.0.0-next.12"
- resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.12.tgz#431ec342a7195622f86688bbda82e3166ce8cb28"
- integrity sha512-6RglhutqrGFMO1MNUXp95RBuYIuc8wTnMAV5MUhLmjTOy78ncwOw7RgeQ/HeymkKXRhZd0s2DNrM1rL7unk3MQ==
-
"@prisma/client@3.15.2":
version "3.15.2"
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-3.15.2.tgz#2181398147afc79bfe0d83c03a88dc45b49bd365"
@@ -5084,6 +5052,28 @@
proxy-from-env "^1.1.0"
which "^2.0.2"
+"@sentry/core@7.70.0":
+ version "7.70.0"
+ resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.70.0.tgz#c481ef27cf05293fb681ee4ff4d4b0b1e8664bb5"
+ integrity sha512-voUsGVM+jwRp99AQYFnRvr7sVd2tUhIMj1L6F42LtD3vp7t5ZnKp3NpXagtFW2vWzXESfyJUBhM0qI/bFvn7ZA==
+ dependencies:
+ "@sentry/types" "7.70.0"
+ "@sentry/utils" "7.70.0"
+ tslib "^2.4.1 || ^1.9.3"
+
+"@sentry/types@7.70.0":
+ version "7.70.0"
+ resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.70.0.tgz#c7b533bb18144e3b020550b38cf4812c32d05ffe"
+ integrity sha512-rY4DqpiDBtXSk4MDNBH3dwWqfPbNBI/9GA7Y5WJSIcObBtfBKp0fzYliHJZD0pgM7d4DPFrDn42K9Iiumgymkw==
+
+"@sentry/utils@7.70.0":
+ version "7.70.0"
+ resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.70.0.tgz#825387ceb10cbb1e145357394b697a1a6d60eb74"
+ integrity sha512-0cChMH0lsGp+5I3D4wOHWwjFN19HVrGUs7iWTLTO5St3EaVbdeLbI1vFXHxMxvopbwgpeZafbreHw/loIdZKpw==
+ dependencies:
+ "@sentry/types" "7.70.0"
+ tslib "^2.4.1 || ^1.9.3"
+
"@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"
@@ -5219,37 +5209,29 @@
resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5"
integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==
-"@size-limit/file@4.10.1":
- version "4.10.1"
- resolved "https://registry.yarnpkg.com/@size-limit/file/-/file-4.10.1.tgz#e5cb204edf4974b1cad6a6eb0b639dcf6b48091b"
- integrity sha512-3+h+CfrbIeTodPNpjJgs+jQoXYhAIAuK3HN9j5hdV9H6Q84hshbdn/TMYMIQuVQm9JodPk6l2G1wDd6LqweKHQ==
+"@size-limit/esbuild@9.0.0":
+ version "9.0.0"
+ resolved "https://registry.yarnpkg.com/@size-limit/esbuild/-/esbuild-9.0.0.tgz#08e0138d01e8a693e0d7ed274ac36b4065afe814"
+ integrity sha512-y3NY0inaFeLqV6SRXNVILhawQdQcODxF30qft6OalsrqtQtBjt++6ZeahYhUbrVexUEWRh6c7yPCe8RvHn8hlA==
dependencies:
- semver "7.3.4"
+ esbuild "^0.19.2"
+ nanoid "^3.3.6"
-"@size-limit/preset-small-lib@^4.5.5":
- version "4.10.1"
- resolved "https://registry.yarnpkg.com/@size-limit/preset-small-lib/-/preset-small-lib-4.10.1.tgz#ad154b5760410c089816245490743a74e8300608"
- integrity sha512-a1hUwbKiPy5INmWXiJFUUy6N7vFHGNecAkrwCxQbsjRn3rkA2qqt5JHJRpzFCBpynQBR1qA5OtQNd2fN2VKB/Q==
+"@size-limit/file@9.0.0":
+ version "9.0.0"
+ resolved "https://registry.yarnpkg.com/@size-limit/file/-/file-9.0.0.tgz#eed5415f5bcc8407979e47ffa49ffaf12d2d2378"
+ integrity sha512-oM2UaH2FRq4q22k+R+P6xCpzET10T94LFdSjb9svVu/vOD7NaB9LGcG6se8TW1BExXiyXO4GEhLsBt3uMKM3qA==
dependencies:
- "@size-limit/file" "4.10.1"
- "@size-limit/webpack" "4.10.1"
+ semver "7.5.4"
-"@size-limit/webpack@4.10.1":
- version "4.10.1"
- resolved "https://registry.yarnpkg.com/@size-limit/webpack/-/webpack-4.10.1.tgz#c1d8cc7f7e387b76944485c8416cf0a11894cccc"
- integrity sha512-BZMl/FrJVgKuXen9gGf+5MspDzvAQWl+cEDYdzvf7JQb7e/oiH89ACLQJsWY1Q4f+j0N9U5XlDMHdsHVdNdp5w==
+"@size-limit/preset-small-lib@~9.0.0":
+ version "9.0.0"
+ resolved "https://registry.yarnpkg.com/@size-limit/preset-small-lib/-/preset-small-lib-9.0.0.tgz#cbac7f3460fb4fac935d0f39a5757864f627f4e2"
+ integrity sha512-nkbZxn12pTpABYVyX5nsjQuLFpn8wDmd2XKoq/MiqKOc3ocz5BBwXTruqTL5ZKDW1OxEAWZMQlxf2kg3kY3X1Q==
dependencies:
- css-loader "^5.1.1"
- escape-string-regexp "^4.0.0"
- file-loader "^6.2.0"
- mkdirp "^1.0.4"
- nanoid "^3.1.20"
- optimize-css-assets-webpack-plugin "^5.0.4"
- pnp-webpack-plugin "^1.6.4"
- rimraf "^3.0.2"
- style-loader "^2.0.0"
- webpack "^4.44.1"
- webpack-bundle-analyzer "^4.4.0"
+ "@size-limit/esbuild" "9.0.0"
+ "@size-limit/file" "9.0.0"
+ size-limit "9.0.0"
"@socket.io/base64-arraybuffer@~1.0.2":
version "1.0.2"
@@ -6206,7 +6188,7 @@
dependencies:
"@types/node" "*"
-"@types/resolve@^1.17.0":
+"@types/resolve@1.20.3", "@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==
@@ -7124,7 +7106,7 @@ acorn-walk@^7.1.1:
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
-acorn-walk@^8.0.0, acorn-walk@^8.1.1, acorn-walk@^8.2.0:
+acorn-walk@^8.1.1, acorn-walk@^8.2.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
@@ -7144,16 +7126,16 @@ acorn@^7.1.1, acorn@^7.4.0:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
-acorn@^8.0.4, acorn@^8.2.4, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.0, acorn@^8.7.1:
- version "8.8.0"
- 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==
+acorn@^8.2.4, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.0, acorn@^8.7.1:
+ version "8.8.0"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
+ integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
+
add-stream@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa"
@@ -9961,6 +9943,11 @@ byte-size@8.1.1:
resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-8.1.1.tgz#3424608c62d59de5bfda05d31e0313c6174842ae"
integrity sha512-tUkzZWK0M/qdoLEqikxBWe4kumyuwjl3HO6zHTr4yEI23EojPtLYXdG1+AQY7MN0cGyNDvEaJ8wiYQm6P2bPxg==
+bytes-iec@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/bytes-iec/-/bytes-iec-3.1.1.tgz#94cd36bf95c2c22a82002c247df8772d1d591083"
+ integrity sha512-fey6+4jDK7TFtFg/klGSvNKJctyU7n2aQdnM+CO0ruLPbqqMOM8Tio0Pc+deqUeVKX1tL5DQep1zQ7+37aTAsA==
+
bytes@1:
version "1.0.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-1.0.0.tgz#3569ede8ba34315fab99c3e92cb04c7220de1fa8"
@@ -9976,7 +9963,7 @@ bytes@3.1.0:
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
-bytes@3.1.2, bytes@^3.1.0:
+bytes@3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
@@ -10529,11 +10516,6 @@ ci-info@^3.8.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"
- integrity sha512-CLOGsVDrVamzv8sXJGaILUVI6dsuAkouJP/n6t+OxLPeeA4DDby7zn9SB6EUpa1H7oIKoE+rMmkW80zYsFfUjA==
-
cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
@@ -10960,7 +10942,7 @@ commander@^4.0.0, commander@^4.1.1:
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
-commander@^6.0.0, commander@^6.2.0, commander@^6.2.1:
+commander@^6.0.0, commander@^6.2.1:
version "6.2.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
@@ -11572,7 +11554,7 @@ css-loader@6.2.0:
postcss-value-parser "^4.1.0"
semver "^7.3.5"
-css-loader@^5.1.1, css-loader@^5.2.0:
+css-loader@^5.2.0:
version "5.2.7"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.2.7.tgz#9b9f111edf6fb2be5dc62525644cbc9c232064ae"
integrity sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg==
@@ -11832,7 +11814,7 @@ cssnano-utils@^3.1.0:
resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861"
integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==
-cssnano@4.1.10, cssnano@^4.1.10:
+cssnano@4.1.10:
version "4.1.10"
resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.10.tgz#0ac41f0b13d13d465487e111b778d42da631b8b2"
integrity sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==
@@ -12679,7 +12661,7 @@ duplexer3@^0.1.4:
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
-duplexer@^0.1.1, duplexer@^0.1.2, duplexer@~0.1.1:
+duplexer@^0.1.1, duplexer@~0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6"
integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==
@@ -14868,14 +14850,6 @@ file-loader@6.0.0:
loader-utils "^2.0.0"
schema-utils "^2.6.5"
-file-loader@^6.2.0:
- version "6.2.0"
- resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d"
- integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==
- dependencies:
- loader-utils "^2.0.0"
- schema-utils "^3.0.0"
-
file-uri-to-path@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
@@ -15999,7 +15973,7 @@ globby@10.0.0:
merge2 "^1.2.3"
slash "^3.0.0"
-globby@11.1.0, globby@^11.0.1, globby@^11.0.2, globby@^11.0.3, globby@^11.1.0:
+globby@11.1.0, globby@^11.0.1, globby@^11.0.3, globby@^11.1.0:
version "11.1.0"
resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
@@ -16207,13 +16181,6 @@ gud@^1.0.0:
resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0"
integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==
-gzip-size@^6.0.0:
- version "6.0.0"
- resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462"
- integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==
- dependencies:
- duplexer "^0.1.2"
-
handle-thing@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e"
@@ -19225,14 +19192,6 @@ language-tags@^1.0.5:
dependencies:
language-subtag-registry "~0.3.2"
-last-call-webpack-plugin@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz#9742df0e10e3cf46e5c0381c2de90d3a7a2d7555"
- integrity sha512-7KI2l2GIZa9p2spzPIVZBYyNKkN+e/SQPpnjlTiPhdbDW3F86tdKKELxKpzJ5sgU19wQWsACULZmpTPYHeWO5w==
- dependencies:
- lodash "^4.17.5"
- webpack-sources "^1.1.0"
-
latest-version@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face"
@@ -19469,16 +19428,16 @@ lie@3.1.1:
dependencies:
immediate "~3.0.5"
-lilconfig@^2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.2.tgz#9f802752254697d22a5c33e88d97b7329008c060"
- integrity sha512-4zUThttj8TQ4N7Pps92Z79jPf1OMcll4m61pivQSVk5MT78hVhNa2LrKTuNYD0AGLpmpf7zeIKOxSt6hHBfypw==
-
lilconfig@^2.0.3:
version "2.0.6"
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.6.tgz#32a384558bd58af3d4c6e077dd1ad1d397bc69d4"
integrity sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==
+lilconfig@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52"
+ integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==
+
line-column@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/line-column/-/line-column-1.0.2.tgz#d25af2936b6f4849172b312e4792d1d987bc34a2"
@@ -19876,7 +19835,7 @@ lodash.uniqby@^4.7.0:
resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302"
integrity sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI=
-lodash@4.17.21, lodash@^4.17.0, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.7.0:
+lodash@4.17.21, lodash@^4.17.0, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -21560,7 +21519,7 @@ nan@^2.12.1:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==
-nanoid@^3.1.16, nanoid@^3.1.20, nanoid@^3.1.23:
+nanoid@^3.1.16, nanoid@^3.1.23:
version "3.3.4"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
@@ -21587,6 +21546,13 @@ nanomatch@^1.2.9:
snapdragon "^0.8.1"
to-regex "^3.0.1"
+nanospinner@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/nanospinner/-/nanospinner-1.1.0.tgz#d17ff621cb1784b0a206b400da88a0ef6db39b97"
+ integrity sha512-yFvNYMig4AthKYfHFl1sLj7B2nkHL4lzdig4osvl9/LdGbXwrdFRoqBS98gsEsOakr0yH+r5NZ/1Y9gdVB8trA==
+ dependencies:
+ picocolors "^1.0.0"
+
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"
@@ -22761,11 +22727,6 @@ open@^8.4.0:
is-docker "^2.1.1"
is-wsl "^2.2.0"
-opener@^1.5.2:
- version "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"
@@ -22782,14 +22743,6 @@ opn@^5.5.0:
dependencies:
is-wsl "^1.1.0"
-optimize-css-assets-webpack-plugin@^5.0.4:
- version "5.0.4"
- resolved "https://registry.yarnpkg.com/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.4.tgz#85883c6528aaa02e30bbad9908c92926bb52dc90"
- integrity sha512-wqd6FdI2a5/FdoiCNNkEvLeA//lHHfG24Ln2Xm2qqdIk4aOlsR18jwpyOihqQ8849W3qu2DX8fOYxpvTMj+93A==
- dependencies:
- cssnano "^4.1.10"
- last-call-webpack-plugin "^3.0.0"
-
optional-require@^1.1.8:
version "1.1.8"
resolved "https://registry.yarnpkg.com/optional-require/-/optional-require-1.1.8.tgz#16364d76261b75d964c482b2406cb824d8ec44b7"
@@ -23776,7 +23729,7 @@ pluralize@^8.0.0:
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1"
integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==
-pnp-webpack-plugin@1.6.4, pnp-webpack-plugin@^1.6.4:
+pnp-webpack-plugin@1.6.4:
version "1.6.4"
resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149"
integrity sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg==
@@ -26364,6 +26317,15 @@ resolve@1.20.0:
is-core-module "^2.2.0"
path-parse "^1.0.6"
+resolve@1.22.8:
+ version "1.22.8"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
+ integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
+ dependencies:
+ is-core-module "^2.13.0"
+ path-parse "^1.0.7"
+ supports-preserve-symlinks-flag "^1.0.0"
+
resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.11.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0, resolve@^1.22.1, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.8.1:
version "1.22.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
@@ -27013,13 +26975,6 @@ semver@7.3.2:
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==
-semver@7.3.4:
- version "7.3.4"
- resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97"
- integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==
- dependencies:
- lru-cache "^6.0.0"
-
semver@7.3.5:
version "7.3.5"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
@@ -27034,7 +26989,7 @@ 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.5.4:
+semver@7.5.4, 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==
@@ -27345,15 +27300,6 @@ sinon@^7.3.2:
nise "^1.5.2"
supports-color "^5.5.0"
-sirv@^1.0.7:
- version "1.0.11"
- resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.11.tgz#81c19a29202048507d6ec0d8ba8910fda52eb5a4"
- integrity sha512-SR36i3/LSWja7AJNRBz4fF/Xjpn7lQFI30tZ434dIy+bitLYSP+ZEenHg36i23V2SGEz+kqjksg0uOGZ5LPiqg==
- dependencies:
- "@polka/url" "^1.0.0-next.9"
- mime "^2.3.1"
- totalist "^1.0.0"
-
sirv@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.2.tgz#128b9a628d77568139cff85703ad5497c46a4760"
@@ -27368,19 +27314,17 @@ sisteransi@^1.0.5:
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
-size-limit@^4.5.5:
- version "4.10.1"
- resolved "https://registry.yarnpkg.com/size-limit/-/size-limit-4.10.1.tgz#b34e5047e1f08bb2941045b40d13739b2b9e5369"
- integrity sha512-PEnh88PBmG/Kp6U/IR64Judu/smaq33lEDXf0sJiF1+tHwyrrSEWQkraFLbWKUI6cxT0weXuaTlh1sKYY7EK9Q==
+size-limit@9.0.0, size-limit@~9.0.0:
+ version "9.0.0"
+ resolved "https://registry.yarnpkg.com/size-limit/-/size-limit-9.0.0.tgz#203c47303462a8351976eb26175acea5f4e80447"
+ integrity sha512-DrA7o2DeRN3s+vwCA9nn7Ck9Y4pn9t0GNUwQRpKqBtBmNkl6LA2s/NlNCdtKHrEkRTeYA1ZQ65mnYveo9rUqgA==
dependencies:
- bytes "^3.1.0"
- chokidar "^3.5.1"
- ci-job-number "^1.2.2"
- colorette "^1.2.2"
- globby "^11.0.2"
- lilconfig "^2.0.2"
- ora "^5.3.0"
- read-pkg-up "^7.0.1"
+ bytes-iec "^3.1.1"
+ chokidar "^3.5.3"
+ globby "^11.1.0"
+ lilconfig "^2.1.0"
+ nanospinner "^1.1.0"
+ picocolors "^1.0.0"
skip-regex@^1.0.2:
version "1.0.2"
@@ -29252,11 +29196,6 @@ toidentifier@1.0.1:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
-totalist@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df"
- integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==
-
totalist@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.0.tgz#4ef9c58c5f095255cdc3ff2a0a55091c57a3a1bd"
@@ -29745,11 +29684,11 @@ undici@5.20.0:
busboy "^1.6.0"
undici@^5.21.0:
- version "5.21.0"
- resolved "https://registry.yarnpkg.com/undici/-/undici-5.21.0.tgz#b00dfc381f202565ab7f52023222ab862bb2494f"
- integrity sha512-HOjK8l6a57b2ZGXOcUsI5NLfoTrfmbOl90ixJDl0AEFG4wgHNDQxtZy15/ZQp7HhjkpaGlp/eneMgtsu1dIlUA==
+ version "5.26.2"
+ resolved "https://registry.yarnpkg.com/undici/-/undici-5.26.2.tgz#fa61bfe40f732540d15e58b0c1271872d8e3c995"
+ integrity sha512-a4PDLQgLTPHVzOK+x3F79/M4GtyYPl+aX9AAK7aQxpwxDwCqkeZCScy7Gk5kWT3JtdFq1uhO3uZJdLtHI4dK9A==
dependencies:
- busboy "^1.6.0"
+ "@fastify/busboy" "^2.0.0"
unherit@^3.0.0:
version "3.0.1"
@@ -30686,21 +30625,6 @@ webidl-conversions@^7.0.0:
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a"
integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==
-webpack-bundle-analyzer@^4.4.0:
- version "4.4.0"
- resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.4.0.tgz#74013106e7e2b07cbd64f3a5ae847f7e814802c7"
- integrity sha512-9DhNa+aXpqdHk8LkLPTBU/dMfl84Y+WE2+KnfI6rSpNRNVKa0VGLjPd2pjFubDeqnWmulFggxmWBxhfJXZnR0g==
- dependencies:
- acorn "^8.0.4"
- acorn-walk "^8.0.0"
- chalk "^4.1.0"
- commander "^6.2.0"
- gzip-size "^6.0.0"
- lodash "^4.17.20"
- opener "^1.5.2"
- sirv "^1.0.7"
- ws "^7.3.1"
-
webpack-dev-middleware@3.7.2:
version "3.7.2"
resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz#0019c3db716e3fa5cecbf64f2ab88a74bab331f3"
@@ -30927,35 +30851,6 @@ webpack@5.50.0:
watchpack "^2.2.0"
webpack-sources "^3.2.0"
-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==
- 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@^4.47.0:
version "4.47.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.47.0.tgz#8b8a02152d7076aeb03b61b47dad2eeed9810ebc"
@@ -31400,7 +31295,7 @@ ws@^6.2.1:
dependencies:
async-limiter "~1.0.0"
-ws@^7.3.1, ws@^7.4.6:
+ws@^7.4.6:
version "7.5.6"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b"
integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==