diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 66bf558f473a..c331823234af 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,6 +40,15 @@ env: BUILD_CACHE_KEY: ${{ github.event.inputs.commit || github.sha }} + # GH will use the first restore-key it finds that matches + # So it will start by looking for one from the same branch, else take the newest one it can find elsewhere + # We want to prefer the cache from the current master branch, if we don't find any on the current branch + NX_CACHE_RESTORE_KEYS: | + nx-Linux-${{ github.ref }}-${{ github.event.inputs.commit || github.sha }} + nx-Linux-${{ github.ref }} + nx-Linux-refs/heads/master + nx-Linux + jobs: job_get_metadata: name: Get Metadata @@ -120,6 +129,11 @@ jobs: changed_browser: ${{ steps.changed.outputs.browser }} changed_browser_integration: ${{ steps.changed.outputs.browser_integration }} changed_any_code: ${{ steps.changed.outputs.any_code }} + # Note: These next three have to be checked as strings ('true'/'false')! + is_master: ${{ github.ref == 'refs/heads/master' }} + is_release: ${{ startsWith(github.ref, 'refs/heads/release/') }} + force_skip_cache: + ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'ci-skip-cache') }} job_install_deps: name: Install Dependencies @@ -139,14 +153,19 @@ jobs: - name: Compute dependency cache key id: compute_lockfile_hash run: echo "hash=${{ hashFiles('yarn.lock') }}" >> "$GITHUB_OUTPUT" + + # When the `ci-skip-cache` label is added to a PR, we always want to skip dependency cache - name: Check dependency cache uses: actions/cache@v3 id: cache_dependencies + if: needs.job_get_metadata.outputs.force_skip_cache == 'false' with: path: ${{ env.CACHED_DEPENDENCY_PATHS }} key: ${{ steps.compute_lockfile_hash.outputs.hash }} + - name: Install dependencies - if: steps.cache_dependencies.outputs.cache-hit == '' + if: + steps.cache_dependencies.outputs.cache-hit == '' || needs.job_get_metadata.outputs.force_skip_cache == 'true' run: yarn install --ignore-engines --frozen-lockfile outputs: dependency_cache_key: ${{ steps.compute_lockfile_hash.outputs.hash }} @@ -168,12 +187,31 @@ jobs: with: path: ${{ env.CACHED_DEPENDENCY_PATHS }} key: ${{ needs.job_install_deps.outputs.dependency_cache_key }} + - name: Check build cache uses: actions/cache@v3 id: cache_built_packages + if: needs.job_get_metadata.outputs.force_skip_cache == 'false' with: path: ${{ env.CACHED_BUILD_PATHS }} key: ${{ env.BUILD_CACHE_KEY }} + + - name: NX cache + uses: actions/cache@v3 + # Disable cache when: + # - on master + # - on release branches + # - when PR has `ci-skip-cache` label + if: | + needs.job_get_metadata.outputs.is_release == 'false' && + needs.job_get_metadata.outputs.force_skip_cache == 'false' + with: + path: node_modules/.cache/nx + key: nx-Linux-${{ github.ref }}-${{ env.HEAD_COMMIT }} + # On master branch, we want to _store_ the cache (so it can be used by other branches), but never _restore_ from it + restore-keys: + ${{needs.job_get_metadata.outputs.is_master == 'false' && env.NX_CACHE_RESTORE_KEYS || 'nx-never-restore'}} + - name: Build packages # Under normal circumstances, using the git SHA as a cache key, there shouldn't ever be a cache hit on the built # packages, and so `yarn build` should always run. This `if` check is therefore only there for testing CI issues @@ -232,7 +270,7 @@ jobs: timeout-minutes: 15 runs-on: ubuntu-20.04 # Size Check will error out outside of the context of a PR - if: github.event_name == 'pull_request' || github.ref == 'refs/heads/master' + if: github.event_name == 'pull_request' || needs.job_get_metadata.outputs.is_master == 'true' steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) uses: actions/checkout@v3 @@ -258,7 +296,7 @@ jobs: - name: Check bundle sizes uses: getsentry/size-limit-action@v5 # Don't run size check on release branches - at that point, we're already committed - if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} + if: needs.job_get_metadata.outputs.is_release == 'false' with: github_token: ${{ secrets.GITHUB_TOKEN }} skip_step: build @@ -320,7 +358,7 @@ jobs: needs: [job_get_metadata, job_build] runs-on: ubuntu-20.04 # Build artifacts are only needed for releasing workflow. - if: startsWith(github.ref, 'refs/heads/release/') + if: needs.job_get_metadata.outputs.is_release == 'true' steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) uses: actions/checkout@v3 @@ -339,7 +377,7 @@ jobs: path: ${{ env.CACHED_BUILD_PATHS }} key: ${{ env.BUILD_CACHE_KEY }} - name: Pack - run: yarn build:npm + run: yarn build:tarball - name: Archive artifacts uses: actions/upload-artifact@v3.1.1 with: @@ -505,6 +543,7 @@ jobs: if: needs.job_get_metadata.outputs.changed_browser_integration == 'true' || github.event_name != 'pull_request' runs-on: ubuntu-20.04 strategy: + fail-fast: false matrix: bundle: - esm @@ -540,13 +579,34 @@ jobs: with: path: ${{ env.CACHED_BUILD_PATHS }} key: ${{ env.BUILD_CACHE_KEY }} + - name: Get npm cache directory + id: npm-cache-dir + run: | + echo "::set-output name=dir::$(npm config get cache)" + - name: Get Playwright version + id: playwright-version + run: | + echo "::set-output name=version::$(node -p "require('@playwright/test/package.json').version")" + - uses: actions/cache@v3 + name: Check if Playwright browser is cached + id: playwright-cache + with: + path: ${{ steps.npm-cache-dir.outputs.dir }} + key: ${{ runner.os }}-Playwright-${{steps.playwright-version.outputs.version}} + - name: Install Playwright browser if not cached + if: steps.playwright-cache.outputs.cache-hit != 'true' + run: npx playwright install --with-deps + env: + PLAYWRIGHT_BROWSERS_PATH: ${{steps.npm-cache-dir.outputs.dir}} + - name: Install OS dependencies of Playwright if cache hit + if: steps.playwright-cache.outputs.cache-hit == 'true' + run: npx playwright install-deps - name: Run Playwright tests env: PW_BUNDLE: ${{ matrix.bundle }} PW_TRACING_ONLY: ${{ matrix.tracing_only }} run: | cd packages/integration-tests - yarn run playwright install-deps webkit yarn test:ci job_browser_integration_tests: @@ -755,3 +815,51 @@ jobs: if: contains(needs.*.result, 'failure') run: | echo "One of the dependent jobs have failed. You may need to re-run it." && exit 1 + + replay_metrics: + name: Replay Metrics + needs: [job_get_metadata, job_build] + runs-on: ubuntu-20.04 + timeout-minutes: 30 + if: contains(github.event.pull_request.labels.*.name, 'ci-overhead-measurements') + steps: + - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) + uses: actions/checkout@v3 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Set up Node + uses: volta-cli/action@v4 + - name: Check dependency cache + uses: actions/cache@v3 + with: + path: ${{ env.CACHED_DEPENDENCY_PATHS }} + key: ${{ needs.job_build.outputs.dependency_cache_key }} + - name: Check build cache + uses: actions/cache@v3 + with: + path: ${{ env.CACHED_BUILD_PATHS }} + key: ${{ env.BUILD_CACHE_KEY }} + + - name: Setup + run: yarn install + working-directory: packages/replay/metrics + + - name: Collect + run: yarn ci:collect + working-directory: packages/replay/metrics + + - name: Process + id: process + run: yarn ci:process + working-directory: packages/replay/metrics + # Don't run on forks - the PR comment cannot be added. + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + env: + GITHUB_TOKEN: ${{ github.token }} + + - name: Upload results + uses: actions/upload-artifact@v3 + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + with: + name: ${{ steps.process.outputs.artifactName }} + path: ${{ steps.process.outputs.artifactPath }} diff --git a/.gitignore b/.gitignore index 17ef110da73a..d822f532c8cc 100644 --- a/.gitignore +++ b/.gitignore @@ -47,4 +47,4 @@ tmp.js # eslint .eslintcache -eslintcache/* +**/eslintcache/* diff --git a/.size-limit.js b/.size-limit.js index 20cc71fb75f0..aee03e5a242c 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -46,7 +46,7 @@ module.exports = [ }, { name: '@sentry/nextjs Client - Webpack (gzipped + minified)', - path: 'packages/nextjs/build/esm/index.client.js', + path: 'packages/nextjs/build/esm/client/index.js', import: '{ init }', gzip: true, limit: '57 KB', diff --git a/.vscode/launch.json b/.vscode/launch.json index 2fd396c8ddbf..6092a79fb1f6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -37,6 +37,22 @@ "internalConsoleOptions": "openOnSessionStart", "outputCapture": "std" }, + { + "type": "node", + "name": "Debug replay metrics collection script", + "request": "launch", + "cwd": "${workspaceFolder}/packages/replay/metrics/", + "program": "${workspaceFolder}/packages/replay/metrics/configs/dev/collect.ts", + "preLaunchTask": "Build Replay metrics script", + }, + { + "type": "node", + "name": "Debug replay metrics processing script", + "request": "launch", + "cwd": "${workspaceFolder}/packages/replay/metrics/", + "program": "${workspaceFolder}/packages/replay/metrics/configs/dev/process.ts", + "preLaunchTask": "Build Replay metrics script", + }, // Run rollup using the config file which is in the currently active tab. { "name": "Debug rollup (config from open file)", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 6e797a064c61..e68b7996f8e9 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -7,7 +7,13 @@ "type": "npm", "script": "predebug", "path": "packages/nextjs/test/integration/", - "detail": "Link the SDK (if not already linked) and build test app" + "detail": "Link the SDK (if not already linked) and build test app", + }, + { + "label": "Build Replay metrics script", + "type": "npm", + "script": "build", + "path": "packages/replay/metrics", } ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cc773f11af1..7c1cda541031 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,26 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 7.30.0 + +- feat(core): Add `addIntegration` method to client (#6651) +- feat(core): Add `replay_event` type for events (#6481) +- feat(gatsby): Support Gatsby v5 (#6635) +- feat(integrations): Add HTTPClient integration (#6500) +- feat(node): Add `LocalVariables` integration to capture local variables to stack frames (#6478) +- feat(node): Check for invalid url in node transport (#6623) +- feat(replay): Remove `replayType` from tags and into `replay_event` (#6658) +- feat(transport): Return result through Transport send (#6626) +- fix(nextjs): Don't wrap `res.json` and `res.send` (#6674) +- fix(nextjs): Don't write to `res.end` to fix `next export` (#6682) +- fix(nextjs): Exclude SDK from Edge runtime bundles (#6683) +- fix(replay): Allow Replay to be used in Electron renderers with nodeIntegration enabled (#6644) +- fix(utils): Ignore stack frames over 1kb (#6627) +- ref(angular) Add error-like objects handling (#6446) +- ref(nextjs): Remove `instrumentSever` (#6592) + +Work in this release contributed by @rjoonas, @Naddiseo, and @theofidry. Thank you for your contributions! + ## 7.29.0 This update includes a change to the `@sentry/nextjs` SDK that may increase response times of requests in applications diff --git a/docs/event-sending.md b/docs/event-sending.md index 06077e807ed3..f014e1f2f94e 100644 --- a/docs/event-sending.md +++ b/docs/event-sending.md @@ -77,8 +77,8 @@ This document gives an outline for how event sending works, and which which plac ## Replay (WIP) * `replay.sendReplayRequest()` - * `createPayload()` - * `getReplayEvent()` + * `createRecordingData()` + * `prepareReplayEvent()` * `client._prepareEvent()` (see baseclient) * `baseclient._applyClientOptions()` * `baseclient._applyIntegrationsMetadata()` diff --git a/docs/new-sdk-release-checklist.md b/docs/new-sdk-release-checklist.md index 5e795b19e18d..c9d9cec079be 100644 --- a/docs/new-sdk-release-checklist.md +++ b/docs/new-sdk-release-checklist.md @@ -21,7 +21,7 @@ This page serves as a checklist of what to do when releasing a new SDK for the f - [ ] Make sure that the `LICENSE` file exists and has the correct license (We default to the `MIT` license) - [ ] Also check, that the same license is mentioned in `package.json` -- [ ] Make sure that the tarball (`yarn build:npm`) has all the necessary contents +- [ ] Make sure that the tarball (`yarn build:tarball`) has all the necessary contents For basic SDKs, this means that the tarball has at least these files: diff --git a/lerna.json b/lerna.json index e4acd69f4123..8285520a2798 100644 --- a/lerna.json +++ b/lerna.json @@ -1,7 +1,7 @@ { - "lerna": "3.4.0", - "version": "7.29.0", - "packages": "packages/*", + "$schema": "node_modules/lerna/schemas/lerna-schema.json", + "version": "7.30.0", + "packages": ["packages/*"], "npmClient": "yarn", "useWorkspaces": true } diff --git a/nx.json b/nx.json new file mode 100644 index 000000000000..2a801035b45c --- /dev/null +++ b/nx.json @@ -0,0 +1,85 @@ +{ + "tasksRunnerOptions": { + "default": { + "runner": "nx/tasks-runners/default", + "options": { + "cacheableOperations": [ + "build:bundle", + "build:transpile", + "build:types" + ] + } + } + }, + "namedInputs": { + "sharedGlobals": [ + "{workspaceRoot}/*.js", + "{workspaceRoot}/*.json", + "{workspaceRoot}/yarn.lock" + ], + "default": [ + "{projectRoot}/**/*", + "sharedGlobals", + "!{projectRoot}/test/**/*", + "!{projectRoot}/**/*.md" + ] + }, + "targetDefaults": { + "build:bundle": { + "dependsOn": [ + "^build:transpile", + "build:transpile", + "^build:types", + "build:types" + ], + "outputs": [ + "{projectRoot}/build/bundles" + ] + }, + "build:tarball": { + "dependsOn": [ + "^build:transpile", + "build:transpile", + "^build:types", + "build:types" + ], + "outputs": [] + }, + "build:transpile": { + "dependsOn": [ + "^build:transpile:uncached", + "^build:transpile", + "build:transpile:uncached" + ], + "outputs": [ + "{projectRoot}/build/npm", + "{projectRoot}/build/esm", + "{projectRoot}/build/cjs" + ] + }, + "build:types": { + "dependsOn": [ + "^build:types" + ], + "outputs": [ + "{projectRoot}/build/types", + "{projectRoot}/build/npm/types" + ] + } + }, + "targets": { + "@sentry/serverless": { + "build:bundle": { + "dependsOn": [ + "^build:transpile", + "build:transpile", + "^build:types", + "build:types" + ], + "outputs": [ + "{projectRoot}/build/aws" + ] + } + } + } +} diff --git a/package.json b/package.json index b7ab59c0c862..296f1a1758f6 100644 --- a/package.json +++ b/package.json @@ -1,33 +1,31 @@ { "private": true, "scripts": { - "build": "node ./scripts/verify-packages-versions.js && yarn run-p build:rollup build:types build:bundle && yarn build:extras", - "build:bundle": "yarn ts-node scripts/ensure-bundle-deps.ts && yarn lerna run --parallel build:bundle", - "build:dev": "run-p build:types build:rollup", - "build:dev:filter": "lerna run --stream --concurrency 1 --sort build:dev --include-filtered-dependencies --include-filtered-dependents --scope", - "build:extras": "lerna run --parallel build:extras", - "build:rollup": "lerna run --parallel build:rollup", - "build:types": "lerna run --stream build:types", - "build:watch": "lerna run --parallel build:watch", - "build:dev:watch": "lerna run --parallel build:dev:watch", + "build": "node ./scripts/verify-packages-versions.js && run-s build:types build:transpile build:bundle", + "build:bundle": "lerna run build:bundle", + "build:dev": "run-s build:types build:transpile", + "build:dev:filter": "lerna run build:dev --include-filtered-dependencies --include-filtered-dependents --scope", + "build:transpile": "lerna run build:transpile", + "build:types": "lerna run build:types", + "build:watch": "lerna run build:watch", + "build:dev:watch": "lerna run build:dev:watch", "build:types:watch": "ts-node scripts/build-types-watch.ts", - "build:npm": "lerna run --parallel build:npm", - "circularDepCheck": "lerna run --parallel circularDepCheck", + "build:tarball": "lerna run build:tarball", + "circularDepCheck": "lerna run circularDepCheck", "clean": "run-p clean:build clean:caches", - "clean:build": "lerna run --parallel clean", + "clean:build": "lerna run clean", "clean:caches": "yarn rimraf eslintcache && yarn jest --clearCache", "clean:deps": "lerna clean --yes && rm -rf node_modules && yarn", "clean:all": "run-p clean:build clean:caches clean:deps", "codecov": "codecov", - "fix": "lerna run --parallel fix", - "link:yarn": "lerna exec --parallel yarn link", - "lint": "lerna run --parallel lint", - "lint:eslint": "lerna run --parallel lint:eslint", + "fix": "lerna run fix", + "link:yarn": "lerna exec yarn link", + "lint": "lerna run lint", + "lint:eslint": "lerna run lint:eslint", "postpublish": "lerna run --stream --concurrency 1 postpublish", - "test": "lerna run --ignore @sentry-internal/browser-integration-tests --ignore @sentry-internal/node-integration-tests --stream --concurrency 1 --sort test", - "test-ci": "ts-node ./scripts/test.ts", - "test-ci-browser": "cross-env TESTS_SKIP=node ts-node ./scripts/test.ts", - "test-ci-node": "cross-env TESTS_SKIP=browser ts-node ./scripts/test.ts", + "test": "lerna run --ignore @sentry-internal/* test", + "test-ci-browser": "lerna run test --ignore \"@sentry/{node,opentelemetry-node,serverless,nextjs,remix,gatsby}\" --ignore @sentry-internal/*", + "test-ci-node": "ts-node ./scripts/node-unit-tests.ts", "postinstall": "patch-package" }, "volta": { @@ -87,7 +85,7 @@ "jsdom": "^19.0.0", "karma-browserstack-launcher": "^1.5.1", "karma-firefox-launcher": "^1.1.0", - "lerna": "3.13.4", + "lerna": "^6.0.3", "madge": "4.0.2", "magic-string": "^0.27.0", "mocha": "^6.1.4", @@ -116,5 +114,5 @@ "@types/express-serve-static-core": "4.17.30" }, "version": "0.0.0", - "dependencies": {} + "name": "sentry-javascript" } diff --git a/packages/angular/package.json b/packages/angular/package.json index 7fb82c86056d..6e86b2caf79b 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/angular", - "version": "7.29.0", + "version": "7.30.0", "description": "Official Sentry SDK for Angular", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/angular", @@ -21,9 +21,9 @@ "rxjs": "^6.5.5 || ^7.x" }, "dependencies": { - "@sentry/browser": "7.29.0", - "@sentry/types": "7.29.0", - "@sentry/utils": "7.29.0", + "@sentry/browser": "7.30.0", + "@sentry/types": "7.30.0", + "@sentry/utils": "7.30.0", "tslib": "^2.0.0" }, "devDependencies": { @@ -41,13 +41,12 @@ "zone.js": "^0.11.8" }, "scripts": { - "build": "yarn build:ngc", - "build:ngc": "ng build --prod", + "build": "yarn build:transpile", + "build:transpile": "ng build --prod", "build:dev": "run-s build", - "build:extras": "yarn build", - "build:watch": "run-p build:ngc:watch", - "build:ngc:watch": "ng build --prod --watch", - "build:npm": "npm pack ./build", + "build:watch": "run-p build:transpile:watch", + "build:transpile:watch": "ng build --prod --watch", + "build:tarball": "npm pack ./build", "circularDepCheck": "madge --circular src/index.ts", "clean": "rimraf build coverage sentry-angular-*.tgz", "fix": "run-s fix:eslint fix:prettier", diff --git a/packages/angular/src/errorhandler.ts b/packages/angular/src/errorhandler.ts index 8cfe2427f409..941029c2ff9f 100644 --- a/packages/angular/src/errorhandler.ts +++ b/packages/angular/src/errorhandler.ts @@ -1,8 +1,9 @@ import { HttpErrorResponse } from '@angular/common/http'; -import { ErrorHandler as AngularErrorHandler, Inject, Injectable } from '@angular/core'; +import type { ErrorHandler as AngularErrorHandler } from '@angular/core'; +import { Inject, Injectable } from '@angular/core'; import * as Sentry from '@sentry/browser'; import { captureException } from '@sentry/browser'; -import { addExceptionMechanism } from '@sentry/utils'; +import { addExceptionMechanism, isString } from '@sentry/utils'; import { runOutsideAngular } from './zone'; @@ -32,7 +33,7 @@ function tryToUnwrapZonejsError(error: unknown): unknown | Error { function extractHttpModuleError(error: HttpErrorResponse): string | Error { // The `error` property of http exception can be either an `Error` object, which we can use directly... - if (error.error instanceof Error) { + if (isErrorOrErrorLikeObject(error.error)) { return error.error; } @@ -50,6 +51,31 @@ function extractHttpModuleError(error: HttpErrorResponse): string | Error { return error.message; } +type ErrorCandidate = { + name?: unknown; + message?: unknown; + stack?: unknown; +}; + +function isErrorOrErrorLikeObject(value: unknown): value is Error { + if (value instanceof Error) { + return true; + } + + if (value === null || typeof value !== 'object') { + return false; + } + + const candidate = value as ErrorCandidate; + + return ( + isString(candidate.name) && + isString(candidate.name) && + isString(candidate.message) && + (undefined === candidate.stack || isString(candidate.stack)) + ); +} + /** * Implementation of Angular's ErrorHandler provider that can be used as a drop-in replacement for the stock one. */ @@ -117,16 +143,16 @@ class SentryErrorHandler implements AngularErrorHandler { protected _defaultExtractor(errorCandidate: unknown): unknown { const error = tryToUnwrapZonejsError(errorCandidate); - // We can handle messages and Error objects directly. - if (typeof error === 'string' || error instanceof Error) { - return error; - } - // If it's http module error, extract as much information from it as we can. if (error instanceof HttpErrorResponse) { return extractHttpModuleError(error); } + // We can handle messages and Error objects directly. + if (typeof error === 'string' || isErrorOrErrorLikeObject(error)) { + return error; + } + // Nothing was extracted, fallback to default error message. return null; } diff --git a/packages/angular/src/sdk.ts b/packages/angular/src/sdk.ts index 93b6148f2943..bd0cbd99747b 100644 --- a/packages/angular/src/sdk.ts +++ b/packages/angular/src/sdk.ts @@ -1,5 +1,6 @@ import { VERSION } from '@angular/core'; -import { BrowserOptions, init as browserInit, SDK_VERSION, setContext } from '@sentry/browser'; +import type { BrowserOptions } from '@sentry/browser'; +import { init as browserInit, SDK_VERSION, setContext } from '@sentry/browser'; import { logger } from '@sentry/utils'; import { ANGULAR_MINIMUM_VERSION } from './constants'; diff --git a/packages/angular/src/tracing.ts b/packages/angular/src/tracing.ts index d2bf67900f3f..56c14b36f409 100644 --- a/packages/angular/src/tracing.ts +++ b/packages/angular/src/tracing.ts @@ -1,10 +1,13 @@ /* eslint-disable max-lines */ -import { AfterViewInit, Directive, Injectable, Input, NgModule, OnDestroy, OnInit } from '@angular/core'; -import { ActivatedRouteSnapshot, Event, NavigationEnd, NavigationStart, ResolveEnd, Router } from '@angular/router'; +import type { AfterViewInit, OnDestroy, OnInit } from '@angular/core'; +import { Directive, Injectable, Input, NgModule } from '@angular/core'; +import type { ActivatedRouteSnapshot, Event, Router } from '@angular/router'; +import { NavigationEnd, NavigationStart, ResolveEnd } from '@angular/router'; import { getCurrentHub, WINDOW } from '@sentry/browser'; -import { Span, Transaction, TransactionContext } from '@sentry/types'; +import type { Span, Transaction, TransactionContext } from '@sentry/types'; import { logger, stripUrlQueryAndFragment, timestampWithMs } from '@sentry/utils'; -import { Observable, Subscription } from 'rxjs'; +import type { Observable } from 'rxjs'; +import { Subscription } from 'rxjs'; import { filter, tap } from 'rxjs/operators'; import { ANGULAR_INIT_OP, ANGULAR_OP, ANGULAR_ROUTING_OP } from './constants'; diff --git a/packages/angular/test/errorhandler.test.ts b/packages/angular/test/errorhandler.test.ts index df28e809bb26..e4398fd8aa70 100644 --- a/packages/angular/test/errorhandler.test.ts +++ b/packages/angular/test/errorhandler.test.ts @@ -33,7 +33,7 @@ class CustomError extends Error { } class ErrorLikeShapedClass implements Partial { - constructor(public message: string) {} + constructor(public name: string, public message: string) {} } function createErrorEvent(message: string, innerError: any): ErrorEvent { @@ -118,8 +118,7 @@ describe('SentryErrorHandler', () => { createErrorHandler().handleError(errorLikeWithoutStack); expect(captureExceptionSpy).toHaveBeenCalledTimes(1); - // TODO: to be changed; see https://github.com/getsentry/sentry-javascript/issues/6332 - expect(captureExceptionSpy).toHaveBeenCalledWith('Handled unknown error', expect.any(Function)); + expect(captureExceptionSpy).toHaveBeenCalledWith(errorLikeWithoutStack, expect.any(Function)); }); it('extracts an error-like object with a stack', () => { @@ -132,8 +131,7 @@ describe('SentryErrorHandler', () => { createErrorHandler().handleError(errorLikeWithStack); expect(captureExceptionSpy).toHaveBeenCalledTimes(1); - // TODO: to be changed; see https://github.com/getsentry/sentry-javascript/issues/6332 - expect(captureExceptionSpy).toHaveBeenCalledWith('Handled unknown error', expect.any(Function)); + expect(captureExceptionSpy).toHaveBeenCalledWith(errorLikeWithStack, expect.any(Function)); }); it('extracts an object that could look like an error but is not (does not have a message)', () => { @@ -150,7 +148,6 @@ describe('SentryErrorHandler', () => { it('extracts an object that could look like an error but is not (does not have an explicit name)', () => { const notErr: Partial = { - // missing name; but actually is always there as part of the Object prototype message: 'something failed.', }; @@ -194,12 +191,12 @@ describe('SentryErrorHandler', () => { }); it('extracts an instance of class not extending Error but that has an error-like shape', () => { - const err = new ErrorLikeShapedClass('something happened'); + const err = new ErrorLikeShapedClass('sentry-error', 'something happened'); createErrorHandler().handleError(err); expect(captureExceptionSpy).toHaveBeenCalledTimes(1); - expect(captureExceptionSpy).toHaveBeenCalledWith('Handled unknown error', expect.any(Function)); + expect(captureExceptionSpy).toHaveBeenCalledWith(err, expect.any(Function)); }); it('extracts an instance of a class that does not extend Error and does not have an error-like shape', () => { @@ -304,11 +301,7 @@ describe('SentryErrorHandler', () => { createErrorHandler().handleError(err); expect(captureExceptionSpy).toHaveBeenCalledTimes(1); - // TODO: to be changed; see https://github.com/getsentry/sentry-javascript/issues/6332 - expect(captureExceptionSpy).toHaveBeenCalledWith( - 'Http failure response for (unknown url): undefined undefined', - expect.any(Function), - ); + expect(captureExceptionSpy).toHaveBeenCalledWith(errorLikeWithoutStack, expect.any(Function)); }); it('extracts an `HttpErrorResponse` with error-like object with a stack', () => { @@ -322,11 +315,7 @@ describe('SentryErrorHandler', () => { createErrorHandler().handleError(err); expect(captureExceptionSpy).toHaveBeenCalledTimes(1); - // TODO: to be changed; see https://github.com/getsentry/sentry-javascript/issues/6332 - expect(captureExceptionSpy).toHaveBeenCalledWith( - 'Http failure response for (unknown url): undefined undefined', - expect.any(Function), - ); + expect(captureExceptionSpy).toHaveBeenCalledWith(errorLikeWithStack, expect.any(Function)); }); it('extracts an `HttpErrorResponse` with an object that could look like an error but is not (does not have a message)', () => { @@ -347,7 +336,6 @@ describe('SentryErrorHandler', () => { it('extracts an `HttpErrorResponse` with an object that could look like an error but is not (does not have an explicit name)', () => { const notErr: Partial = { - // missing name; but actually is always there as part of the Object prototype message: 'something failed.', }; const err = new HttpErrorResponse({ error: notErr }); @@ -453,16 +441,13 @@ describe('SentryErrorHandler', () => { }); it('extracts an `HttpErrorResponse` with an instance of class not extending Error but that has an error-like shape', () => { - const innerErr = new ErrorLikeShapedClass('something happened'); + const innerErr = new ErrorLikeShapedClass('sentry-error', 'something happened'); const err = new HttpErrorResponse({ error: innerErr }); createErrorHandler().handleError(err); expect(captureExceptionSpy).toHaveBeenCalledTimes(1); - expect(captureExceptionSpy).toHaveBeenCalledWith( - 'Http failure response for (unknown url): undefined undefined', - expect.any(Function), - ); + expect(captureExceptionSpy).toHaveBeenCalledWith(innerErr, expect.any(Function)); }); it('extracts an `HttpErrorResponse` with an instance of a class that does not extend Error and does not have an error-like shape', () => { diff --git a/packages/angular/test/tracing.test.ts b/packages/angular/test/tracing.test.ts index d5a88c9bb082..18bc1324f1eb 100644 --- a/packages/angular/test/tracing.test.ts +++ b/packages/angular/test/tracing.test.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; -import { ActivatedRouteSnapshot } from '@angular/router'; -import { Hub } from '@sentry/types'; +import type { ActivatedRouteSnapshot } from '@angular/router'; +import type { Hub } from '@sentry/types'; import { instrumentAngularRouting, TraceClassDecorator, TraceDirective, TraceMethodDecorator } from '../src'; import { getParameterizedRouteFromSnapshot } from '../src/tracing'; diff --git a/packages/angular/test/utils/index.ts b/packages/angular/test/utils/index.ts index 0f4ff55b0b23..b15ad2028560 100644 --- a/packages/angular/test/utils/index.ts +++ b/packages/angular/test/utils/index.ts @@ -1,8 +1,10 @@ import { Component, NgModule } from '@angular/core'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { Router, Routes } from '@angular/router'; +import type { ComponentFixture } from '@angular/core/testing'; +import { TestBed } from '@angular/core/testing'; +import type { Routes } from '@angular/router'; +import { Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; -import { Transaction } from '@sentry/types'; +import type { Transaction } from '@sentry/types'; import { instrumentAngularRouting, TraceService } from '../../src'; diff --git a/packages/browser/.eslintignore b/packages/browser/.eslintignore new file mode 100644 index 000000000000..81c6b54e0be2 --- /dev/null +++ b/packages/browser/.eslintignore @@ -0,0 +1 @@ +tmp.js diff --git a/packages/browser/package.json b/packages/browser/package.json index 660866ac5c96..439423eea09f 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/browser", - "version": "7.29.0", + "version": "7.30.0", "description": "Official Sentry SDK for browsers", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/browser", @@ -16,10 +16,10 @@ "access": "public" }, "dependencies": { - "@sentry/core": "7.29.0", - "@sentry/replay": "7.29.0", - "@sentry/types": "7.29.0", - "@sentry/utils": "7.29.0", + "@sentry/core": "7.30.0", + "@sentry/replay": "7.30.0", + "@sentry/types": "7.30.0", + "@sentry/utils": "7.30.0", "tslib": "^1.9.3" }, "devDependencies": { @@ -44,17 +44,15 @@ "webpack": "^4.30.0" }, "scripts": { - "build": "run-p build:rollup build:bundle build:types", - "build:bundle": "yarn ts-node ../../scripts/ensure-bundle-deps.ts && yarn rollup --config rollup.bundle.config.js", - "build:dev": "run-p build:rollup build:types", - "build:rollup": "rollup -c rollup.npm.config.js", + "build": "run-p build:transpile build:bundle build:types", + "build:bundle": "rollup --config rollup.bundle.config.js", + "build:transpile": "rollup -c rollup.npm.config.js", "build:types": "tsc -p tsconfig.types.json", - "build:watch": "run-p build:rollup:watch build:bundle:watch build:types:watch", + "build:watch": "run-p build:transpile:watch build:bundle:watch build:types:watch", "build:bundle:watch": "rollup --config rollup.bundle.config.js --watch", - "build:dev:watch": "run-p build:rollup:watch build:types:watch", - "build:rollup:watch": "rollup -c rollup.npm.config.js --watch", + "build:transpile:watch": "rollup -c rollup.npm.config.js --watch", "build:types:watch": "tsc -p tsconfig.types.json --watch", - "build:npm": "ts-node ../../scripts/prepack.ts --bundles && npm pack ./build/npm", + "build:tarball": "ts-node ../../scripts/prepack.ts --bundles && npm pack ./build/npm", "circularDepCheck": "madge --circular src/index.ts", "clean": "rimraf build coverage .rpt2_cache sentry-browser-*.tgz", "fix": "run-s fix:eslint fix:prettier", diff --git a/packages/browser/src/client.ts b/packages/browser/src/client.ts index dbec43cd19cd..fe0265cfd98b 100644 --- a/packages/browser/src/client.ts +++ b/packages/browser/src/client.ts @@ -1,13 +1,21 @@ -import { BaseClient, getEnvelopeEndpointWithUrlEncodedAuth, Scope, SDK_VERSION } from '@sentry/core'; -import type { BrowserClientReplayOptions } from '@sentry/types'; -import { ClientOptions, Event, EventHint, Options, Severity, SeverityLevel } from '@sentry/types'; +import type { Scope } from '@sentry/core'; +import { BaseClient, getEnvelopeEndpointWithUrlEncodedAuth, SDK_VERSION } from '@sentry/core'; +import type { + BrowserClientReplayOptions, + ClientOptions, + Event, + EventHint, + Options, + Severity, + SeverityLevel, +} from '@sentry/types'; import { createClientReportEnvelope, dsnToString, logger, serializeEnvelope } from '@sentry/utils'; import { eventFromException, eventFromMessage } from './eventbuilder'; import { WINDOW } from './helpers'; -import { Breadcrumbs } from './integrations'; +import type { Breadcrumbs } from './integrations'; import { BREADCRUMB_INTEGRATION_ID } from './integrations/breadcrumbs'; -import { BrowserTransportOptions } from './transports/types'; +import type { BrowserTransportOptions } from './transports/types'; /** * Configuration options for the Sentry Browser SDK. * @see @sentry/types Options for more information. diff --git a/packages/browser/src/eventbuilder.ts b/packages/browser/src/eventbuilder.ts index d089a5b670e0..4db58b9b7b43 100644 --- a/packages/browser/src/eventbuilder.ts +++ b/packages/browser/src/eventbuilder.ts @@ -1,5 +1,5 @@ import { getCurrentHub } from '@sentry/core'; -import { Event, EventHint, Exception, Severity, SeverityLevel, StackFrame, StackParser } from '@sentry/types'; +import type { Event, EventHint, Exception, Severity, SeverityLevel, StackFrame, StackParser } from '@sentry/types'; import { addExceptionMechanism, addExceptionTypeValue, diff --git a/packages/browser/src/helpers.ts b/packages/browser/src/helpers.ts index 68a1d25f00ce..5f7b6cee7df3 100644 --- a/packages/browser/src/helpers.ts +++ b/packages/browser/src/helpers.ts @@ -1,5 +1,5 @@ import { captureException, withScope } from '@sentry/core'; -import { DsnLike, Event as SentryEvent, Mechanism, Scope, WrappedFunction } from '@sentry/types'; +import type { DsnLike, Event as SentryEvent, Mechanism, Scope, WrappedFunction } from '@sentry/types'; import { addExceptionMechanism, addExceptionTypeValue, @@ -179,5 +179,5 @@ export interface ReportDialogOptions { errorFormEntry?: string; successMessage?: string; /** Callback after reportDialog showed up */ - onLoad?(): void; + onLoad?(this: void): void; } diff --git a/packages/browser/src/integrations/breadcrumbs.ts b/packages/browser/src/integrations/breadcrumbs.ts index 8ffeb5a9f99d..b54aad03e9b2 100644 --- a/packages/browser/src/integrations/breadcrumbs.ts +++ b/packages/browser/src/integrations/breadcrumbs.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable max-lines */ import { getCurrentHub } from '@sentry/core'; -import { Event, Integration } from '@sentry/types'; +import type { Event, Integration } from '@sentry/types'; import { addInstrumentationHandler, getEventDescription, diff --git a/packages/browser/src/integrations/dedupe.ts b/packages/browser/src/integrations/dedupe.ts index aaaef31d7257..84d6d983812b 100644 --- a/packages/browser/src/integrations/dedupe.ts +++ b/packages/browser/src/integrations/dedupe.ts @@ -1,4 +1,4 @@ -import { Event, EventProcessor, Exception, Hub, Integration, StackFrame } from '@sentry/types'; +import type { Event, EventProcessor, Exception, Hub, Integration, StackFrame } from '@sentry/types'; import { logger } from '@sentry/utils'; /** Deduplication filter */ diff --git a/packages/browser/src/integrations/globalhandlers.ts b/packages/browser/src/integrations/globalhandlers.ts index 79d5d3d6a444..5bfdc126a712 100644 --- a/packages/browser/src/integrations/globalhandlers.ts +++ b/packages/browser/src/integrations/globalhandlers.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ import { getCurrentHub } from '@sentry/core'; -import { Event, EventHint, Hub, Integration, Primitive, StackParser } from '@sentry/types'; +import type { Event, EventHint, Hub, Integration, Primitive, StackParser } from '@sentry/types'; import { addExceptionMechanism, addInstrumentationHandler, @@ -11,7 +11,7 @@ import { logger, } from '@sentry/utils'; -import { BrowserClient } from '../client'; +import type { BrowserClient } from '../client'; import { eventFromUnknownInput } from '../eventbuilder'; import { shouldIgnoreOnError } from '../helpers'; diff --git a/packages/browser/src/integrations/httpcontext.ts b/packages/browser/src/integrations/httpcontext.ts index b99eeeb7540b..24b6df24312a 100644 --- a/packages/browser/src/integrations/httpcontext.ts +++ b/packages/browser/src/integrations/httpcontext.ts @@ -1,5 +1,5 @@ import { addGlobalEventProcessor, getCurrentHub } from '@sentry/core'; -import { Event, Integration } from '@sentry/types'; +import type { Event, Integration } from '@sentry/types'; import { WINDOW } from '../helpers'; @@ -36,7 +36,7 @@ export class HttpContext implements Integration { ...(referrer && { Referer: referrer }), ...(userAgent && { 'User-Agent': userAgent }), }; - const request = { ...(url && { url }), headers }; + const request = { ...event.request, ...(url && { url }), headers }; return { ...event, request }; } diff --git a/packages/browser/src/integrations/linkederrors.ts b/packages/browser/src/integrations/linkederrors.ts index 8fa4413f5a83..bb53d0210f96 100644 --- a/packages/browser/src/integrations/linkederrors.ts +++ b/packages/browser/src/integrations/linkederrors.ts @@ -1,8 +1,8 @@ import { addGlobalEventProcessor, getCurrentHub } from '@sentry/core'; -import { Event, EventHint, Exception, ExtendedError, Integration, StackParser } from '@sentry/types'; +import type { Event, EventHint, Exception, ExtendedError, Integration, StackParser } from '@sentry/types'; import { isInstanceOf } from '@sentry/utils'; -import { BrowserClient } from '../client'; +import type { BrowserClient } from '../client'; import { exceptionFromError } from '../eventbuilder'; const DEFAULT_KEY = 'cause'; diff --git a/packages/browser/src/integrations/trycatch.ts b/packages/browser/src/integrations/trycatch.ts index 2690b977ce80..d29be09a6a0b 100644 --- a/packages/browser/src/integrations/trycatch.ts +++ b/packages/browser/src/integrations/trycatch.ts @@ -1,4 +1,4 @@ -import { Integration, WrappedFunction } from '@sentry/types'; +import type { Integration, WrappedFunction } from '@sentry/types'; import { fill, getFunctionName, getOriginalFunction } from '@sentry/utils'; import { WINDOW, wrap } from '../helpers'; diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index f4c05482b4a9..c7f2ed74194a 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -1,8 +1,8 @@ +import type { Hub } from '@sentry/core'; import { getCurrentHub, getIntegrationsToSetup, getReportDialogEndpoint, - Hub, initAndBind, Integrations as CoreIntegrations, } from '@sentry/core'; @@ -14,8 +14,10 @@ import { supportsFetch, } from '@sentry/utils'; -import { BrowserClient, BrowserClientOptions, BrowserOptions } from './client'; -import { ReportDialogOptions, WINDOW, wrap as internalWrap } from './helpers'; +import type { BrowserClientOptions, BrowserOptions } from './client'; +import { BrowserClient } from './client'; +import type { ReportDialogOptions } from './helpers'; +import { WINDOW, wrap as internalWrap } from './helpers'; import { Breadcrumbs, Dedupe, GlobalHandlers, HttpContext, LinkedErrors, TryCatch } from './integrations'; import { defaultStackParser } from './stack-parsers'; import { makeFetchTransport, makeXHRTransport } from './transports'; @@ -164,7 +166,6 @@ export function showReportDialog(options: ReportDialogOptions = {}, hub: Hub = g script.src = getReportDialogEndpoint(dsn, options); if (options.onLoad) { - // eslint-disable-next-line @typescript-eslint/unbound-method script.onload = options.onLoad; } diff --git a/packages/browser/src/stack-parsers.ts b/packages/browser/src/stack-parsers.ts index eeb08c882d8c..7e77a6c0d8e3 100644 --- a/packages/browser/src/stack-parsers.ts +++ b/packages/browser/src/stack-parsers.ts @@ -1,4 +1,4 @@ -import { StackFrame, StackLineParser, StackLineParserFn } from '@sentry/types'; +import type { StackFrame, StackLineParser, StackLineParserFn } from '@sentry/types'; import { createStackParser } from '@sentry/utils'; // global reference to slice diff --git a/packages/browser/src/transports/fetch.ts b/packages/browser/src/transports/fetch.ts index 5f64bcdde841..83b75c82ba39 100644 --- a/packages/browser/src/transports/fetch.ts +++ b/packages/browser/src/transports/fetch.ts @@ -1,9 +1,10 @@ import { createTransport } from '@sentry/core'; -import { Transport, TransportMakeRequestResponse, TransportRequest } from '@sentry/types'; +import type { Transport, TransportMakeRequestResponse, TransportRequest } from '@sentry/types'; import { rejectedSyncPromise } from '@sentry/utils'; -import { BrowserTransportOptions } from './types'; -import { clearCachedFetchImplementation, FetchImpl, getNativeFetchImplementation } from './utils'; +import type { BrowserTransportOptions } from './types'; +import type { FetchImpl } from './utils'; +import { clearCachedFetchImplementation, getNativeFetchImplementation } from './utils'; /** * Creates a Transport that uses the Fetch API to send events to Sentry. diff --git a/packages/browser/src/transports/types.ts b/packages/browser/src/transports/types.ts index 4f4b7ef2c98a..e0ed666cc787 100644 --- a/packages/browser/src/transports/types.ts +++ b/packages/browser/src/transports/types.ts @@ -1,4 +1,4 @@ -import { BaseTransportOptions } from '@sentry/types'; +import type { BaseTransportOptions } from '@sentry/types'; export interface BrowserTransportOptions extends BaseTransportOptions { /** Fetch API init parameters. Used by the FetchTransport */ diff --git a/packages/browser/src/transports/xhr.ts b/packages/browser/src/transports/xhr.ts index b10ca4e2b7ca..8fae5dc8067e 100644 --- a/packages/browser/src/transports/xhr.ts +++ b/packages/browser/src/transports/xhr.ts @@ -1,8 +1,8 @@ import { createTransport } from '@sentry/core'; -import { Transport, TransportMakeRequestResponse, TransportRequest } from '@sentry/types'; +import type { Transport, TransportMakeRequestResponse, TransportRequest } from '@sentry/types'; import { SyncPromise } from '@sentry/utils'; -import { BrowserTransportOptions } from './types'; +import type { BrowserTransportOptions } from './types'; /** * The DONE ready state for XmlHttpRequest diff --git a/packages/browser/test/unit/eventbuilder.test.ts b/packages/browser/test/unit/eventbuilder.test.ts index 62a70a4b2740..ac9b564e99e0 100644 --- a/packages/browser/test/unit/eventbuilder.test.ts +++ b/packages/browser/test/unit/eventbuilder.test.ts @@ -1,4 +1,4 @@ -import { Client } from '@sentry/types'; +import type { Client } from '@sentry/types'; import { defaultStackParser } from '../../src'; import { eventFromPlainObject } from '../../src/eventbuilder'; diff --git a/packages/browser/test/unit/helper/browser-client-options.ts b/packages/browser/test/unit/helper/browser-client-options.ts index 9bdaf2518d40..8ca73faa2d23 100644 --- a/packages/browser/test/unit/helper/browser-client-options.ts +++ b/packages/browser/test/unit/helper/browser-client-options.ts @@ -1,7 +1,7 @@ import { createTransport } from '@sentry/core'; import { resolvedSyncPromise } from '@sentry/utils'; -import { BrowserClientOptions } from '../../../src/client'; +import type { BrowserClientOptions } from '../../../src/client'; export function getDefaultBrowserClientOptions(options: Partial = {}): BrowserClientOptions { return { diff --git a/packages/browser/test/unit/index.test.ts b/packages/browser/test/unit/index.test.ts index 8a59db3bdd46..8bc3ffab36e1 100644 --- a/packages/browser/test/unit/index.test.ts +++ b/packages/browser/test/unit/index.test.ts @@ -1,5 +1,6 @@ import { getReportDialogEndpoint, SDK_VERSION } from '@sentry/core'; +import type { Event } from '../../src'; import { addBreadcrumb, BrowserClient, @@ -7,7 +8,6 @@ import { captureException, captureMessage, configureScope, - Event, flush, getCurrentHub, init, diff --git a/packages/browser/test/unit/integrations/helpers.test.ts b/packages/browser/test/unit/integrations/helpers.test.ts index e17ef73d4e1c..a3fe734d79d4 100644 --- a/packages/browser/test/unit/integrations/helpers.test.ts +++ b/packages/browser/test/unit/integrations/helpers.test.ts @@ -1,4 +1,4 @@ -import { WrappedFunction } from '@sentry/types'; +import type { WrappedFunction } from '@sentry/types'; import { spy } from 'sinon'; import { wrap } from '../../../src/helpers'; diff --git a/packages/browser/test/unit/integrations/linkederrors.test.ts b/packages/browser/test/unit/integrations/linkederrors.test.ts index 1a80a86093b2..40738ca2af09 100644 --- a/packages/browser/test/unit/integrations/linkederrors.test.ts +++ b/packages/browser/test/unit/integrations/linkederrors.test.ts @@ -1,4 +1,4 @@ -import { Event as SentryEvent, Exception, ExtendedError } from '@sentry/types'; +import type { Event as SentryEvent, Exception, ExtendedError } from '@sentry/types'; import { BrowserClient } from '../../../src/client'; import * as LinkedErrorsModule from '../../../src/integrations/linkederrors'; diff --git a/packages/browser/test/unit/sdk.test.ts b/packages/browser/test/unit/sdk.test.ts index 5d03d26d3312..4afac502c562 100644 --- a/packages/browser/test/unit/sdk.test.ts +++ b/packages/browser/test/unit/sdk.test.ts @@ -1,14 +1,11 @@ /* eslint-disable @typescript-eslint/unbound-method */ -import { Scope } from '@sentry/core'; -import { createTransport } from '@sentry/core'; +import { createTransport, Scope } from '@sentry/core'; import { MockIntegration } from '@sentry/core/test/lib/sdk.test'; -import { Client, Integration } from '@sentry/types'; +import type { Client, Integration } from '@sentry/types'; import { resolvedSyncPromise } from '@sentry/utils'; -import { BrowserOptions } from '../../src'; +import type { BrowserOptions } from '../../src'; import { init } from '../../src/sdk'; -// eslint-disable-next-line no-var -declare var global: any; const PUBLIC_DSN = 'https://username@domain/123'; diff --git a/packages/browser/test/unit/transports/fetch.test.ts b/packages/browser/test/unit/transports/fetch.test.ts index 624a3d1d007a..53ed5c34e21b 100644 --- a/packages/browser/test/unit/transports/fetch.test.ts +++ b/packages/browser/test/unit/transports/fetch.test.ts @@ -1,10 +1,10 @@ -import { EventEnvelope, EventItem } from '@sentry/types'; +import type { EventEnvelope, EventItem } from '@sentry/types'; import { createEnvelope, serializeEnvelope } from '@sentry/utils'; import { TextEncoder } from 'util'; import { makeFetchTransport } from '../../../src/transports/fetch'; -import { BrowserTransportOptions } from '../../../src/transports/types'; -import { FetchImpl } from '../../../src/transports/utils'; +import type { BrowserTransportOptions } from '../../../src/transports/types'; +import type { FetchImpl } from '../../../src/transports/utils'; const DEFAULT_FETCH_TRANSPORT_OPTIONS: BrowserTransportOptions = { url: 'https://sentry.io/api/42/store/?sentry_key=123&sentry_version=7', diff --git a/packages/browser/test/unit/transports/xhr.test.ts b/packages/browser/test/unit/transports/xhr.test.ts index 117edce8d2ea..07ca7f4e07dc 100644 --- a/packages/browser/test/unit/transports/xhr.test.ts +++ b/packages/browser/test/unit/transports/xhr.test.ts @@ -1,8 +1,8 @@ -import { EventEnvelope, EventItem } from '@sentry/types'; +import type { EventEnvelope, EventItem } from '@sentry/types'; import { createEnvelope, serializeEnvelope } from '@sentry/utils'; import { TextEncoder } from 'util'; -import { BrowserTransportOptions } from '../../../src/transports/types'; +import type { BrowserTransportOptions } from '../../../src/transports/types'; import { makeXHRTransport } from '../../../src/transports/xhr'; const DEFAULT_XHR_TRANSPORT_OPTIONS: BrowserTransportOptions = { diff --git a/packages/core/package.json b/packages/core/package.json index 555c83a2379a..35da822edaf6 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/core", - "version": "7.29.0", + "version": "7.30.0", "description": "Base implementation for all Sentry JavaScript SDKs", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/core", @@ -16,20 +16,20 @@ "access": "public" }, "dependencies": { - "@sentry/types": "7.29.0", - "@sentry/utils": "7.29.0", + "@sentry/types": "7.30.0", + "@sentry/utils": "7.30.0", "tslib": "^1.9.3" }, "scripts": { - "build": "run-p build:rollup build:types", + "build": "run-p build:transpile build:types", "build:dev": "run-s build", - "build:rollup": "rollup -c rollup.npm.config.js", + "build:transpile": "rollup -c rollup.npm.config.js", "build:types": "tsc -p tsconfig.types.json", - "build:watch": "run-p build:rollup:watch build:types:watch", + "build:watch": "run-p build:transpile:watch build:types:watch", "build:dev:watch": "run-s build:watch", - "build:rollup:watch": "rollup -c rollup.npm.config.js --watch", + "build:transpile:watch": "rollup -c rollup.npm.config.js --watch", "build:types:watch": "tsc -p tsconfig.types.json --watch", - "build:npm": "ts-node ../../scripts/prepack.ts && npm pack ./build", + "build:tarball": "ts-node ../../scripts/prepack.ts && npm pack ./build", "circularDepCheck": "madge --circular src/index.ts", "clean": "rimraf build coverage sentry-core-*.tgz", "fix": "run-s fix:eslint fix:prettier", diff --git a/packages/core/src/api.ts b/packages/core/src/api.ts index f454e148cc6c..7191871d9ec4 100644 --- a/packages/core/src/api.ts +++ b/packages/core/src/api.ts @@ -1,4 +1,4 @@ -import { ClientOptions, DsnComponents, DsnLike, SdkInfo } from '@sentry/types'; +import type { ClientOptions, DsnComponents, DsnLike, SdkInfo } from '@sentry/types'; import { dsnToString, makeDsn, urlEncode } from '@sentry/utils'; const SENTRY_API_VERSION = '7'; diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index c343c2249a6e..957e7f6321d2 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -1,5 +1,5 @@ /* eslint-disable max-lines */ -import { +import type { Client, ClientOptions, DataCategory, @@ -37,8 +37,9 @@ import { import { getEnvelopeEndpointWithUrlEncodedAuth } from './api'; import { createEventEnvelope, createSessionEnvelope } from './envelope'; -import { IntegrationIndex, setupIntegrations } from './integration'; -import { Scope } from './scope'; +import type { IntegrationIndex } from './integration'; +import { setupIntegration, setupIntegrations } from './integration'; +import type { Scope } from './scope'; import { updateSession } from './session'; import { prepareEvent } from './utils/prepareEvent'; @@ -291,6 +292,13 @@ export abstract class BaseClient implements Client { } } + /** + * @inheritDoc + */ + public addIntegration(integration: Integration): void { + setupIntegration(integration, this._integrations); + } + /** * @inheritDoc */ diff --git a/packages/core/src/envelope.ts b/packages/core/src/envelope.ts index 1a2f44fd82c6..0a4cd6a87870 100644 --- a/packages/core/src/envelope.ts +++ b/packages/core/src/envelope.ts @@ -1,4 +1,4 @@ -import { +import type { DsnComponents, Event, EventEnvelope, @@ -63,7 +63,15 @@ export function createEventEnvelope( tunnel?: string, ): EventEnvelope { const sdkInfo = getSdkMetadataForEnvelopeHeader(metadata); - const eventType = event.type || 'event'; + + /* + Note: Due to TS, event.type may be `replay_event`, theoretically. + In practice, we never call `createEventEnvelope` with `replay_event` type, + and we'd have to adjut a looot of types to make this work properly. + We want to avoid casting this around, as that could lead to bugs (e.g. when we add another type) + So the safe choice is to really guard against the replay_event type here. + */ + const eventType = event.type && event.type !== 'replay_event' ? event.type : 'event'; enhanceEventWithSdkInfo(event, metadata && metadata.sdk); diff --git a/packages/core/src/exports.ts b/packages/core/src/exports.ts index 97a70d327b8a..72a5f8587f94 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -1,4 +1,4 @@ -import { +import type { Breadcrumb, CaptureContext, CustomSamplingContext, @@ -13,8 +13,9 @@ import { User, } from '@sentry/types'; -import { getCurrentHub, Hub } from './hub'; -import { Scope } from './scope'; +import type { Hub } from './hub'; +import { getCurrentHub } from './hub'; +import type { Scope } from './scope'; // Note: All functions in this file are typed with a return value of `ReturnType`, // where HUB_FUNCTION is some method on the Hub class. diff --git a/packages/core/src/hub.ts b/packages/core/src/hub.ts index 5036158e08eb..dde906bffde5 100644 --- a/packages/core/src/hub.ts +++ b/packages/core/src/hub.ts @@ -1,5 +1,5 @@ /* eslint-disable max-lines */ -import { +import type { Breadcrumb, BreadcrumbHint, Client, @@ -233,7 +233,7 @@ export class Hub implements HubInterface { */ public captureEvent(event: Event, hint?: EventHint): string { const eventId = hint && hint.event_id ? hint.event_id : uuid4(); - if (event.type !== 'transaction') { + if (!event.type) { this._lastEventId = eventId; } @@ -258,7 +258,6 @@ export class Hub implements HubInterface { if (!scope || !client) return; - // eslint-disable-next-line @typescript-eslint/unbound-method const { beforeBreadcrumb = null, maxBreadcrumbs = DEFAULT_BREADCRUMBS } = (client.getOptions && client.getOptions()) || {}; diff --git a/packages/core/src/integration.ts b/packages/core/src/integration.ts index e80b2a0843fe..77d232b00987 100644 --- a/packages/core/src/integration.ts +++ b/packages/core/src/integration.ts @@ -1,4 +1,4 @@ -import { Integration, Options } from '@sentry/types'; +import type { Integration, Options } from '@sentry/types'; import { arrayify, logger } from '@sentry/utils'; import { getCurrentHub } from './hub'; @@ -88,14 +88,19 @@ export function setupIntegrations(integrations: Integration[]): IntegrationIndex const integrationIndex: IntegrationIndex = {}; integrations.forEach(integration => { - integrationIndex[integration.name] = integration; - - if (installedIntegrations.indexOf(integration.name) === -1) { - integration.setupOnce(addGlobalEventProcessor, getCurrentHub); - installedIntegrations.push(integration.name); - __DEBUG_BUILD__ && logger.log(`Integration installed: ${integration.name}`); - } + setupIntegration(integration, integrationIndex); }); return integrationIndex; } + +/** Setup a single integration. */ +export function setupIntegration(integration: Integration, integrationIndex: IntegrationIndex): void { + integrationIndex[integration.name] = integration; + + if (installedIntegrations.indexOf(integration.name) === -1) { + integration.setupOnce(addGlobalEventProcessor, getCurrentHub); + installedIntegrations.push(integration.name); + __DEBUG_BUILD__ && logger.log(`Integration installed: ${integration.name}`); + } +} diff --git a/packages/core/src/integrations/functiontostring.ts b/packages/core/src/integrations/functiontostring.ts index 6eb1cdbda123..d26a49b99780 100644 --- a/packages/core/src/integrations/functiontostring.ts +++ b/packages/core/src/integrations/functiontostring.ts @@ -1,4 +1,4 @@ -import { Integration, WrappedFunction } from '@sentry/types'; +import type { Integration, WrappedFunction } from '@sentry/types'; import { getOriginalFunction } from '@sentry/utils'; let originalFunctionToString: () => void; diff --git a/packages/core/src/integrations/inboundfilters.ts b/packages/core/src/integrations/inboundfilters.ts index ca71d13873ce..047060ae4961 100644 --- a/packages/core/src/integrations/inboundfilters.ts +++ b/packages/core/src/integrations/inboundfilters.ts @@ -1,4 +1,4 @@ -import { Event, EventProcessor, Hub, Integration, StackFrame } from '@sentry/types'; +import type { Event, EventProcessor, Hub, Integration, StackFrame } from '@sentry/types'; import { getEventDescription, logger, stringMatchesSomePattern } from '@sentry/utils'; // "Script error." is hard coded into browsers for errors that it can't read. diff --git a/packages/core/src/scope.ts b/packages/core/src/scope.ts index 44d2d6d61a18..7143275baa8b 100644 --- a/packages/core/src/scope.ts +++ b/packages/core/src/scope.ts @@ -1,5 +1,5 @@ /* eslint-disable max-lines */ -import { +import type { Attachment, Breadcrumb, CaptureContext, diff --git a/packages/core/src/sdk.ts b/packages/core/src/sdk.ts index 16a64520ff48..7d2df2115ddd 100644 --- a/packages/core/src/sdk.ts +++ b/packages/core/src/sdk.ts @@ -1,4 +1,4 @@ -import { Client, ClientOptions } from '@sentry/types'; +import type { Client, ClientOptions } from '@sentry/types'; import { logger } from '@sentry/utils'; import { getCurrentHub } from './hub'; diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index c8cba3e30a04..2987f09addb5 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -1,4 +1,4 @@ -import { SerializedSession, Session, SessionContext, SessionStatus } from '@sentry/types'; +import type { SerializedSession, Session, SessionContext, SessionStatus } from '@sentry/types'; import { dropUndefinedKeys, timestampInSeconds, uuid4 } from '@sentry/utils'; /** diff --git a/packages/core/src/sessionflusher.ts b/packages/core/src/sessionflusher.ts index 7b2bda98c4da..9b4579ade486 100644 --- a/packages/core/src/sessionflusher.ts +++ b/packages/core/src/sessionflusher.ts @@ -1,4 +1,10 @@ -import { AggregationCounts, Client, RequestSessionStatus, SessionAggregates, SessionFlusherLike } from '@sentry/types'; +import type { + AggregationCounts, + Client, + RequestSessionStatus, + SessionAggregates, + SessionFlusherLike, +} from '@sentry/types'; import { dropUndefinedKeys } from '@sentry/utils'; import { getCurrentHub } from './hub'; diff --git a/packages/core/src/transports/base.ts b/packages/core/src/transports/base.ts index 23a7d00d70df..4a3be77e8531 100644 --- a/packages/core/src/transports/base.ts +++ b/packages/core/src/transports/base.ts @@ -1,4 +1,4 @@ -import { +import type { Envelope, EnvelopeItem, EnvelopeItemType, @@ -7,8 +7,10 @@ import { EventItem, InternalBaseTransportOptions, Transport, + TransportMakeRequestResponse, TransportRequestExecutor, } from '@sentry/types'; +import type { PromiseBuffer, RateLimits } from '@sentry/utils'; import { createEnvelope, envelopeItemTypeToDataCategory, @@ -16,8 +18,6 @@ import { isRateLimited, logger, makePromiseBuffer, - PromiseBuffer, - RateLimits, resolvedSyncPromise, SentryError, serializeEnvelope, @@ -35,13 +35,15 @@ export const DEFAULT_TRANSPORT_BUFFER_SIZE = 30; export function createTransport( options: InternalBaseTransportOptions, makeRequest: TransportRequestExecutor, - buffer: PromiseBuffer = makePromiseBuffer(options.bufferSize || DEFAULT_TRANSPORT_BUFFER_SIZE), + buffer: PromiseBuffer = makePromiseBuffer( + options.bufferSize || DEFAULT_TRANSPORT_BUFFER_SIZE, + ), ): Transport { let rateLimits: RateLimits = {}; const flush = (timeout?: number): PromiseLike => buffer.drain(timeout); - function send(envelope: Envelope): PromiseLike { + function send(envelope: Envelope): PromiseLike { const filteredEnvelopeItems: EnvelopeItem[] = []; // Drop rate limited items from envelope @@ -71,7 +73,7 @@ export function createTransport( }); }; - const requestTask = (): PromiseLike => + const requestTask = (): PromiseLike => makeRequest({ body: serializeEnvelope(filteredEnvelope, options.textEncoder) }).then( response => { // We don't want to throw on NOK responses, but we want to at least log them @@ -80,10 +82,11 @@ export function createTransport( } rateLimits = updateRateLimits(rateLimits, response); + return response; }, error => { - __DEBUG_BUILD__ && logger.error('Failed while sending event:', error); recordEnvelopeLoss('network_error'); + throw error; }, ); diff --git a/packages/core/src/utils/prepareEvent.ts b/packages/core/src/utils/prepareEvent.ts index 1d23d4d89572..3aa0d86df3f6 100644 --- a/packages/core/src/utils/prepareEvent.ts +++ b/packages/core/src/utils/prepareEvent.ts @@ -1,4 +1,4 @@ -import { ClientOptions, Event, EventHint } from '@sentry/types'; +import type { ClientOptions, Event, EventHint } from '@sentry/types'; import { dateTimestampInSeconds, normalize, resolvedSyncPromise, truncate, uuid4 } from '@sentry/utils'; import { Scope } from '../scope'; diff --git a/packages/core/src/version.ts b/packages/core/src/version.ts index 624d4bc7a47b..a90c364596fe 100644 --- a/packages/core/src/version.ts +++ b/packages/core/src/version.ts @@ -1 +1 @@ -export const SDK_VERSION = '7.29.0'; +export const SDK_VERSION = '7.30.0'; diff --git a/packages/core/test/lib/api.test.ts b/packages/core/test/lib/api.test.ts index 752f23844aad..141301878a5d 100644 --- a/packages/core/test/lib/api.test.ts +++ b/packages/core/test/lib/api.test.ts @@ -1,5 +1,5 @@ /* eslint-disable deprecation/deprecation */ -import { ClientOptions, DsnComponents } from '@sentry/types'; +import type { ClientOptions, DsnComponents } from '@sentry/types'; import { makeDsn } from '@sentry/utils'; import { getEnvelopeEndpointWithUrlEncodedAuth, getReportDialogEndpoint } from '../../src/api'; diff --git a/packages/core/test/lib/base.test.ts b/packages/core/test/lib/base.test.ts index 5b11e59fd7f3..c82014515067 100644 --- a/packages/core/test/lib/base.test.ts +++ b/packages/core/test/lib/base.test.ts @@ -1,4 +1,4 @@ -import { Event, Span } from '@sentry/types'; +import type { Event, Span } from '@sentry/types'; import { dsnToString, logger, SentryError, SyncPromise } from '@sentry/utils'; import { Hub, makeSession, Scope } from '../../src'; diff --git a/packages/core/test/lib/envelope.test.ts b/packages/core/test/lib/envelope.test.ts index ecd409b23041..9d24e1eef19e 100644 --- a/packages/core/test/lib/envelope.test.ts +++ b/packages/core/test/lib/envelope.test.ts @@ -1,4 +1,4 @@ -import { DsnComponents, DynamicSamplingContext, Event } from '@sentry/types'; +import type { DsnComponents, DynamicSamplingContext, Event } from '@sentry/types'; import { createEventEnvelope } from '../../src/envelope'; diff --git a/packages/core/test/lib/integration.test.ts b/packages/core/test/lib/integration.test.ts index 1b5b161b3d86..14b1697b9054 100644 --- a/packages/core/test/lib/integration.test.ts +++ b/packages/core/test/lib/integration.test.ts @@ -1,4 +1,4 @@ -import { Integration, Options } from '@sentry/types'; +import type { Integration, Options } from '@sentry/types'; import { getIntegrationsToSetup } from '../../src/integration'; diff --git a/packages/core/test/lib/integrations/inboundfilters.test.ts b/packages/core/test/lib/integrations/inboundfilters.test.ts index 8170e68d9cd5..ff9aca20270a 100644 --- a/packages/core/test/lib/integrations/inboundfilters.test.ts +++ b/packages/core/test/lib/integrations/inboundfilters.test.ts @@ -1,6 +1,7 @@ -import { Event, EventProcessor } from '@sentry/types'; +import type { Event, EventProcessor } from '@sentry/types'; -import { InboundFilters, InboundFiltersOptions } from '../../../src/integrations/inboundfilters'; +import type { InboundFiltersOptions } from '../../../src/integrations/inboundfilters'; +import { InboundFilters } from '../../../src/integrations/inboundfilters'; /** * Creates an instance of the InboundFilters integration and returns diff --git a/packages/core/test/lib/sdk.test.ts b/packages/core/test/lib/sdk.test.ts index 84dc75fbc3a2..c2838d7ed115 100644 --- a/packages/core/test/lib/sdk.test.ts +++ b/packages/core/test/lib/sdk.test.ts @@ -1,5 +1,5 @@ import { Scope } from '@sentry/core'; -import { Client, Integration } from '@sentry/types'; +import type { Client, Integration } from '@sentry/types'; import { installedIntegrations } from '../../src/integration'; import { initAndBind } from '../../src/sdk'; diff --git a/packages/core/test/lib/transports/base.test.ts b/packages/core/test/lib/transports/base.test.ts index 27f21b9391b1..4f4072803f7b 100644 --- a/packages/core/test/lib/transports/base.test.ts +++ b/packages/core/test/lib/transports/base.test.ts @@ -1,5 +1,6 @@ -import { AttachmentItem, EventEnvelope, EventItem, TransportMakeRequestResponse } from '@sentry/types'; -import { createEnvelope, PromiseBuffer, resolvedSyncPromise, serializeEnvelope } from '@sentry/utils'; +import type { AttachmentItem, EventEnvelope, EventItem, TransportMakeRequestResponse } from '@sentry/types'; +import type { PromiseBuffer } from '@sentry/utils'; +import { createEnvelope, resolvedSyncPromise, serializeEnvelope } from '@sentry/utils'; import { TextEncoder } from 'util'; import { createTransport } from '../../../src/transports/base'; diff --git a/packages/core/test/mocks/client.ts b/packages/core/test/mocks/client.ts index aec42264ff58..7ca980189e19 100644 --- a/packages/core/test/mocks/client.ts +++ b/packages/core/test/mocks/client.ts @@ -1,4 +1,13 @@ -import { ClientOptions, Event, EventHint, Integration, Outcome, Session, Severity, SeverityLevel } from '@sentry/types'; +import type { + ClientOptions, + Event, + EventHint, + Integration, + Outcome, + Session, + Severity, + SeverityLevel, +} from '@sentry/types'; import { resolvedSyncPromise } from '@sentry/utils'; import { TextEncoder } from 'util'; diff --git a/packages/core/test/mocks/integration.ts b/packages/core/test/mocks/integration.ts index ff15be3f2c4d..ff01158b0632 100644 --- a/packages/core/test/mocks/integration.ts +++ b/packages/core/test/mocks/integration.ts @@ -1,4 +1,4 @@ -import { Event, EventProcessor, Integration } from '@sentry/types'; +import type { Event, EventProcessor, Integration } from '@sentry/types'; import { configureScope, getCurrentHub } from '../../src'; diff --git a/packages/core/test/mocks/transport.ts b/packages/core/test/mocks/transport.ts index 0541dab4a77d..2559e6e2cf2e 100644 --- a/packages/core/test/mocks/transport.ts +++ b/packages/core/test/mocks/transport.ts @@ -1,4 +1,4 @@ -import { Transport } from '@sentry/types'; +import type { Transport } from '@sentry/types'; import { SyncPromise } from '@sentry/utils'; import { TextEncoder } from 'util'; diff --git a/packages/e2e-tests/README.md b/packages/e2e-tests/README.md index ee4cedd17fb6..51b1f118f658 100644 --- a/packages/e2e-tests/README.md +++ b/packages/e2e-tests/README.md @@ -66,7 +66,7 @@ fields: - The `buildCommand` command runs only once before any of the tests and is supposed to build the test application. If this command returns a non-zero exit code, it counts as a failed test and the test application's tests are not run. In - the example above, we use the `--pure-lockfile` flag to install depencies without modifiying the lockfile so that + the example above, we use the `--pure-lockfile` flag to install dependencies without modifiying the lockfile so that there aren't any changes in the git worktree after running the tests. - The `testCommand` command is supposed to run tests on the test application. If the configured command returns a non-zero exit code, it counts as a failed test. @@ -107,7 +107,7 @@ A standardized frontend test application has the following features: `standard-frontend-nextjs`. - A page at path `/` - Having a `` that captures an Exception when clicked. The returned - `eventId` from the `Sentry.captureException()` call must be written to `window.capturedExceptionId`. It doesn not + `eventId` from the `Sentry.captureException()` call must be written to `window.capturedExceptionId`. It does not matter what the captured error looks like. - Having an link with `id="navigation"` that navigates to `/user/5`. It doesn't have to be an `` tag, for example if a framework has another way of doing routing, the important part is that the element to click for navigation has diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index a59169ef61ce..fb5a4a1d4914 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/e2e-tests", - "version": "7.29.0", + "version": "7.30.0", "license": "MIT", "engines": { "node": ">=10" @@ -21,6 +21,7 @@ "devDependencies": { "@types/glob": "8.0.0", "@types/node": "^14.6.4", + "dotenv": "16.0.3", "glob": "8.0.3", "ts-node": "10.9.1", "typescript": "3.8.3", diff --git a/packages/e2e-tests/publish-packages.ts b/packages/e2e-tests/publish-packages.ts index 22341b7aec6c..31ba3a6b4eb4 100644 --- a/packages/e2e-tests/publish-packages.ts +++ b/packages/e2e-tests/publish-packages.ts @@ -6,7 +6,7 @@ import * as path from 'path'; const repositoryRoot = path.resolve(__dirname, '../..'); // Create tarballs -childProcess.execSync('yarn build:npm', { encoding: 'utf8', cwd: repositoryRoot, stdio: 'inherit' }); +childProcess.execSync('yarn build:tarball', { encoding: 'utf8', cwd: repositoryRoot, stdio: 'inherit' }); // Get absolute paths of all the packages we want to publish to the fake registry const packageTarballPaths = glob.sync('packages/*/sentry-*.tgz', { diff --git a/packages/e2e-tests/run.ts b/packages/e2e-tests/run.ts index fd1ab60f2244..2709feb3a040 100644 --- a/packages/e2e-tests/run.ts +++ b/packages/e2e-tests/run.ts @@ -1,10 +1,14 @@ /* eslint-disable max-lines */ /* eslint-disable no-console */ import * as childProcess from 'child_process'; +import * as dotenv from 'dotenv'; import * as fs from 'fs'; import * as glob from 'glob'; import * as path from 'path'; +// Load environment variables from .env file locally +dotenv.config(); + const repositoryRoot = path.resolve(__dirname, '../..'); const TEST_REGISTRY_CONTAINER_NAME = 'verdaccio-e2e-test-registry'; @@ -51,6 +55,7 @@ if (missingEnvVar) { const envVarsToInject = { REACT_APP_E2E_TEST_DSN: process.env.E2E_TEST_DSN, + NEXT_PUBLIC_E2E_TEST_DSN: process.env.E2E_TEST_DSN, }; // https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#grouping-log-lines diff --git a/packages/e2e-tests/test-applications/create-next-app/.gitignore b/packages/e2e-tests/test-applications/create-next-app/.gitignore new file mode 100644 index 000000000000..d9f766878259 --- /dev/null +++ b/packages/e2e-tests/test-applications/create-next-app/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +!*.d.ts + +# Sentry +.sentryclirc diff --git a/packages/e2e-tests/test-applications/create-next-app/.npmrc b/packages/e2e-tests/test-applications/create-next-app/.npmrc new file mode 100644 index 000000000000..c6b3ef9b3eaa --- /dev/null +++ b/packages/e2e-tests/test-applications/create-next-app/.npmrc @@ -0,0 +1,2 @@ +@sentry:registry=http://localhost:4873 +@sentry-internal:registry=http://localhost:4873 diff --git a/packages/e2e-tests/test-applications/create-next-app/globals.d.ts b/packages/e2e-tests/test-applications/create-next-app/globals.d.ts new file mode 100644 index 000000000000..109dbcd55648 --- /dev/null +++ b/packages/e2e-tests/test-applications/create-next-app/globals.d.ts @@ -0,0 +1,4 @@ +interface Window { + recordedTransactions?: string[]; + capturedExceptionId?: string; +} diff --git a/packages/e2e-tests/test-applications/create-next-app/next-env.d.ts b/packages/e2e-tests/test-applications/create-next-app/next-env.d.ts new file mode 100644 index 000000000000..4f11a03dc6cc --- /dev/null +++ b/packages/e2e-tests/test-applications/create-next-app/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/packages/e2e-tests/test-applications/create-next-app/next.config.js b/packages/e2e-tests/test-applications/create-next-app/next.config.js new file mode 100644 index 000000000000..efd4c00feec6 --- /dev/null +++ b/packages/e2e-tests/test-applications/create-next-app/next.config.js @@ -0,0 +1,39 @@ +// This file sets a custom webpack configuration to use your Next.js app +// with Sentry. +// https://nextjs.org/docs/api-reference/next.config.js/introduction +// https://docs.sentry.io/platforms/javascript/guides/nextjs/ + +const { withSentryConfig } = require('@sentry/nextjs'); + +const moduleExports = { + // Your existing module.exports + + sentry: { + // Use `hidden-source-map` rather than `source-map` as the Webpack `devtool` + // for client-side builds. (This will be the default starting in + // `@sentry/nextjs` version 8.0.0.) See + // https://webpack.js.org/configuration/devtool/ and + // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#use-hidden-source-map + // for more information. + hideSourceMaps: true, + }, +}; + +const sentryWebpackPluginOptions = { + // Additional config options for the Sentry Webpack plugin. Keep in mind that + // the following options are set automatically, and overriding them is not + // recommended: + // release, url, org, project, authToken, configFile, stripPrefix, + // urlPrefix, include, ignore + + silent: true, // Suppresses all logs + // For all available options, see: + // https://github.com/getsentry/sentry-webpack-plugin#options. + + // We're not testing source map uploads at the moment. + dryRun: true, +}; + +// Make sure adding Sentry options is the last code to run before exporting, to +// ensure that your source maps include changes from all other Webpack plugins +module.exports = withSentryConfig(moduleExports, sentryWebpackPluginOptions); diff --git a/packages/e2e-tests/test-applications/create-next-app/package.json b/packages/e2e-tests/test-applications/create-next-app/package.json new file mode 100644 index 000000000000..4dc80476aee9 --- /dev/null +++ b/packages/e2e-tests/test-applications/create-next-app/package.json @@ -0,0 +1,27 @@ +{ + "name": "create-next-app", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + "test": "TEST_MODE=build playwright test", + "test:dev": "TEST_MODE=dev playwright test" + }, + "dependencies": { + "@next/font": "13.0.7", + "@sentry/nextjs": "*", + "@types/node": "18.11.17", + "@types/react": "18.0.26", + "@types/react-dom": "18.0.9", + "next": "13.0.7", + "react": "18.2.0", + "react-dom": "18.2.0", + "typescript": "4.9.4" + }, + "devDependencies": { + "@playwright/test": "^1.27.1" + } +} diff --git a/packages/e2e-tests/test-applications/create-next-app/pages/_app.tsx b/packages/e2e-tests/test-applications/create-next-app/pages/_app.tsx new file mode 100644 index 000000000000..da826ed16c72 --- /dev/null +++ b/packages/e2e-tests/test-applications/create-next-app/pages/_app.tsx @@ -0,0 +1,5 @@ +import type { AppProps } from 'next/app'; + +export default function App({ Component, pageProps }: AppProps) { + return ; +} diff --git a/packages/e2e-tests/test-applications/create-next-app/pages/_document.tsx b/packages/e2e-tests/test-applications/create-next-app/pages/_document.tsx new file mode 100644 index 000000000000..e1e9cbbb75aa --- /dev/null +++ b/packages/e2e-tests/test-applications/create-next-app/pages/_document.tsx @@ -0,0 +1,13 @@ +import { Html, Head, Main, NextScript } from 'next/document'; + +export default function Document() { + return ( + + + +
+ + + + ); +} diff --git a/packages/e2e-tests/test-applications/create-next-app/pages/_error.tsx b/packages/e2e-tests/test-applications/create-next-app/pages/_error.tsx new file mode 100644 index 000000000000..031553bc20a2 --- /dev/null +++ b/packages/e2e-tests/test-applications/create-next-app/pages/_error.tsx @@ -0,0 +1,40 @@ +/** + * NOTE: This requires `@sentry/nextjs` version 7.3.0 or higher. + * + * NOTE: If using this with `next` version 12.2.0 or lower, uncomment the + * penultimate line in `CustomErrorComponent`. + * + * This page is loaded by Nextjs: + * - on the server, when data-fetching methods throw or reject + * - on the client, when `getInitialProps` throws or rejects + * - on the client, when a React lifecycle method throws or rejects, and it's + * caught by the built-in Nextjs error boundary + * + * See: + * - https://nextjs.org/docs/basic-features/data-fetching/overview + * - https://nextjs.org/docs/api-reference/data-fetching/get-initial-props + * - https://reactjs.org/docs/error-boundaries.html + */ + +import * as Sentry from '@sentry/nextjs'; +import NextErrorComponent from 'next/error'; +import { NextPageContext } from 'next'; + +const CustomErrorComponent = (props: { statusCode: any }) => { + // If you're using a Nextjs version prior to 12.2.1, uncomment this to + // compensate for https://github.com/vercel/next.js/issues/8592 + // Sentry.captureUnderscoreErrorException(props); + + return ; +}; + +CustomErrorComponent.getInitialProps = async (contextData: NextPageContext) => { + // In case this is running in a serverless function, await this in order to give Sentry + // time to send the error before the lambda exits + await Sentry.captureUnderscoreErrorException(contextData); + + // This will contain the status code of the response + return NextErrorComponent.getInitialProps(contextData); +}; + +export default CustomErrorComponent; diff --git a/packages/e2e-tests/test-applications/create-next-app/pages/api/hello.ts b/packages/e2e-tests/test-applications/create-next-app/pages/api/hello.ts new file mode 100644 index 000000000000..eb4cc6657b37 --- /dev/null +++ b/packages/e2e-tests/test-applications/create-next-app/pages/api/hello.ts @@ -0,0 +1,10 @@ +// Next.js API route support: https://nextjs.org/docs/api-routes/introduction +import type { NextApiRequest, NextApiResponse } from 'next'; + +type Data = { + name: string; +}; + +export default function handler(req: NextApiRequest, res: NextApiResponse) { + res.status(200).json({ name: 'John Doe' }); +} diff --git a/packages/e2e-tests/test-applications/create-next-app/pages/index.tsx b/packages/e2e-tests/test-applications/create-next-app/pages/index.tsx new file mode 100644 index 000000000000..807b48f3a9d3 --- /dev/null +++ b/packages/e2e-tests/test-applications/create-next-app/pages/index.tsx @@ -0,0 +1,30 @@ +import Head from 'next/head'; +import Link from 'next/link'; +import * as Sentry from '@sentry/nextjs'; + +export default function Home() { + return ( + <> + + Create Next App + + + + +
+ { + const eventId = Sentry.captureException(new Error('I am an error!')); + window.capturedExceptionId = eventId; + }} + /> + + navigate + +
+ + ); +} diff --git a/packages/e2e-tests/test-applications/create-next-app/pages/user/[id].tsx b/packages/e2e-tests/test-applications/create-next-app/pages/user/[id].tsx new file mode 100644 index 000000000000..08f65a85273d --- /dev/null +++ b/packages/e2e-tests/test-applications/create-next-app/pages/user/[id].tsx @@ -0,0 +1,5 @@ +const User = () => { + return

I am a blank page :)

; +}; + +export default User; diff --git a/packages/e2e-tests/test-applications/create-next-app/playwright.config.ts b/packages/e2e-tests/test-applications/create-next-app/playwright.config.ts new file mode 100644 index 000000000000..6bccee48f9fc --- /dev/null +++ b/packages/e2e-tests/test-applications/create-next-app/playwright.config.ts @@ -0,0 +1,69 @@ +import type { PlaywrightTestConfig } from '@playwright/test'; +import { devices } from '@playwright/test'; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +const config: PlaywrightTestConfig = { + testDir: './tests', + /* Maximum time one test can run for. */ + timeout: 60 * 1000, + expect: { + /** + * Maximum time expect() should wait for the condition to be met. + * For example in `await expect(locator).toHaveText();` + */ + timeout: 5000, + }, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: 0, + /* Opt out of parallel tests on CI. */ + workers: 1, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'dot', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ + actionTimeout: 0, + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + }, + }, + // For now we only test Chrome! + // { + // name: 'firefox', + // use: { + // ...devices['Desktop Firefox'], + // }, + // }, + // { + // name: 'webkit', + // use: { + // ...devices['Desktop Safari'], + // }, + // }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: process.env.TEST_MODE === 'build' ? 'yarn start' : 'yarn dev', + port: 3000, + }, +}; + +export default config; diff --git a/packages/e2e-tests/test-applications/create-next-app/sentry.client.config.ts b/packages/e2e-tests/test-applications/create-next-app/sentry.client.config.ts new file mode 100644 index 000000000000..9357b9eedc41 --- /dev/null +++ b/packages/e2e-tests/test-applications/create-next-app/sentry.client.config.ts @@ -0,0 +1,30 @@ +// This file configures the initialization of Sentry on the browser. +// The config you add here will be used whenever a page is visited. +// https://docs.sentry.io/platforms/javascript/guides/nextjs/ + +import * as Sentry from '@sentry/nextjs'; + +Sentry.init({ + dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN, + // Adjust this value in production, or use tracesSampler for greater control + tracesSampleRate: 1.0, + // ... + // Note: if you want to override the automatic release value, do not set a + // `release` value here - use the environment variable `SENTRY_RELEASE`, so + // that it will also get attached to your source maps +}); + +Sentry.addGlobalEventProcessor(event => { + if ( + event.type === 'transaction' && + (event.contexts?.trace?.op === 'pageload' || event.contexts?.trace?.op === 'navigation') + ) { + const eventId = event.event_id; + if (eventId) { + window.recordedTransactions = window.recordedTransactions || []; + window.recordedTransactions.push(eventId); + } + } + + return event; +}); diff --git a/packages/e2e-tests/test-applications/create-next-app/sentry.server.config.ts b/packages/e2e-tests/test-applications/create-next-app/sentry.server.config.ts new file mode 100644 index 000000000000..dfce34424f98 --- /dev/null +++ b/packages/e2e-tests/test-applications/create-next-app/sentry.server.config.ts @@ -0,0 +1,15 @@ +// This file configures the initialization of Sentry on the server. +// The config you add here will be used whenever the server handles a request. +// https://docs.sentry.io/platforms/javascript/guides/nextjs/ + +import * as Sentry from '@sentry/nextjs'; + +Sentry.init({ + dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN, + // Adjust this value in production, or use tracesSampler for greater control + tracesSampleRate: 1.0, + // ... + // Note: if you want to override the automatic release value, do not set a + // `release` value here - use the environment variable `SENTRY_RELEASE`, so + // that it will also get attached to your source maps +}); diff --git a/packages/e2e-tests/test-applications/create-next-app/test-recipe.json b/packages/e2e-tests/test-applications/create-next-app/test-recipe.json new file mode 100644 index 000000000000..b41a7a4fd05d --- /dev/null +++ b/packages/e2e-tests/test-applications/create-next-app/test-recipe.json @@ -0,0 +1,15 @@ +{ + "$schema": "../../test-recipe-schema.json", + "testApplicationName": "create-next-app", + "buildCommand": "yarn install --pure-lockfile && npx playwright install && yarn build", + "tests": [ + { + "testName": "Playwright tests - Build Mode", + "testCommand": "yarn test" + }, + { + "testName": "Playwright tests - Dev Mode", + "testCommand": "yarn test:dev" + } + ] +} diff --git a/packages/e2e-tests/test-applications/create-next-app/tests/client/behaviour.test.ts b/packages/e2e-tests/test-applications/create-next-app/tests/client/behaviour.test.ts new file mode 100644 index 000000000000..a819664f35e3 --- /dev/null +++ b/packages/e2e-tests/test-applications/create-next-app/tests/client/behaviour.test.ts @@ -0,0 +1,171 @@ +import { test, expect } from '@playwright/test'; +import axios, { AxiosError } from 'axios'; + +const authToken = process.env.E2E_TEST_AUTH_TOKEN; +const sentryTestOrgSlug = process.env.E2E_TEST_SENTRY_ORG_SLUG; +const sentryTestProject = process.env.E2E_TEST_SENTRY_TEST_PROJECT; +const EVENT_POLLING_TIMEOUT = 30_000; + +test('Sends an exception to Sentry', async ({ page, baseURL }) => { + await page.goto('/'); + + const exceptionButton = page.locator('id=exception-button'); + await exceptionButton.click(); + + const exceptionIdHandle = await page.waitForFunction(() => window.capturedExceptionId); + const exceptionEventId = await exceptionIdHandle.jsonValue(); + + console.log(`Polling for error eventId: ${exceptionEventId}`); + + await expect + .poll( + async () => { + try { + const response = await axios.get( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + return response.status; + } catch (e) { + if (e instanceof AxiosError && e.response) { + if (e.response.status !== 404) { + throw e; + } else { + return e.response.status; + } + } else { + throw e; + } + } + }, + { + timeout: EVENT_POLLING_TIMEOUT, + }, + ) + .toBe(200); +}); + +test('Sends a pageload transaction to Sentry', async ({ page }) => { + await page.goto('/'); + + const recordedTransactionsHandle = await page.waitForFunction(() => { + if (window.recordedTransactions && window.recordedTransactions?.length >= 1) { + return window.recordedTransactions; + } else { + return undefined; + } + }); + const recordedTransactionEventIds = await recordedTransactionsHandle.jsonValue(); + + if (recordedTransactionEventIds === undefined) { + throw new Error("Application didn't record any transaction event IDs."); + } + + let hadPageLoadTransaction = false; + + console.log(`Polling for transaction eventIds: ${JSON.stringify(recordedTransactionEventIds)}`); + + await Promise.all( + recordedTransactionEventIds.map(async transactionEventId => { + await expect + .poll( + async () => { + try { + const response = await axios.get( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + if (response.data.contexts.trace.op === 'pageload') { + hadPageLoadTransaction = true; + } + + return response.status; + } catch (e) { + if (e instanceof AxiosError && e.response) { + if (e.response.status !== 404) { + throw e; + } else { + return e.response.status; + } + } else { + throw e; + } + } + }, + { + timeout: EVENT_POLLING_TIMEOUT, + }, + ) + .toBe(200); + }), + ); + + expect(hadPageLoadTransaction).toBe(true); +}); + +test('Sends a navigation transaction to Sentry', async ({ page }) => { + await page.goto('/'); + + // Give pageload transaction time to finish + await page.waitForTimeout(4000); + + const linkElement = page.locator('id=navigation'); + await linkElement.click(); + + const recordedTransactionsHandle = await page.waitForFunction(() => { + if (window.recordedTransactions && window.recordedTransactions?.length >= 2) { + return window.recordedTransactions; + } else { + return undefined; + } + }); + const recordedTransactionEventIds = await recordedTransactionsHandle.jsonValue(); + + if (recordedTransactionEventIds === undefined) { + throw new Error("Application didn't record any transaction event IDs."); + } + + let hadPageNavigationTransaction = false; + + console.log(`Polling for transaction eventIds: ${JSON.stringify(recordedTransactionEventIds)}`); + + await Promise.all( + recordedTransactionEventIds.map(async transactionEventId => { + await expect + .poll( + async () => { + try { + const response = await axios.get( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + if (response.data.contexts.trace.op === 'navigation') { + hadPageNavigationTransaction = true; + } + + return response.status; + } catch (e) { + if (e instanceof AxiosError && e.response) { + if (e.response.status !== 404) { + throw e; + } else { + return e.response.status; + } + } else { + throw e; + } + } + }, + { + timeout: EVENT_POLLING_TIMEOUT, + }, + ) + .toBe(200); + }), + ); + + expect(hadPageNavigationTransaction).toBe(true); +}); diff --git a/packages/e2e-tests/test-applications/create-next-app/tsconfig.json b/packages/e2e-tests/test-applications/create-next-app/tsconfig.json new file mode 100644 index 000000000000..3ff0501fdb85 --- /dev/null +++ b/packages/e2e-tests/test-applications/create-next-app/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "next.config.js"], + "exclude": ["node_modules"] +} diff --git a/packages/e2e-tests/test-applications/create-next-app/yarn.lock b/packages/e2e-tests/test-applications/create-next-app/yarn.lock new file mode 100644 index 000000000000..24b1a68e4df3 --- /dev/null +++ b/packages/e2e-tests/test-applications/create-next-app/yarn.lock @@ -0,0 +1,789 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@next/env@13.0.7": + version "13.0.7" + resolved "https://registry.yarnpkg.com/@next/env/-/env-13.0.7.tgz#7b6ccd9006d3fb57c369e3fb62b28e15324141e9" + integrity sha512-ZBclBRB7DbkSswXgbJ+muF5RxfgmAuQKAWL8tcm86aZmoiL1ZainxQK0hMcMYdh+IYG8UObAKV2wKB5O+6P4ng== + +"@next/font@13.0.7": + version "13.0.7" + resolved "https://registry.yarnpkg.com/@next/font/-/font-13.0.7.tgz#e0046376edb0ce592d9cfddea8f4ab321eb1515a" + integrity sha512-39SzuoMI6jbrIzPs3KtXdKX03OrVp6Y7kRHcoVmOg69spiBzruPJ5x5DQSfN+OXqznbvVBNZBXnmdnSqs3qXiA== + +"@next/swc-android-arm-eabi@13.0.7": + version "13.0.7" + resolved "https://registry.yarnpkg.com/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.0.7.tgz#ddbf3d092d22f17238aa34072f5dcb8129d8b23e" + integrity sha512-QTEamOK/LCwBf05GZ261rULMbZEpE3TYdjHlXfznV+nXwTztzkBNFXwP67gv2wW44BROzgi/vrR9H8oP+J5jxg== + +"@next/swc-android-arm64@13.0.7": + version "13.0.7" + resolved "https://registry.yarnpkg.com/@next/swc-android-arm64/-/swc-android-arm64-13.0.7.tgz#96f150232eb66da377226f21a371d30389371ed5" + integrity sha512-wcy2H0Tl9ME8vKy2GnJZ7Mybwys+43F/Eh2Pvph7mSDpMbYBJ6iA0zeY62iYYXxlZhnAID3+h79FUqUEakkClw== + +"@next/swc-darwin-arm64@13.0.7": + version "13.0.7" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.0.7.tgz#34e80a22573b5321ade8417dfb814cf6e1fd9997" + integrity sha512-F/mU7csN1/J2cqXJPMgTQ6MwAbc1pJ6sp6W+X0z5JEY4IFDzxKd3wRc3pCiNF7j8xW381JlNpWxhjCctnNmfaw== + +"@next/swc-darwin-x64@13.0.7": + version "13.0.7" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.0.7.tgz#ecec57211bf54a15872bb44e5ea70c99c2efe785" + integrity sha512-636AuRQynCPnIPRVzcCk5B7OMq9XjaYam2T0HeWUCE6y7EqEO3kxiuZ4QmN81T7A6Ydb+JnivYrLelHXmgdj6A== + +"@next/swc-freebsd-x64@13.0.7": + version "13.0.7" + resolved "https://registry.yarnpkg.com/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.0.7.tgz#b4a8a49c3c3d200c9d6c43193b82ee39c6eb1d59" + integrity sha512-92XAMzNgQazowZ9t7uZmHRA5VdBl/SwEdrf5UybdfRovsxB4r3+yJWEvFaqYpSEp0gwndbwLokJdpz7OwFdL3Q== + +"@next/swc-linux-arm-gnueabihf@13.0.7": + version "13.0.7" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.0.7.tgz#6f550d348c6ece2b25426a53c5be49a3a8fc54a3" + integrity sha512-3r1CWl5P6I5n5Yxip8EXv/Rfu2Cp6wVmIOpvmczyUR82j+bcMkwPAcUjNkG/vMCagS4xV7NElrcdGb39iFmfLg== + +"@next/swc-linux-arm64-gnu@13.0.7": + version "13.0.7" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.0.7.tgz#20bd7f25a3af0edb4d3506c005f54212eb9a855b" + integrity sha512-RXo8tt6ppiwyS6hpDw3JdAjKcdVewsefxnxk9xOH4mRhMyq9V2lQx0e24X/dRiZqkx3jnWReR2WRrUlgN1UkSQ== + +"@next/swc-linux-arm64-musl@13.0.7": + version "13.0.7" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.0.7.tgz#f421bedcf2e1ad1ad7c90af1102df83634e92b6a" + integrity sha512-RWpnW+bmfXyxyY7iARbueYDGuIF+BEp3etLeYh/RUNHb9PhOHLDgJOG8haGSykud3a6CcyBI8hEjqOhoObaDpw== + +"@next/swc-linux-x64-gnu@13.0.7": + version "13.0.7" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.0.7.tgz#76cb25d3c00041dabc02e0b3ddd10f9325eb3f60" + integrity sha512-/ygUIiMMTYnbKlFs5Ba9J5k/tNxFWy8eI1bBF8UuMTvV8QJHl/aLDiA5dwsei2kk99/cu3eay62JnJXkSk3RSQ== + +"@next/swc-linux-x64-musl@13.0.7": + version "13.0.7" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.0.7.tgz#4e49b54b3578f7c4753dd7ac9c5e683914427884" + integrity sha512-dLzr6AL77USJN0ejgx5AS8O8SbFlbYTzs0XwAWag4oQpUG2p3ARvxwQgYQ0Z+6EP0zIRZ/XfLkN/mhsyi3m4PA== + +"@next/swc-win32-arm64-msvc@13.0.7": + version "13.0.7" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.0.7.tgz#98f622f9d0e34746e1ec7f25ce436a809a42313d" + integrity sha512-+vFIVa82AwqFkpFClKT+n73fGxrhAZ2u1u3mDYEBdxO6c9U4Pj3S5tZFsGFK9kLT/bFvf/eeVOICSLCC7MSgJQ== + +"@next/swc-win32-ia32-msvc@13.0.7": + version "13.0.7" + resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.0.7.tgz#f27f99aeec4207be7688a417f5934ea4868dadfc" + integrity sha512-RNLXIhp+assD39dQY9oHhDxw+/qSJRARKhOFsHfOtf8yEfCHqcKkn3X/L+ih60ntaEqK294y1WkMk6ylotsxwA== + +"@next/swc-win32-x64-msvc@13.0.7": + version "13.0.7" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.0.7.tgz#7aaa6cee723cde844e891e895e5561a60d9fa7f3" + integrity sha512-kvdnlLcrnEq72ZP0lqe2Z5NqvB9N5uSCvtXJ0PhKvNncWWd0fEG9Ec9erXgwCmVlM2ytw41k9/uuQ+SVw4Pihw== + +"@playwright/test@^1.27.1": + version "1.29.1" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.29.1.tgz#f2ed4dc143b9c7825a7ad2703b2f1ac4354e1145" + integrity sha512-iQxk2DX5U9wOGV3+/Jh9OHPsw5H3mleUL2S4BgQuwtlAfK3PnKvn38m4Rg9zIViGHVW24opSm99HQm/UFLEy6w== + dependencies: + "@types/node" "*" + playwright-core "1.29.1" + +"@rollup/plugin-sucrase@4.0.4": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@rollup/plugin-sucrase/-/plugin-sucrase-4.0.4.tgz#0a3b3d97cdc239ec3399f5a10711f751e9f95d98" + integrity sha512-YH4J8yoJb5EVnLhAwWxYAQNh2SJOR+SdZ6XdgoKEv6Kxm33riYkM8MlMaggN87UoISP52qAFyZ5ey56wu6umGg== + dependencies: + "@rollup/pluginutils" "^4.1.1" + sucrase "^3.20.0" + +"@rollup/plugin-virtual@3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-virtual/-/plugin-virtual-3.0.0.tgz#8c3f54b4ab4b267d9cd3dcbaedc58d4fd1deddca" + integrity sha512-K9KORe1myM62o0lKkNR4MmCxjwuAXsZEtIHpaILfv4kILXTOrXt/R2ha7PzMcCHPYdnkWPiBZK8ed4Zr3Ll5lQ== + +"@rollup/pluginutils@^4.1.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d" + integrity sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ== + dependencies: + estree-walker "^2.0.1" + picomatch "^2.2.2" + + +"@swc/helpers@0.4.14": + version "0.4.14" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.4.14.tgz#1352ac6d95e3617ccb7c1498ff019654f1e12a74" + integrity sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw== + dependencies: + tslib "^2.4.0" + +"@types/node@*": + version "18.11.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.18.tgz#8dfb97f0da23c2293e554c5a50d61ef134d7697f" + integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA== + +"@types/node@18.11.17": + version "18.11.17" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.17.tgz#5c009e1d9c38f4a2a9d45c0b0c493fe6cdb4bcb5" + integrity sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng== + +"@types/prop-types@*": + version "15.7.5" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" + integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== + +"@types/react-dom@18.0.9": + version "18.0.9" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.9.tgz#ffee5e4bfc2a2f8774b15496474f8e7fe8d0b504" + integrity sha512-qnVvHxASt/H7i+XG1U1xMiY5t+IHcPGUK7TDMDzom08xa7e86eCeKOiLZezwCKVxJn6NEiiy2ekgX8aQssjIKg== + dependencies: + "@types/react" "*" + +"@types/react@*", "@types/react@18.0.26": + version "18.0.26" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.26.tgz#8ad59fc01fef8eaf5c74f4ea392621749f0b7917" + integrity sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + +"@types/scheduler@*": + version "0.16.2" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" + integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== + +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +are-we-there-yet@~1.1.2: + version "1.1.7" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz#b15474a932adab4ff8a50d9adfa7e4e926f21146" + integrity sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g== + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +caniuse-lite@^1.0.30001406: + version "1.0.30001442" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001442.tgz#40337f1cf3be7c637b061e2f78582dc1daec0614" + integrity sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow== + +chalk@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +client-only@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" + integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +commander@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== + +cookie@^0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== + +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +csstype@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9" + integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw== + +debug@4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +estree-walker@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg== + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +glob@7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== + +hoist-non-react-statics@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw== + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +"js-tokens@^3.0.0 || ^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +lie@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e" + integrity sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw== + dependencies: + immediate "~3.0.5" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +localforage@^1.8.1: + version "1.10.0" + resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.10.0.tgz#5c465dc5f62b2807c3a84c0c6a1b1b3212781dd4" + integrity sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg== + dependencies: + lie "3.1.1" + +loose-envify@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lru_map@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd" + integrity sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ== + +minimatch@^3.0.4: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.6: + version "1.2.7" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" + integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== + +mkdirp@^0.5.5: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +mz@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + +nanoid@^3.3.4: + version "3.3.4" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" + integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== + +next@13.0.7: + version "13.0.7" + resolved "https://registry.yarnpkg.com/next/-/next-13.0.7.tgz#f07a0cc3afefdb86fb6668048e910a2193e3c1e2" + integrity sha512-YfTifqX9vfHm+rSU/H/3xvzOHDkYuMuh4wsvTjiqj9h7qHEF7KHB66X4qrH96Po+ohdid4JY8YVGPziDwdXL0A== + dependencies: + "@next/env" "13.0.7" + "@swc/helpers" "0.4.14" + caniuse-lite "^1.0.30001406" + postcss "8.4.14" + styled-jsx "5.1.0" + optionalDependencies: + "@next/swc-android-arm-eabi" "13.0.7" + "@next/swc-android-arm64" "13.0.7" + "@next/swc-darwin-arm64" "13.0.7" + "@next/swc-darwin-x64" "13.0.7" + "@next/swc-freebsd-x64" "13.0.7" + "@next/swc-linux-arm-gnueabihf" "13.0.7" + "@next/swc-linux-arm64-gnu" "13.0.7" + "@next/swc-linux-arm64-musl" "13.0.7" + "@next/swc-linux-x64-gnu" "13.0.7" + "@next/swc-linux-x64-musl" "13.0.7" + "@next/swc-win32-arm64-msvc" "13.0.7" + "@next/swc-win32-ia32-msvc" "13.0.7" + "@next/swc-win32-x64-msvc" "13.0.7" + +node-fetch@^2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + +npmlog@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ== + +object-assign@^4.0.1, object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.2.2: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pirates@^4.0.1: + version "4.0.5" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" + integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== + +playwright-core@1.29.1: + version "1.29.1" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.29.1.tgz#9ec15d61c4bd2f386ddf6ce010db53a030345a47" + integrity sha512-20Ai3d+lMkWpI9YZYlxk8gxatfgax5STW8GaMozAHwigLiyiKQrdkt7gaoT9UQR8FIVDg6qVXs9IoZUQrDjIIg== + +postcss@8.4.14: + version "8.4.14" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf" + integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig== + dependencies: + nanoid "^3.3.4" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +progress@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +react-dom@18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" + integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.23.0" + +react-is@^16.7.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react@18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" + integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== + dependencies: + loose-envify "^1.1.0" + +readable-stream@^2.0.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +rollup@2.78.0: + version "2.78.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.78.0.tgz#00995deae70c0f712ea79ad904d5f6b033209d9e" + integrity sha512-4+YfbQC9QEVvKTanHhIAFVUFSRsezvQF8vFOJwtGfb9Bb+r014S+qryr9PSmw8x6sMnPkmFBGAvIFVQxvJxjtg== + optionalDependencies: + fsevents "~2.3.2" + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +scheduler@^0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" + integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== + dependencies: + loose-envify "^1.1.0" + +set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + +signal-exit@^3.0.0: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw== + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +"string-width@^1.0.2 || 2 || 3 || 4": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg== + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +styled-jsx@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.1.0.tgz#4a5622ab9714bd3fcfaeec292aa555871f057563" + integrity sha512-/iHaRJt9U7T+5tp6TRelLnqBqiaIT0HsO0+vgyj8hK2KUk7aejFqRrumqPUlAqDwAj8IbS/1hk3IhBAAK/FCUQ== + dependencies: + client-only "0.0.1" + +sucrase@^3.20.0: + version "3.29.0" + resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.29.0.tgz#3207c5bc1b980fdae1e539df3f8a8a518236da7d" + integrity sha512-bZPAuGA5SdFHuzqIhTAqt9fvNEo9rESqXIG3oiKdF8K4UmkQxC4KlNL3lVyAErXp+mPvUqZ5l13qx6TrDIGf3A== + dependencies: + commander "^4.0.0" + glob "7.1.6" + lines-and-columns "^1.1.6" + mz "^2.7.0" + pirates "^4.0.1" + ts-interface-checker "^0.1.9" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +ts-interface-checker@^0.1.9: + version "0.1.13" + resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" + integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== + +tslib@^1.9.3: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" + integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== + +typescript@4.9.4: + version "4.9.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78" + integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg== + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +"webpack-sources@^2.0.0 || ^3.0.0": + version "3.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +which@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.0: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== diff --git a/packages/ember/package.json b/packages/ember/package.json index 70f05202afac..1083b75ecc68 100644 --- a/packages/ember/package.json +++ b/packages/ember/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/ember", - "version": "7.29.0", + "version": "7.30.0", "description": "Official Sentry SDK for Ember.js", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/ember", @@ -18,7 +18,7 @@ }, "scripts": { "build": "ember build --environment=production", - "build:npm": "ember ts:precompile && npm pack && ember ts:clean", + "build:tarball": "ember ts:precompile && npm pack && ember ts:clean", "clean": "yarn rimraf sentry-ember-*.tgz", "lint": "run-p lint:js lint:hbs lint:ts", "lint:hbs": "ember-template-lint .", @@ -30,10 +30,10 @@ }, "dependencies": { "@embroider/macros": "^1.9.0", - "@sentry/browser": "7.29.0", - "@sentry/tracing": "7.29.0", - "@sentry/types": "7.29.0", - "@sentry/utils": "7.29.0", + "@sentry/browser": "7.30.0", + "@sentry/tracing": "7.30.0", + "@sentry/types": "7.30.0", + "@sentry/utils": "7.30.0", "ember-auto-import": "^1.12.1 || ^2.4.3", "ember-cli-babel": "^7.26.11", "ember-cli-htmlbars": "^6.1.1", diff --git a/packages/eslint-config-sdk/package.json b/packages/eslint-config-sdk/package.json index 4b117f149d0e..acb20a03ec7e 100644 --- a/packages/eslint-config-sdk/package.json +++ b/packages/eslint-config-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/eslint-config-sdk", - "version": "7.29.0", + "version": "7.30.0", "description": "Official Sentry SDK eslint config", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/eslint-config-sdk", @@ -19,10 +19,10 @@ "access": "public" }, "dependencies": { - "@sentry-internal/eslint-plugin-sdk": "7.29.0", - "@sentry-internal/typescript": "7.29.0", - "@typescript-eslint/eslint-plugin": "^3.9.0", - "@typescript-eslint/parser": "^3.9.0", + "@sentry-internal/eslint-plugin-sdk": "7.30.0", + "@sentry-internal/typescript": "7.30.0", + "@typescript-eslint/eslint-plugin": "^5.48.0", + "@typescript-eslint/parser": "^5.48.0", "eslint-config-prettier": "^6.11.0", "eslint-plugin-deprecation": "^1.1.0", "eslint-plugin-import": "^2.22.0", @@ -39,7 +39,7 @@ "clean": "yarn rimraf sentry-internal-eslint-config-sdk-*.tgz", "lint": "prettier --check \"**/*.js\"", "fix": "prettier --write \"**/*.js\"", - "build:npm": "npm pack", + "build:tarball": "npm pack", "circularDepCheck": "madge --circular src/index.js" }, "volta": { diff --git a/packages/eslint-config-sdk/src/index.js b/packages/eslint-config-sdk/src/index.js index a405a12fc4bb..ef5d219bcc6e 100644 --- a/packages/eslint-config-sdk/src/index.js +++ b/packages/eslint-config-sdk/src/index.js @@ -24,7 +24,7 @@ module.exports = { '@sentry-internal/sdk/no-eq-empty': 'error', // Unused variables should be removed unless they are marked with and underscore (ex. _varName). - '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], // Make sure that all ts-ignore comments are given a description. '@typescript-eslint/ban-ts-comment': [ @@ -55,6 +55,8 @@ module.exports = { // in SDKs, we should make sure that we are correctly preserving class scope. '@typescript-eslint/unbound-method': 'error', + '@typescript-eslint/consistent-type-imports': 'error', + // Private and protected members of a class should be prefixed with a leading underscore. // typeLike declarations (class, interface, typeAlias, enum, typeParameter) should be // PascalCase. diff --git a/packages/eslint-plugin-sdk/package.json b/packages/eslint-plugin-sdk/package.json index bef126ca3150..1060fcd21bab 100644 --- a/packages/eslint-plugin-sdk/package.json +++ b/packages/eslint-plugin-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/eslint-plugin-sdk", - "version": "7.29.0", + "version": "7.30.0", "description": "Official Sentry SDK eslint plugin", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/eslint-plugin-sdk", @@ -33,7 +33,7 @@ "lint:eslint": "eslint . --cache --cache-location '../../eslintcache/' --format stylish", "lint:prettier": "prettier --check \"{src,test}/**/*.js\"", "test": "mocha test --recursive", - "build:npm": "npm pack", + "build:tarball": "npm pack", "circularDepCheck": "madge --circular src/index.js" }, "volta": { diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json index 508ba3fed1e5..ae6b74f8da09 100644 --- a/packages/gatsby/package.json +++ b/packages/gatsby/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/gatsby", - "version": "7.29.0", + "version": "7.30.0", "description": "Official Sentry SDK for Gatsby.js", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/gatsby", @@ -20,10 +20,10 @@ "access": "public" }, "dependencies": { - "@sentry/react": "7.29.0", - "@sentry/tracing": "7.29.0", - "@sentry/types": "7.29.0", - "@sentry/utils": "7.29.0", + "@sentry/react": "7.30.0", + "@sentry/tracing": "7.30.0", + "@sentry/types": "7.30.0", + "@sentry/utils": "7.30.0", "@sentry/webpack-plugin": "1.19.0" }, "peerDependencies": { @@ -35,17 +35,17 @@ "react": "^18.0.0" }, "scripts": { - "build": "run-p build:rollup build:types && yarn build:extras", + "build": "run-p build:transpile build:types", "build:dev": "run-s build", - "build:extras": "yarn build:plugin", "build:plugin": "tsc -p tsconfig.plugin.json", + "build:transpile": "run-p build:rollup build:plugin", "build:rollup": "rollup -c rollup.npm.config.js", "build:types": "tsc -p tsconfig.types.json", - "build:watch": "run-p build:rollup:watch build:types:watch", + "build:watch": "run-p build:transpile:watch build:types:watch", "build:dev:watch": "run-s build:watch", - "build:rollup:watch": "rollup -c rollup.npm.config.js --watch", + "build:transpile:watch": "rollup -c rollup.npm.config.js --watch", "build:types:watch": "tsc -p tsconfig.types.json --watch", - "build:npm": "ts-node ../../scripts/prepack.ts && npm pack ./build", + "build:tarball": "ts-node ../../scripts/prepack.ts && npm pack ./build", "circularDepCheck": "madge --circular src/index.ts", "clean": "rimraf build coverage *.d.ts sentry-gatsby-*.tgz", "fix": "run-s fix:eslint fix:prettier", diff --git a/packages/gatsby/src/sdk.ts b/packages/gatsby/src/sdk.ts index d3a7cac50a1f..6f4f8a110e3f 100644 --- a/packages/gatsby/src/sdk.ts +++ b/packages/gatsby/src/sdk.ts @@ -1,7 +1,7 @@ import { init as reactInit, SDK_VERSION } from '@sentry/react'; import { getIntegrationsFromOptions } from './utils/integrations'; -import { GatsbyOptions } from './utils/types'; +import type { GatsbyOptions } from './utils/types'; /** * Inits the Sentry Gatsby SDK. diff --git a/packages/gatsby/src/utils/integrations.ts b/packages/gatsby/src/utils/integrations.ts index 3d6f725e3666..96cabb33bb68 100644 --- a/packages/gatsby/src/utils/integrations.ts +++ b/packages/gatsby/src/utils/integrations.ts @@ -1,7 +1,7 @@ import * as Tracing from '@sentry/tracing'; -import { Integration } from '@sentry/types'; +import type { Integration } from '@sentry/types'; -import { GatsbyOptions } from './types'; +import type { GatsbyOptions } from './types'; type UserFnIntegrations = (integrations: Integration[]) => Integration[]; export type UserIntegrations = Integration[] | UserFnIntegrations; diff --git a/packages/gatsby/src/utils/types.ts b/packages/gatsby/src/utils/types.ts index 936a5b3c6ae6..87ba3f7caf46 100644 --- a/packages/gatsby/src/utils/types.ts +++ b/packages/gatsby/src/utils/types.ts @@ -1,3 +1,3 @@ -import { BrowserOptions } from '@sentry/react'; +import type { BrowserOptions } from '@sentry/react'; export type GatsbyOptions = BrowserOptions; diff --git a/packages/gatsby/test/sdk.test.ts b/packages/gatsby/test/sdk.test.ts index a1e60c59fd06..1c4342a13a4b 100644 --- a/packages/gatsby/test/sdk.test.ts +++ b/packages/gatsby/test/sdk.test.ts @@ -1,10 +1,10 @@ import { init, SDK_VERSION } from '@sentry/react'; import { Integrations } from '@sentry/tracing'; -import { Integration } from '@sentry/types'; +import type { Integration } from '@sentry/types'; import { init as gatsbyInit } from '../src/sdk'; -import { UserIntegrations } from '../src/utils/integrations'; -import { GatsbyOptions } from '../src/utils/types'; +import type { UserIntegrations } from '../src/utils/integrations'; +import type { GatsbyOptions } from '../src/utils/types'; jest.mock('@sentry/react', () => { const actual = jest.requireActual('@sentry/react'); diff --git a/packages/hub/package.json b/packages/hub/package.json index f37f53689d47..318a99482f1b 100644 --- a/packages/hub/package.json +++ b/packages/hub/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/hub", - "version": "7.29.0", + "version": "7.30.0", "description": "Sentry hub which handles global state managment.", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/hub", @@ -16,21 +16,21 @@ "access": "public" }, "dependencies": { - "@sentry/core": "7.29.0", - "@sentry/types": "7.29.0", - "@sentry/utils": "7.29.0", + "@sentry/core": "7.30.0", + "@sentry/types": "7.30.0", + "@sentry/utils": "7.30.0", "tslib": "^1.9.3" }, "scripts": { - "build": "run-p build:rollup build:types", + "build": "run-p build:transpile build:types", "build:dev": "run-s build", - "build:rollup": "rollup -c rollup.npm.config.js", + "build:transpile": "rollup -c rollup.npm.config.js", "build:types": "tsc -p tsconfig.types.json", - "build:watch": "run-p build:rollup:watch build:types:watch", + "build:watch": "run-p build:transpile:watch build:types:watch", "build:dev:watch": "run-s build:watch", - "build:rollup:watch": "rollup -c rollup.npm.config.js --watch", + "build:transpile:watch": "rollup -c rollup.npm.config.js --watch", "build:types:watch": "tsc -p tsconfig.types.json --watch", - "build:npm": "ts-node ../../scripts/prepack.ts && npm pack ./build", + "build:tarball": "ts-node ../../scripts/prepack.ts && npm pack ./build", "circularDepCheck": "madge --circular src/index.ts", "clean": "rimraf build coverage sentry-hub-*.tgz", "fix": "run-s fix:eslint fix:prettier", diff --git a/packages/hub/test/exports.test.ts b/packages/hub/test/exports.test.ts index a0fabf8061f6..967448a05d4a 100644 --- a/packages/hub/test/exports.test.ts +++ b/packages/hub/test/exports.test.ts @@ -1,5 +1,6 @@ /* eslint-disable deprecation/deprecation */ +import type { Scope } from '../src'; import { captureEvent, captureException, @@ -7,7 +8,6 @@ import { configureScope, getCurrentHub, getHubFromCarrier, - Scope, setContext, setExtra, setExtras, diff --git a/packages/hub/test/hub.test.ts b/packages/hub/test/hub.test.ts index 5ce521ead4c4..08b876174eb7 100644 --- a/packages/hub/test/hub.test.ts +++ b/packages/hub/test/hub.test.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/unbound-method */ /* eslint-disable deprecation/deprecation */ -import { Client, Event } from '@sentry/types'; +import type { Client, Event, EventType } from '@sentry/types'; import { getCurrentHub, Hub, Scope } from '../src'; @@ -358,10 +358,11 @@ describe('Hub', () => { expect(args[1].event_id).toEqual(hub.lastEventId()); }); - test('transactions do not set lastEventId', () => { + const eventTypesToIgnoreLastEventId: EventType[] = ['transaction', 'replay_event']; + it.each(eventTypesToIgnoreLastEventId)('eventType %s does not set lastEventId', eventType => { const event: Event = { extra: { b: 3 }, - type: 'transaction', + type: eventType, }; const testClient = makeClient(); const hub = new Hub(testClient); diff --git a/packages/hub/test/scope.test.ts b/packages/hub/test/scope.test.ts index 468d29dee030..d2686afb6477 100644 --- a/packages/hub/test/scope.test.ts +++ b/packages/hub/test/scope.test.ts @@ -1,6 +1,6 @@ /* eslint-disable deprecation/deprecation */ -import { Event, EventHint, RequestSessionStatus } from '@sentry/types'; +import type { Event, EventHint, RequestSessionStatus } from '@sentry/types'; import { GLOBAL_OBJ } from '@sentry/utils'; import { addGlobalEventProcessor, Scope } from '../src'; diff --git a/packages/hub/test/session.test.ts b/packages/hub/test/session.test.ts index 9de03fa34940..fd8a1a58c359 100644 --- a/packages/hub/test/session.test.ts +++ b/packages/hub/test/session.test.ts @@ -1,6 +1,6 @@ /* eslint-disable deprecation/deprecation */ -import { SessionContext } from '@sentry/types'; +import type { SessionContext } from '@sentry/types'; import { timestampInSeconds } from '@sentry/utils'; import { closeSession, makeSession, updateSession } from '../src'; diff --git a/packages/hub/test/sessionflusher.test.ts b/packages/hub/test/sessionflusher.test.ts index b743eeca1e70..0079e56087b4 100644 --- a/packages/hub/test/sessionflusher.test.ts +++ b/packages/hub/test/sessionflusher.test.ts @@ -1,6 +1,6 @@ /* eslint-disable deprecation/deprecation */ -import { Client } from '@sentry/types'; +import type { Client } from '@sentry/types'; import { SessionFlusher } from '../src'; diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 5c30315831d7..d9377403dbf4 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/browser-integration-tests", - "version": "7.29.0", + "version": "7.30.0", "main": "index.js", "license": "MIT", "engines": { @@ -13,6 +13,9 @@ "lint": "run-s lint:prettier lint:eslint", "lint:eslint": "eslint . --cache --cache-location '../../eslintcache/' --format stylish", "lint:prettier": "prettier --check \"{suites,utils}/**/*.ts\"", + "fix": "run-s fix:eslint fix:prettier", + "fix:eslint": "eslint . --format stylish --fix", + "fix:prettier": "prettier --write \"{suites,utils}/**/*.ts\"", "type-check": "tsc", "pretest": "yarn clean && yarn type-check", "test": "playwright test ./suites", @@ -26,10 +29,10 @@ }, "dependencies": { "@babel/preset-typescript": "^7.16.7", - "@playwright/test": "^1.27.1", + "@playwright/test": "^1.29.2", "babel-loader": "^8.2.2", "html-webpack-plugin": "^5.5.0", - "playwright": "^1.27.1", + "playwright": "^1.29.2", "typescript": "^4.5.2", "webpack": "^5.52.0" }, diff --git a/packages/integration-tests/playwright.config.ts b/packages/integration-tests/playwright.config.ts index 4ae274b13f22..39c4dd6a213e 100644 --- a/packages/integration-tests/playwright.config.ts +++ b/packages/integration-tests/playwright.config.ts @@ -1,4 +1,4 @@ -import { PlaywrightTestConfig } from '@playwright/test'; +import type { PlaywrightTestConfig } from '@playwright/test'; const config: PlaywrightTestConfig = { retries: 2, diff --git a/packages/integration-tests/suites/integrations/httpclient/fetch/subject.js b/packages/integration-tests/suites/integrations/httpclient/fetch/subject.js new file mode 100644 index 000000000000..94ab60d92ed9 --- /dev/null +++ b/packages/integration-tests/suites/integrations/httpclient/fetch/subject.js @@ -0,0 +1,9 @@ +fetch('http://localhost:7654/foo', { + method: 'GET', + credentials: 'include', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + Cache: 'no-cache', + }, +}); diff --git a/packages/integration-tests/suites/integrations/httpclient/fetch/test.ts b/packages/integration-tests/suites/integrations/httpclient/fetch/test.ts new file mode 100644 index 000000000000..3b845c8a8029 --- /dev/null +++ b/packages/integration-tests/suites/integrations/httpclient/fetch/test.ts @@ -0,0 +1,67 @@ +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; + +import { sentryTest } from '../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; + +sentryTest( + 'should assign request and response context from a failed 500 fetch request', + async ({ getLocalTestPath, page }) => { + const url = await getLocalTestPath({ testDir: __dirname }); + + await page.route('**/foo', route => { + return route.fulfill({ + status: 500, + body: JSON.stringify({ + error: { + message: 'Internal Server Error', + }, + }), + headers: { + 'Content-Type': 'text/html', + }, + }); + }); + + const eventData = await getFirstSentryEnvelopeRequest(page, url); + + expect(eventData.exception?.values).toHaveLength(1); + + // Not able to get the cookies from the request/response because of Playwright bug + // https://github.com/microsoft/playwright/issues/11035 + expect(eventData).toMatchObject({ + message: 'HTTP Client Error with status code: 500', + exception: { + values: [ + { + type: 'Error', + value: 'HTTP Client Error with status code: 500', + mechanism: { + type: 'http.client', + handled: true, + }, + }, + ], + }, + request: { + url: 'http://localhost:7654/foo', + method: 'GET', + headers: { + accept: 'application/json', + cache: 'no-cache', + 'content-type': 'application/json', + }, + }, + contexts: { + response: { + status_code: 500, + body_size: 45, + headers: { + 'content-type': 'text/html', + 'content-length': '45', + }, + }, + }, + }); + }, +); diff --git a/packages/integration-tests/suites/integrations/httpclient/init.js b/packages/integration-tests/suites/integrations/httpclient/init.js new file mode 100644 index 000000000000..5d43b49e75fb --- /dev/null +++ b/packages/integration-tests/suites/integrations/httpclient/init.js @@ -0,0 +1,11 @@ +import * as Sentry from '@sentry/browser'; +import { HttpClient } from '@sentry/integrations'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [new HttpClient()], + tracesSampleRate: 1, + sendDefaultPii: true, +}); diff --git a/packages/integration-tests/suites/integrations/httpclient/xhr/subject.js b/packages/integration-tests/suites/integrations/httpclient/xhr/subject.js new file mode 100644 index 000000000000..7a2e3cdd28c0 --- /dev/null +++ b/packages/integration-tests/suites/integrations/httpclient/xhr/subject.js @@ -0,0 +1,8 @@ +const xhr = new XMLHttpRequest(); + +xhr.open('GET', 'http://localhost:7654/foo', true); +xhr.withCredentials = true; +xhr.setRequestHeader('Accept', 'application/json'); +xhr.setRequestHeader('Content-Type', 'application/json'); +xhr.setRequestHeader('Cache', 'no-cache'); +xhr.send(); diff --git a/packages/integration-tests/suites/integrations/httpclient/xhr/test.ts b/packages/integration-tests/suites/integrations/httpclient/xhr/test.ts new file mode 100644 index 000000000000..a6dfcc755ae0 --- /dev/null +++ b/packages/integration-tests/suites/integrations/httpclient/xhr/test.ts @@ -0,0 +1,67 @@ +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; + +import { sentryTest } from '../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; + +sentryTest( + 'should assign request and response context from a failed 500 XHR request', + async ({ getLocalTestPath, page }) => { + const url = await getLocalTestPath({ testDir: __dirname }); + + await page.route('**/foo', route => { + return route.fulfill({ + status: 500, + body: JSON.stringify({ + error: { + message: 'Internal Server Error', + }, + }), + headers: { + 'Content-Type': 'text/html', + }, + }); + }); + + const eventData = await getFirstSentryEnvelopeRequest(page, url); + + expect(eventData.exception?.values).toHaveLength(1); + + // Not able to get the cookies from the request/response because of Playwright bug + // https://github.com/microsoft/playwright/issues/11035 + expect(eventData).toMatchObject({ + message: 'HTTP Client Error with status code: 500', + exception: { + values: [ + { + type: 'Error', + value: 'HTTP Client Error with status code: 500', + mechanism: { + type: 'http.client', + handled: true, + }, + }, + ], + }, + request: { + url: 'http://localhost:7654/foo', + method: 'GET', + headers: { + Accept: 'application/json', + Cache: 'no-cache', + 'Content-Type': 'application/json', + }, + }, + contexts: { + response: { + status_code: 500, + body_size: 45, + headers: { + 'content-type': 'text/html', + 'content-length': '45', + }, + }, + }, + }); + }, +); diff --git a/packages/integration-tests/suites/public-api/addBreadcrumb/empty_obj/test.ts b/packages/integration-tests/suites/public-api/addBreadcrumb/empty_obj/test.ts index 3fea4283b71e..47435f3d57be 100644 --- a/packages/integration-tests/suites/public-api/addBreadcrumb/empty_obj/test.ts +++ b/packages/integration-tests/suites/public-api/addBreadcrumb/empty_obj/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/addBreadcrumb/multiple_breadcrumbs/test.ts b/packages/integration-tests/suites/public-api/addBreadcrumb/multiple_breadcrumbs/test.ts index d864be4f9073..bbaafa997fda 100644 --- a/packages/integration-tests/suites/public-api/addBreadcrumb/multiple_breadcrumbs/test.ts +++ b/packages/integration-tests/suites/public-api/addBreadcrumb/multiple_breadcrumbs/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/addBreadcrumb/simple_breadcrumb/test.ts b/packages/integration-tests/suites/public-api/addBreadcrumb/simple_breadcrumb/test.ts index 224d4dba0932..d6a24b8cc167 100644 --- a/packages/integration-tests/suites/public-api/addBreadcrumb/simple_breadcrumb/test.ts +++ b/packages/integration-tests/suites/public-api/addBreadcrumb/simple_breadcrumb/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/addBreadcrumb/undefined_arg/test.ts b/packages/integration-tests/suites/public-api/addBreadcrumb/undefined_arg/test.ts index 5e5ec669a7dc..ec04b9027783 100644 --- a/packages/integration-tests/suites/public-api/addBreadcrumb/undefined_arg/test.ts +++ b/packages/integration-tests/suites/public-api/addBreadcrumb/undefined_arg/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/captureException/empty_obj/test.ts b/packages/integration-tests/suites/public-api/captureException/empty_obj/test.ts index a41fdcc6a6e1..6ce86bfe7aeb 100644 --- a/packages/integration-tests/suites/public-api/captureException/empty_obj/test.ts +++ b/packages/integration-tests/suites/public-api/captureException/empty_obj/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/captureException/simple_error/test.ts b/packages/integration-tests/suites/public-api/captureException/simple_error/test.ts index 49627e826726..7e884c6eb6dc 100644 --- a/packages/integration-tests/suites/public-api/captureException/simple_error/test.ts +++ b/packages/integration-tests/suites/public-api/captureException/simple_error/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/captureException/undefined_arg/test.ts b/packages/integration-tests/suites/public-api/captureException/undefined_arg/test.ts index 52e2ef5c21f8..5bf560e93707 100644 --- a/packages/integration-tests/suites/public-api/captureException/undefined_arg/test.ts +++ b/packages/integration-tests/suites/public-api/captureException/undefined_arg/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/captureMessage/simple_message/test.ts b/packages/integration-tests/suites/public-api/captureMessage/simple_message/test.ts index cfd5580653ac..60cc4a19f089 100644 --- a/packages/integration-tests/suites/public-api/captureMessage/simple_message/test.ts +++ b/packages/integration-tests/suites/public-api/captureMessage/simple_message/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/captureMessage/with_level/test.ts b/packages/integration-tests/suites/public-api/captureMessage/with_level/test.ts index da17ff07a77e..1422ed64bf31 100644 --- a/packages/integration-tests/suites/public-api/captureMessage/with_level/test.ts +++ b/packages/integration-tests/suites/public-api/captureMessage/with_level/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getMultipleSentryEnvelopeRequests } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/configureScope/clear_scope/test.ts b/packages/integration-tests/suites/public-api/configureScope/clear_scope/test.ts index e86d5e11aef6..02a82ffef26d 100644 --- a/packages/integration-tests/suites/public-api/configureScope/clear_scope/test.ts +++ b/packages/integration-tests/suites/public-api/configureScope/clear_scope/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/configureScope/set_properties/test.ts b/packages/integration-tests/suites/public-api/configureScope/set_properties/test.ts index 992dd7c31043..ba31c0ca18e7 100644 --- a/packages/integration-tests/suites/public-api/configureScope/set_properties/test.ts +++ b/packages/integration-tests/suites/public-api/configureScope/set_properties/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/init/console/test.ts b/packages/integration-tests/suites/public-api/init/console/test.ts index 1f71332533c8..5fc32e430caf 100644 --- a/packages/integration-tests/suites/public-api/init/console/test.ts +++ b/packages/integration-tests/suites/public-api/init/console/test.ts @@ -1,5 +1,6 @@ /* eslint-disable no-console */ -import { ConsoleMessage, expect } from '@playwright/test'; +import type { ConsoleMessage } from '@playwright/test'; +import { expect } from '@playwright/test'; import { sentryTest } from '../../../../utils/fixtures'; diff --git a/packages/integration-tests/suites/public-api/instrumentation/eventListener/test.ts b/packages/integration-tests/suites/public-api/instrumentation/eventListener/test.ts index 71eb3e7023b3..8d821a14906e 100644 --- a/packages/integration-tests/suites/public-api/instrumentation/eventListener/test.ts +++ b/packages/integration-tests/suites/public-api/instrumentation/eventListener/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/setContext/multiple_contexts/test.ts b/packages/integration-tests/suites/public-api/setContext/multiple_contexts/test.ts index 6d00519cbad4..d1e85f49cf27 100644 --- a/packages/integration-tests/suites/public-api/setContext/multiple_contexts/test.ts +++ b/packages/integration-tests/suites/public-api/setContext/multiple_contexts/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/setContext/non_serializable_context/test.ts b/packages/integration-tests/suites/public-api/setContext/non_serializable_context/test.ts index 54fea2c68908..3c6d17dbdb03 100644 --- a/packages/integration-tests/suites/public-api/setContext/non_serializable_context/test.ts +++ b/packages/integration-tests/suites/public-api/setContext/non_serializable_context/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/setContext/simple_context/test.ts b/packages/integration-tests/suites/public-api/setContext/simple_context/test.ts index a39f838f5b18..37e91dbf314d 100644 --- a/packages/integration-tests/suites/public-api/setContext/simple_context/test.ts +++ b/packages/integration-tests/suites/public-api/setContext/simple_context/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/setExtra/multiple_extras/test.ts b/packages/integration-tests/suites/public-api/setExtra/multiple_extras/test.ts index 82a2b4ce21e5..17e5fa9790c7 100644 --- a/packages/integration-tests/suites/public-api/setExtra/multiple_extras/test.ts +++ b/packages/integration-tests/suites/public-api/setExtra/multiple_extras/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/setExtra/non_serializable_extra/test.ts b/packages/integration-tests/suites/public-api/setExtra/non_serializable_extra/test.ts index 168bfc88e2c5..67b2e9cd162c 100644 --- a/packages/integration-tests/suites/public-api/setExtra/non_serializable_extra/test.ts +++ b/packages/integration-tests/suites/public-api/setExtra/non_serializable_extra/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/setExtra/simple_extra/test.ts b/packages/integration-tests/suites/public-api/setExtra/simple_extra/test.ts index 95a6184e95a9..3f77998cd758 100644 --- a/packages/integration-tests/suites/public-api/setExtra/simple_extra/test.ts +++ b/packages/integration-tests/suites/public-api/setExtra/simple_extra/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/setExtras/consecutive_calls/test.ts b/packages/integration-tests/suites/public-api/setExtras/consecutive_calls/test.ts index 641325affa34..9caae5b0bc7c 100644 --- a/packages/integration-tests/suites/public-api/setExtras/consecutive_calls/test.ts +++ b/packages/integration-tests/suites/public-api/setExtras/consecutive_calls/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/setExtras/multiple_extras/test.ts b/packages/integration-tests/suites/public-api/setExtras/multiple_extras/test.ts index 1e238739d8a1..fdea76a5344a 100644 --- a/packages/integration-tests/suites/public-api/setExtras/multiple_extras/test.ts +++ b/packages/integration-tests/suites/public-api/setExtras/multiple_extras/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/setTag/with_non_primitives/test.ts b/packages/integration-tests/suites/public-api/setTag/with_non_primitives/test.ts index e4a1f9b19bd4..3eff6ec07858 100644 --- a/packages/integration-tests/suites/public-api/setTag/with_non_primitives/test.ts +++ b/packages/integration-tests/suites/public-api/setTag/with_non_primitives/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/setTag/with_primitives/test.ts b/packages/integration-tests/suites/public-api/setTag/with_primitives/test.ts index ba2b648ad913..915c39a51596 100644 --- a/packages/integration-tests/suites/public-api/setTag/with_primitives/test.ts +++ b/packages/integration-tests/suites/public-api/setTag/with_primitives/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/setTags/with_non_primitives/test.ts b/packages/integration-tests/suites/public-api/setTags/with_non_primitives/test.ts index e4a1f9b19bd4..3eff6ec07858 100644 --- a/packages/integration-tests/suites/public-api/setTags/with_non_primitives/test.ts +++ b/packages/integration-tests/suites/public-api/setTags/with_non_primitives/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/setTags/with_primitives/test.ts b/packages/integration-tests/suites/public-api/setTags/with_primitives/test.ts index ba2b648ad913..915c39a51596 100644 --- a/packages/integration-tests/suites/public-api/setTags/with_primitives/test.ts +++ b/packages/integration-tests/suites/public-api/setTags/with_primitives/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/setUser/unset_user/test.ts b/packages/integration-tests/suites/public-api/setUser/unset_user/test.ts index 2aed7beb60aa..193a10b8677d 100644 --- a/packages/integration-tests/suites/public-api/setUser/unset_user/test.ts +++ b/packages/integration-tests/suites/public-api/setUser/unset_user/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getMultipleSentryEnvelopeRequests } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/setUser/update_user/test.ts b/packages/integration-tests/suites/public-api/setUser/update_user/test.ts index fa846f0221c2..f673280d5c0b 100644 --- a/packages/integration-tests/suites/public-api/setUser/update_user/test.ts +++ b/packages/integration-tests/suites/public-api/setUser/update_user/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getMultipleSentryEnvelopeRequests } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/startTransaction/basic_usage/test.ts b/packages/integration-tests/suites/public-api/startTransaction/basic_usage/test.ts index 144f0ae29211..7b42d280248d 100644 --- a/packages/integration-tests/suites/public-api/startTransaction/basic_usage/test.ts +++ b/packages/integration-tests/suites/public-api/startTransaction/basic_usage/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/startTransaction/circular_data/test.ts b/packages/integration-tests/suites/public-api/startTransaction/circular_data/test.ts index 7ad0ec532fb7..88ed63b08864 100644 --- a/packages/integration-tests/suites/public-api/startTransaction/circular_data/test.ts +++ b/packages/integration-tests/suites/public-api/startTransaction/circular_data/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/startTransaction/setMeasurement/test.ts b/packages/integration-tests/suites/public-api/startTransaction/setMeasurement/test.ts index e91231093bf3..fecda098bf43 100644 --- a/packages/integration-tests/suites/public-api/startTransaction/setMeasurement/test.ts +++ b/packages/integration-tests/suites/public-api/startTransaction/setMeasurement/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/public-api/withScope/nested_scopes/test.ts b/packages/integration-tests/suites/public-api/withScope/nested_scopes/test.ts index 1cc024e799fc..415a173bc64f 100644 --- a/packages/integration-tests/suites/public-api/withScope/nested_scopes/test.ts +++ b/packages/integration-tests/suites/public-api/withScope/nested_scopes/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getMultipleSentryEnvelopeRequests } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/replay/captureReplay/template.html b/packages/integration-tests/suites/replay/captureReplay/template.html new file mode 100644 index 000000000000..2b3e2f0b27b4 --- /dev/null +++ b/packages/integration-tests/suites/replay/captureReplay/template.html @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/integration-tests/suites/replay/captureReplay/test.ts b/packages/integration-tests/suites/replay/captureReplay/test.ts new file mode 100644 index 000000000000..51a1d900f9ed --- /dev/null +++ b/packages/integration-tests/suites/replay/captureReplay/test.ts @@ -0,0 +1,67 @@ +import { expect } from '@playwright/test'; +import { SDK_VERSION } from '@sentry/browser'; +import type { Event } from '@sentry/types'; + +import { sentryTest } from '../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest } from '../../../utils/helpers'; + +sentryTest('captureReplay', async ({ getLocalTestPath, page }) => { + // Currently bundle tests are not supported for replay + if (process.env.PW_BUNDLE && process.env.PW_BUNDLE.startsWith('bundle_')) { + sentryTest.skip(); + } + + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const url = await getLocalTestPath({ testDir: __dirname }); + await page.goto(url); + + await page.click('button'); + await page.waitForTimeout(300); + + const replayEvent = await getFirstSentryEnvelopeRequest(page, url); + + expect(replayEvent).toBeDefined(); + expect(replayEvent).toEqual({ + type: 'replay_event', + timestamp: expect.any(Number), + error_ids: [], + trace_ids: [], + urls: [expect.stringContaining('/dist/index.html')], + replay_id: expect.stringMatching(/\w{32}/), + segment_id: 2, + replay_type: 'session', + event_id: expect.stringMatching(/\w{32}/), + environment: 'production', + sdk: { + integrations: [ + 'InboundFilters', + 'FunctionToString', + 'TryCatch', + 'Breadcrumbs', + 'GlobalHandlers', + 'LinkedErrors', + 'Dedupe', + 'HttpContext', + 'Replay', + ], + version: SDK_VERSION, + name: 'sentry.javascript.browser', + }, + sdkProcessingMetadata: {}, + request: { + url: expect.stringContaining('/dist/index.html'), + headers: { + 'User-Agent': expect.stringContaining(''), + }, + }, + platform: 'javascript', + tags: { sessionSampleRate: 1, errorSampleRate: 0 }, + }); +}); diff --git a/packages/integration-tests/suites/replay/init.js b/packages/integration-tests/suites/replay/init.js new file mode 100644 index 000000000000..9050f274417c --- /dev/null +++ b/packages/integration-tests/suites/replay/init.js @@ -0,0 +1,16 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; +window.Replay = new Sentry.Replay({ + flushMinDelay: 200, + initialFlushDelay: 200, +}); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + sampleRate: 0, + replaysSessionSampleRate: 1.0, + replaysOnErrorSampleRate: 0.0, + + integrations: [window.Replay], +}); diff --git a/packages/integration-tests/suites/replay/sampling/init.js b/packages/integration-tests/suites/replay/sampling/init.js new file mode 100644 index 000000000000..67b681515697 --- /dev/null +++ b/packages/integration-tests/suites/replay/sampling/init.js @@ -0,0 +1,16 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; +window.Replay = new Sentry.Replay({ + flushMinDelay: 200, + initialFlushDelay: 200, +}); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + sampleRate: 0, + replaysSessionSampleRate: 0.0, + replaysOnErrorSampleRate: 0.0, + + integrations: [window.Replay], +}); diff --git a/packages/integration-tests/suites/replay/sampling/template.html b/packages/integration-tests/suites/replay/sampling/template.html new file mode 100644 index 000000000000..2b3e2f0b27b4 --- /dev/null +++ b/packages/integration-tests/suites/replay/sampling/template.html @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/integration-tests/suites/replay/sampling/test.ts b/packages/integration-tests/suites/replay/sampling/test.ts new file mode 100644 index 000000000000..9a351ce30f6f --- /dev/null +++ b/packages/integration-tests/suites/replay/sampling/test.ts @@ -0,0 +1,34 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../utils/fixtures'; +import { getReplaySnapshot } from '../../../utils/helpers'; + +sentryTest('sampling', async ({ getLocalTestPath, page }) => { + // Currently bundle tests are not supported for replay + if (process.env.PW_BUNDLE && process.env.PW_BUNDLE.startsWith('bundle_')) { + sentryTest.skip(); + } + + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + // This should never be called! + expect(true).toBe(false); + + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const url = await getLocalTestPath({ testDir: __dirname }); + await page.goto(url); + + await page.click('button'); + await page.waitForTimeout(200); + + const replay = await getReplaySnapshot(page); + + expect(replay.session?.sampled).toBe(false); + + // Cannot wait on getFirstSentryEnvelopeRequest, as that never resolves +}); diff --git a/packages/integration-tests/suites/sessions/start-session/test.ts b/packages/integration-tests/suites/sessions/start-session/test.ts index e4469bd369c1..8a48f161c93b 100644 --- a/packages/integration-tests/suites/sessions/start-session/test.ts +++ b/packages/integration-tests/suites/sessions/start-session/test.ts @@ -1,5 +1,6 @@ -import { expect, Route } from '@playwright/test'; -import { SessionContext } from '@sentry/types'; +import type { Route } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { SessionContext } from '@sentry/types'; import { sentryTest } from '../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../utils/helpers'; diff --git a/packages/integration-tests/suites/sessions/update-session/test.ts b/packages/integration-tests/suites/sessions/update-session/test.ts index 9634e66c360e..5ce88e4bdc0e 100644 --- a/packages/integration-tests/suites/sessions/update-session/test.ts +++ b/packages/integration-tests/suites/sessions/update-session/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { SessionContext } from '@sentry/types'; +import type { SessionContext } from '@sentry/types'; import { sentryTest } from '../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../utils/helpers'; diff --git a/packages/integration-tests/suites/stacktraces/protocol_containing_fn_identifiers/test.ts b/packages/integration-tests/suites/stacktraces/protocol_containing_fn_identifiers/test.ts index 3f1e01f88070..cd79799df328 100644 --- a/packages/integration-tests/suites/stacktraces/protocol_containing_fn_identifiers/test.ts +++ b/packages/integration-tests/suites/stacktraces/protocol_containing_fn_identifiers/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../utils/helpers'; diff --git a/packages/integration-tests/suites/stacktraces/protocol_fn_identifiers/test.ts b/packages/integration-tests/suites/stacktraces/protocol_fn_identifiers/test.ts index 4c3e7cfe067e..ce2142169bdc 100644 --- a/packages/integration-tests/suites/stacktraces/protocol_fn_identifiers/test.ts +++ b/packages/integration-tests/suites/stacktraces/protocol_fn_identifiers/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../utils/helpers'; diff --git a/packages/integration-tests/suites/stacktraces/regular_fn_identifiers/test.ts b/packages/integration-tests/suites/stacktraces/regular_fn_identifiers/test.ts index 1c30a04cd4fc..e7a9f452fe6a 100644 --- a/packages/integration-tests/suites/stacktraces/regular_fn_identifiers/test.ts +++ b/packages/integration-tests/suites/stacktraces/regular_fn_identifiers/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../utils/helpers'; diff --git a/packages/integration-tests/suites/tracing/browsertracing/backgroundtab-custom/test.ts b/packages/integration-tests/suites/tracing/browsertracing/backgroundtab-custom/test.ts index 8a50d643e0c9..8b201b7d7bbf 100644 --- a/packages/integration-tests/suites/tracing/browsertracing/backgroundtab-custom/test.ts +++ b/packages/integration-tests/suites/tracing/browsertracing/backgroundtab-custom/test.ts @@ -1,5 +1,6 @@ -import { expect, JSHandle } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { JSHandle } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/tracing/browsertracing/backgroundtab-pageload/test.ts b/packages/integration-tests/suites/tracing/browsertracing/backgroundtab-pageload/test.ts index 80e99b0ede13..690e6e857409 100644 --- a/packages/integration-tests/suites/tracing/browsertracing/backgroundtab-pageload/test.ts +++ b/packages/integration-tests/suites/tracing/browsertracing/backgroundtab-pageload/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/tracing/browsertracing/interactions/test.ts b/packages/integration-tests/suites/tracing/browsertracing/interactions/test.ts index 9f12b2e76e6d..b9a70ebda3ec 100644 --- a/packages/integration-tests/suites/tracing/browsertracing/interactions/test.ts +++ b/packages/integration-tests/suites/tracing/browsertracing/interactions/test.ts @@ -1,5 +1,6 @@ -import { expect, Route } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Route } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, getMultipleSentryEnvelopeRequests } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/tracing/browsertracing/long-tasks-disabled/test.ts b/packages/integration-tests/suites/tracing/browsertracing/long-tasks-disabled/test.ts index a8c8c2b16798..c392258570d9 100644 --- a/packages/integration-tests/suites/tracing/browsertracing/long-tasks-disabled/test.ts +++ b/packages/integration-tests/suites/tracing/browsertracing/long-tasks-disabled/test.ts @@ -1,5 +1,6 @@ -import { expect, Route } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Route } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/tracing/browsertracing/long-tasks-enabled/test.ts b/packages/integration-tests/suites/tracing/browsertracing/long-tasks-enabled/test.ts index 83bb5911586a..9ee877e39268 100644 --- a/packages/integration-tests/suites/tracing/browsertracing/long-tasks-enabled/test.ts +++ b/packages/integration-tests/suites/tracing/browsertracing/long-tasks-enabled/test.ts @@ -1,5 +1,6 @@ -import { expect, Route } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Route } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; @@ -17,7 +18,7 @@ sentryTest('should capture long task.', async ({ browserName, getLocalTestPath, const eventData = await getFirstSentryEnvelopeRequest(page, url); const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui')); - expect(uiSpans?.length).toBe(1); + expect(uiSpans?.length).toBeGreaterThan(0); const [firstUISpan] = uiSpans || []; expect(firstUISpan).toEqual( diff --git a/packages/integration-tests/suites/tracing/browsertracing/meta/test.ts b/packages/integration-tests/suites/tracing/browsertracing/meta/test.ts index 88ae1cd331d4..c447f41c8660 100644 --- a/packages/integration-tests/suites/tracing/browsertracing/meta/test.ts +++ b/packages/integration-tests/suites/tracing/browsertracing/meta/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event, EventEnvelopeHeaders } from '@sentry/types'; +import type { Event, EventEnvelopeHeaders } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { envelopeHeaderRequestParser, getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/tracing/browsertracing/navigation/test.ts b/packages/integration-tests/suites/tracing/browsertracing/navigation/test.ts index e8e6cbd3fc78..77157951f494 100644 --- a/packages/integration-tests/suites/tracing/browsertracing/navigation/test.ts +++ b/packages/integration-tests/suites/tracing/browsertracing/navigation/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/tracing/browsertracing/pageload/test.ts b/packages/integration-tests/suites/tracing/browsertracing/pageload/test.ts index 6c5877c62e57..fc7de0b067b7 100644 --- a/packages/integration-tests/suites/tracing/browsertracing/pageload/test.ts +++ b/packages/integration-tests/suites/tracing/browsertracing/pageload/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargets/test.ts b/packages/integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargets/test.ts index 5281b549454c..ca1deada91d0 100644 --- a/packages/integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargets/test.ts +++ b/packages/integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargets/test.ts @@ -1,4 +1,5 @@ -import { expect, Request } from '@playwright/test'; +import type { Request } from '@playwright/test'; +import { expect } from '@playwright/test'; import { sentryTest } from '../../../../../utils/fixtures'; diff --git a/packages/integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargetsAndOrigins/test.ts b/packages/integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargetsAndOrigins/test.ts index e39093012d9e..6e3fea23e09b 100644 --- a/packages/integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargetsAndOrigins/test.ts +++ b/packages/integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargetsAndOrigins/test.ts @@ -1,4 +1,5 @@ -import { expect, Request } from '@playwright/test'; +import type { Request } from '@playwright/test'; +import { expect } from '@playwright/test'; import { sentryTest } from '../../../../../utils/fixtures'; diff --git a/packages/integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTracingOrigins/test.ts b/packages/integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTracingOrigins/test.ts index bc54b6bb0687..0983c53a5622 100644 --- a/packages/integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTracingOrigins/test.ts +++ b/packages/integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTracingOrigins/test.ts @@ -1,4 +1,5 @@ -import { expect, Request } from '@playwright/test'; +import type { Request } from '@playwright/test'; +import { expect } from '@playwright/test'; import { sentryTest } from '../../../../../utils/fixtures'; diff --git a/packages/integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsMatch/test.ts b/packages/integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsMatch/test.ts index 3fe23f53e37c..c046912e6621 100644 --- a/packages/integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsMatch/test.ts +++ b/packages/integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsMatch/test.ts @@ -1,4 +1,5 @@ -import { expect, Request } from '@playwright/test'; +import type { Request } from '@playwright/test'; +import { expect } from '@playwright/test'; import { sentryTest } from '../../../../../utils/fixtures'; diff --git a/packages/integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsNoMatch/test.ts b/packages/integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsNoMatch/test.ts index 50bce1fdc8fa..0767ced38bb5 100644 --- a/packages/integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsNoMatch/test.ts +++ b/packages/integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsNoMatch/test.ts @@ -1,4 +1,5 @@ -import { expect, Request } from '@playwright/test'; +import type { Request } from '@playwright/test'; +import { expect } from '@playwright/test'; import { sentryTest } from '../../../../../utils/fixtures'; diff --git a/packages/integration-tests/suites/tracing/envelope-header-transaction-name/test.ts b/packages/integration-tests/suites/tracing/envelope-header-transaction-name/test.ts index 7ef6bd4304e1..8d5b75e4907c 100644 --- a/packages/integration-tests/suites/tracing/envelope-header-transaction-name/test.ts +++ b/packages/integration-tests/suites/tracing/envelope-header-transaction-name/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { EventEnvelopeHeaders } from '@sentry/types'; +import type { EventEnvelopeHeaders } from '@sentry/types'; import { sentryTest } from '../../../utils/fixtures'; import { envelopeHeaderRequestParser, getFirstSentryEnvelopeRequest } from '../../../utils/helpers'; diff --git a/packages/integration-tests/suites/tracing/envelope-header/test.ts b/packages/integration-tests/suites/tracing/envelope-header/test.ts index fef9ae14a8fb..b70ae62b4903 100644 --- a/packages/integration-tests/suites/tracing/envelope-header/test.ts +++ b/packages/integration-tests/suites/tracing/envelope-header/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { EventEnvelopeHeaders } from '@sentry/types'; +import type { EventEnvelopeHeaders } from '@sentry/types'; import { sentryTest } from '../../../utils/fixtures'; import { envelopeHeaderRequestParser, getFirstSentryEnvelopeRequest } from '../../../utils/helpers'; diff --git a/packages/integration-tests/suites/tracing/metrics/connection-rtt/test.ts b/packages/integration-tests/suites/tracing/metrics/connection-rtt/test.ts index e64fd704682d..273609e97d15 100644 --- a/packages/integration-tests/suites/tracing/metrics/connection-rtt/test.ts +++ b/packages/integration-tests/suites/tracing/metrics/connection-rtt/test.ts @@ -1,5 +1,6 @@ -import { expect, Page } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Page } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/tracing/metrics/pageload-browser-spans/test.ts b/packages/integration-tests/suites/tracing/metrics/pageload-browser-spans/test.ts index 475ea7fa4840..8b8ca6b0f7b8 100644 --- a/packages/integration-tests/suites/tracing/metrics/pageload-browser-spans/test.ts +++ b/packages/integration-tests/suites/tracing/metrics/pageload-browser-spans/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/tracing/metrics/pageload-resource-spans/test.ts b/packages/integration-tests/suites/tracing/metrics/pageload-resource-spans/test.ts index abf9c3c06d16..83ae9580d84d 100644 --- a/packages/integration-tests/suites/tracing/metrics/pageload-resource-spans/test.ts +++ b/packages/integration-tests/suites/tracing/metrics/pageload-resource-spans/test.ts @@ -1,5 +1,6 @@ -import { expect, Route } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Route } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/tracing/metrics/web-vitals-cls/test.ts b/packages/integration-tests/suites/tracing/metrics/web-vitals-cls/test.ts index f8848891a39c..41bf941f10d3 100644 --- a/packages/integration-tests/suites/tracing/metrics/web-vitals-cls/test.ts +++ b/packages/integration-tests/suites/tracing/metrics/web-vitals-cls/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/tracing/metrics/web-vitals-fid/test.ts b/packages/integration-tests/suites/tracing/metrics/web-vitals-fid/test.ts index ff3f125fcf10..966760096add 100644 --- a/packages/integration-tests/suites/tracing/metrics/web-vitals-fid/test.ts +++ b/packages/integration-tests/suites/tracing/metrics/web-vitals-fid/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/tracing/metrics/web-vitals-fp-fcp/test.ts b/packages/integration-tests/suites/tracing/metrics/web-vitals-fp-fcp/test.ts index 8162523542ba..b120e580a55c 100644 --- a/packages/integration-tests/suites/tracing/metrics/web-vitals-fp-fcp/test.ts +++ b/packages/integration-tests/suites/tracing/metrics/web-vitals-fp-fcp/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/tracing/metrics/web-vitals-lcp/test.ts b/packages/integration-tests/suites/tracing/metrics/web-vitals-lcp/test.ts index 01a8b5c2e268..28b85d518e80 100644 --- a/packages/integration-tests/suites/tracing/metrics/web-vitals-lcp/test.ts +++ b/packages/integration-tests/suites/tracing/metrics/web-vitals-lcp/test.ts @@ -1,5 +1,6 @@ -import { expect, Route } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Route } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/tracing/metrics/web-vitals-ttfb/test.ts b/packages/integration-tests/suites/tracing/metrics/web-vitals-ttfb/test.ts index 4259c4a034be..81d5f1f7430e 100644 --- a/packages/integration-tests/suites/tracing/metrics/web-vitals-ttfb/test.ts +++ b/packages/integration-tests/suites/tracing/metrics/web-vitals-ttfb/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/tracing/request/fetch/test.ts b/packages/integration-tests/suites/tracing/request/fetch/test.ts index 2ccf9633507a..a5b8185e20f1 100644 --- a/packages/integration-tests/suites/tracing/request/fetch/test.ts +++ b/packages/integration-tests/suites/tracing/request/fetch/test.ts @@ -1,5 +1,6 @@ -import { expect, Request } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Request } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getMultipleSentryEnvelopeRequests } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/suites/tracing/request/xhr/test.ts b/packages/integration-tests/suites/tracing/request/xhr/test.ts index a16c0893b0e7..39b2a37749b8 100644 --- a/packages/integration-tests/suites/tracing/request/xhr/test.ts +++ b/packages/integration-tests/suites/tracing/request/xhr/test.ts @@ -1,5 +1,6 @@ -import { expect, Request } from '@playwright/test'; -import { Event } from '@sentry/types'; +import type { Request } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; diff --git a/packages/integration-tests/utils/generatePlugin.ts b/packages/integration-tests/utils/generatePlugin.ts index acaf711946f3..3e00d97f7803 100644 --- a/packages/integration-tests/utils/generatePlugin.ts +++ b/packages/integration-tests/utils/generatePlugin.ts @@ -1,8 +1,8 @@ -import { Package } from '@sentry/types'; +import type { Package } from '@sentry/types'; import { readdirSync, readFileSync } from 'fs'; import HtmlWebpackPlugin, { createHtmlTagObject } from 'html-webpack-plugin'; import path from 'path'; -import { Compiler } from 'webpack'; +import type { Compiler } from 'webpack'; const PACKAGES_DIR = '../../packages'; @@ -32,6 +32,14 @@ const BUNDLE_PATHS: Record> = { bundle_es6: 'build/bundles/bundle.tracing.js', bundle_es6_min: 'build/bundles/bundle.tracing.min.js', }, + integrations: { + cjs: 'build/npm/cjs/index.js', + esm: 'build/npm/esm/index.js', + bundle_es5: 'build/bundles/[INTEGRATION_NAME].es5.js', + bundle_es5_min: 'build/bundles/[INTEGRATION_NAME].es5.min.js', + bundle_es6: 'build/bundles/[INTEGRATION_NAME].js', + bundle_es6_min: 'build/bundles/[INTEGRATION_NAME].min.js', + }, }; /* @@ -78,6 +86,7 @@ function generateSentryAlias(): Record { class SentryScenarioGenerationPlugin { public requiresTracing: boolean = false; + public requiredIntegrations: string[] = []; private _name: string = 'SentryScenarioGenerationPlugin'; @@ -89,18 +98,24 @@ class SentryScenarioGenerationPlugin { // To help Webpack resolve Sentry modules in `import` statements in cases where they're provided in bundles rather than in `node_modules` '@sentry/browser': 'Sentry', '@sentry/tracing': 'Sentry', + '@sentry/integrations': 'Sentry.Integrations', } : {}; - // Checking if the current scenario has imported `@sentry/tracing`. + // Checking if the current scenario has imported `@sentry/tracing` or `@sentry/integrations`. compiler.hooks.normalModuleFactory.tap(this._name, factory => { factory.hooks.parser.for('javascript/auto').tap(this._name, parser => { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - parser.hooks.import.tap(this._name, (_statement: unknown, source: string) => { - if (source === '@sentry/tracing') { - this.requiresTracing = true; - } - }); + parser.hooks.import.tap( + this._name, + (statement: { specifiers: [{ imported: { name: string } }] }, source: string) => { + if (source === '@sentry/tracing') { + this.requiresTracing = true; + } else if (source === '@sentry/integrations') { + this.requiredIntegrations.push(statement.specifiers[0].imported.name.toLowerCase()); + } + }, + ); }); }); @@ -113,6 +128,18 @@ class SentryScenarioGenerationPlugin { src: path.resolve(PACKAGES_DIR, bundleName, BUNDLE_PATHS[bundleName][bundleKey]), }); + this.requiredIntegrations.forEach(integration => { + const integrationObject = createHtmlTagObject('script', { + src: path.resolve( + PACKAGES_DIR, + 'integrations', + BUNDLE_PATHS['integrations'][bundleKey].replace('[INTEGRATION_NAME]', integration), + ), + }); + + data.assetTags.scripts.unshift(integrationObject); + }); + data.assetTags.scripts.unshift(bundleObject); } diff --git a/packages/integration-tests/utils/helpers.ts b/packages/integration-tests/utils/helpers.ts index 9bacf6e9fbde..54e1cd44a0bb 100644 --- a/packages/integration-tests/utils/helpers.ts +++ b/packages/integration-tests/utils/helpers.ts @@ -1,5 +1,6 @@ -import { Page, Request } from '@playwright/test'; -import { Event, EventEnvelopeHeaders } from '@sentry/types'; +import type { Page, Request } from '@playwright/test'; +import type { ReplayContainer } from '@sentry/replay/build/npm/types/types'; +import type { Event, EventEnvelopeHeaders } from '@sentry/types'; const envelopeUrlRegex = /\.sentry\.io\/api\/\d+\/envelope\//; @@ -8,7 +9,13 @@ const envelopeRequestParser = (request: Request | null): Event => { const envelope = request?.postData() || ''; // Third row of the envelop is the event payload. - return envelope.split('\n').map(line => JSON.parse(line))[2]; + return envelope.split('\n').map(line => { + try { + return JSON.parse(line); + } catch (error) { + return line; + } + })[2]; }; export const envelopeHeaderRequestParser = (request: Request | null): EventEnvelopeHeaders => { @@ -46,24 +53,34 @@ async function getSentryEvents(page: Page, url?: string): Promise> return eventsHandle.jsonValue(); } +/** + * This returns the replay container (assuming it exists). + * Note that due to how this works with playwright, this is a POJO copy of replay. + * This means that we cannot access any methods on it, and also not mutate it in any way. + */ +export async function getReplaySnapshot(page: Page): Promise { + const replayIntegration = await page.evaluate<{ _replay: ReplayContainer }>('window.Replay'); + return replayIntegration._replay; +} + /** * Waits until a number of requests matching urlRgx at the given URL arrive. * If the timout option is configured, this function will abort waiting, even if it hasn't reveived the configured * amount of requests, and returns all the events recieved up to that point in time. */ -async function getMultipleRequests( +async function getMultipleRequests( page: Page, count: number, urlRgx: RegExp, - requestParser: (req: Request) => Event, + requestParser: (req: Request) => T, options?: { url?: string; timeout?: number; }, -): Promise { - const requests: Promise = new Promise((resolve, reject) => { +): Promise { + const requests: Promise = new Promise((resolve, reject) => { let reqCount = count; - const requestData: Event[] = []; + const requestData: T[] = []; let timeoutId: NodeJS.Timeout | undefined = undefined; function requestHandler(request: Request): void { @@ -115,7 +132,7 @@ async function getMultipleSentryEnvelopeRequests( ): Promise { // TODO: This is not currently checking the type of envelope, just casting for now. // We can update this to include optional type-guarding when we have types for Envelope. - return getMultipleRequests(page, count, envelopeUrlRegex, requestParser, options) as Promise; + return getMultipleRequests(page, count, envelopeUrlRegex, requestParser, options) as Promise; } /** diff --git a/packages/integration-tests/webpack.config.ts b/packages/integration-tests/webpack.config.ts index ff065a044208..ddf31bc897c4 100644 --- a/packages/integration-tests/webpack.config.ts +++ b/packages/integration-tests/webpack.config.ts @@ -1,4 +1,4 @@ -import { Configuration } from 'webpack'; +import type { Configuration } from 'webpack'; const config = function (userConfig: Record): Configuration { return { diff --git a/packages/integrations/package.json b/packages/integrations/package.json index 2839e69abb2b..69d50463f337 100644 --- a/packages/integrations/package.json +++ b/packages/integrations/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/integrations", - "version": "7.29.0", + "version": "7.30.0", "description": "Pluggable integrations that can be used to enhance JS SDKs", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/integrations", @@ -16,8 +16,8 @@ "module": "build/npm/esm/index.js", "types": "build/npm/types/index.d.ts", "dependencies": { - "@sentry/types": "7.29.0", - "@sentry/utils": "7.29.0", + "@sentry/types": "7.30.0", + "@sentry/utils": "7.30.0", "localforage": "^1.8.1", "tslib": "^1.9.3" }, @@ -25,16 +25,16 @@ "chai": "^4.1.2" }, "scripts": { - "build": "run-p build:rollup build:types build:bundle", - "build:bundle": "ts-node ../../scripts/ensure-bundle-deps.ts && ts-node scripts/buildBundles.ts --parallel", - "build:dev": "run-p build:rollup build:types", - "build:rollup": "rollup -c rollup.npm.config.js", + "build": "run-p build:transpile build:types build:bundle", + "build:bundle": "ts-node scripts/buildBundles.ts", + "build:dev": "run-p build:transpile build:types", + "build:transpile": "rollup -c rollup.npm.config.js", "build:types": "tsc -p tsconfig.types.json", - "build:watch": "run-p build:rollup:watch build:types:watch", + "build:watch": "run-p build:transpile:watch build:types:watch", "build:dev:watch": "run-s build:watch", - "build:rollup:watch": "rollup -c rollup.npm.config.js --watch", + "build:transpile:watch": "rollup -c rollup.npm.config.js --watch", "build:types:watch": "tsc -p tsconfig.types.json --watch", - "build:npm": "ts-node ../../scripts/prepack.ts --bundles && npm pack ./build/npm", + "build:tarball": "ts-node ../../scripts/prepack.ts --bundles && npm pack ./build/npm", "circularDepCheck": "madge --circular src/index.ts", "clean": "rimraf build coverage .rpt2_cache sentry-integrations-*.tgz", "fix": "run-s fix:eslint fix:prettier", diff --git a/packages/integrations/src/captureconsole.ts b/packages/integrations/src/captureconsole.ts index ea9f2df40837..980bc74dcad6 100644 --- a/packages/integrations/src/captureconsole.ts +++ b/packages/integrations/src/captureconsole.ts @@ -1,4 +1,4 @@ -import { EventProcessor, Hub, Integration } from '@sentry/types'; +import type { EventProcessor, Hub, Integration } from '@sentry/types'; import { CONSOLE_LEVELS, fill, GLOBAL_OBJ, safeJoin, severityLevelFromString } from '@sentry/utils'; /** Send Console API calls as Sentry Events */ diff --git a/packages/integrations/src/debug.ts b/packages/integrations/src/debug.ts index efa1beba35c9..950610e531a0 100644 --- a/packages/integrations/src/debug.ts +++ b/packages/integrations/src/debug.ts @@ -1,4 +1,4 @@ -import { Event, EventHint, EventProcessor, Hub, Integration } from '@sentry/types'; +import type { Event, EventHint, EventProcessor, Hub, Integration } from '@sentry/types'; import { consoleSandbox } from '@sentry/utils'; interface DebugOptions { diff --git a/packages/integrations/src/dedupe.ts b/packages/integrations/src/dedupe.ts index bd615fc084d2..625cc88f504a 100644 --- a/packages/integrations/src/dedupe.ts +++ b/packages/integrations/src/dedupe.ts @@ -1,4 +1,4 @@ -import { Event, EventProcessor, Exception, Hub, Integration, StackFrame } from '@sentry/types'; +import type { Event, EventProcessor, Exception, Hub, Integration, StackFrame } from '@sentry/types'; import { logger } from '@sentry/utils'; /** Deduplication filter */ diff --git a/packages/integrations/src/extraerrordata.ts b/packages/integrations/src/extraerrordata.ts index ef4e6d49393f..8e694b20f2c1 100644 --- a/packages/integrations/src/extraerrordata.ts +++ b/packages/integrations/src/extraerrordata.ts @@ -1,4 +1,4 @@ -import { Contexts, Event, EventHint, EventProcessor, ExtendedError, Hub, Integration } from '@sentry/types'; +import type { Contexts, Event, EventHint, EventProcessor, ExtendedError, Hub, Integration } from '@sentry/types'; import { addNonEnumerableProperty, isError, isPlainObject, logger, normalize } from '@sentry/utils'; /** JSDoc */ diff --git a/packages/integrations/src/httpclient.ts b/packages/integrations/src/httpclient.ts new file mode 100644 index 000000000000..8fab1825fa03 --- /dev/null +++ b/packages/integrations/src/httpclient.ts @@ -0,0 +1,439 @@ +import type { Event as SentryEvent, EventProcessor, Hub, Integration } from '@sentry/types'; +import { addExceptionMechanism, fill, GLOBAL_OBJ, logger, supportsNativeFetch } from '@sentry/utils'; + +export type HttpStatusCodeRange = [number, number] | number; +export type HttpRequestTarget = string | RegExp; +interface HttpClientOptions { + /** + * HTTP status codes that should be considered failed. + * This array can contain tuples of `[begin, end]` (both inclusive), + * single status codes, or a combinations of both + * + * Example: [[500, 505], 507] + * Default: [[500, 599]] + */ + failedRequestStatusCodes?: HttpStatusCodeRange[]; + + /** + * Targets to track for failed requests. + * This array can contain strings or regular expressions. + * + * Example: ['http://localhost', /api\/.*\/] + * Default: [/.*\/] + */ + failedRequestTargets?: HttpRequestTarget[]; +} + +/** HTTPClient integration creates events for failed client side HTTP requests. */ +export class HttpClient implements Integration { + /** + * @inheritDoc + */ + public static id: string = 'HttpClient'; + + /** + * @inheritDoc + */ + public name: string = HttpClient.id; + + private readonly _options: HttpClientOptions; + + /** + * Returns current hub. + */ + private _getCurrentHub?: () => Hub; + + /** + * @inheritDoc + * + * @param options + */ + public constructor(options?: Partial) { + this._options = { + failedRequestStatusCodes: [[500, 599]], + failedRequestTargets: [/.*/], + ...options, + }; + } + + /** + * @inheritDoc + * + * @param options + */ + public setupOnce(_: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void { + this._getCurrentHub = getCurrentHub; + this._wrapFetch(); + this._wrapXHR(); + } + + /** + * Interceptor function for fetch requests + * + * @param requestInfo The Fetch API request info + * @param response The Fetch API response + * @param requestInit The request init object + */ + private _fetchResponseHandler(requestInfo: RequestInfo, response: Response, requestInit?: RequestInit): void { + if (this._getCurrentHub && this._shouldCaptureResponse(response.status, response.url)) { + const request = new Request(requestInfo, requestInit); + const hub = this._getCurrentHub(); + + let requestHeaders, responseHeaders, requestCookies, responseCookies; + + if (hub.shouldSendDefaultPii()) { + [{ headers: requestHeaders, cookies: requestCookies }, { headers: responseHeaders, cookies: responseCookies }] = + [ + { cookieHeader: 'Cookie', obj: request }, + { cookieHeader: 'Set-Cookie', obj: response }, + ].map(({ cookieHeader, obj }) => { + const headers = this._extractFetchHeaders(obj.headers); + let cookies; + + try { + const cookieString = headers[cookieHeader] || headers[cookieHeader.toLowerCase()] || undefined; + + if (cookieString) { + cookies = this._parseCookieString(cookieString); + } + } catch (e) { + __DEBUG_BUILD__ && logger.log(`Could not extract cookies from header ${cookieHeader}`); + } + + return { + headers, + cookies, + }; + }); + } + + const event = this._createEvent({ + url: request.url, + method: request.method, + status: response.status, + requestHeaders, + responseHeaders, + requestCookies, + responseCookies, + }); + + hub.captureEvent(event); + } + } + + /** + * Interceptor function for XHR requests + * + * @param xhr The XHR request + * @param method The HTTP method + * @param headers The HTTP headers + */ + private _xhrResponseHandler(xhr: XMLHttpRequest, method: string, headers: Record): void { + if (this._getCurrentHub && this._shouldCaptureResponse(xhr.status, xhr.responseURL)) { + let requestHeaders, responseCookies, responseHeaders; + const hub = this._getCurrentHub(); + + if (hub.shouldSendDefaultPii()) { + try { + const cookieString = xhr.getResponseHeader('Set-Cookie') || xhr.getResponseHeader('set-cookie') || undefined; + + if (cookieString) { + responseCookies = this._parseCookieString(cookieString); + } + } catch (e) { + __DEBUG_BUILD__ && logger.log('Could not extract cookies from response headers'); + } + + try { + responseHeaders = this._getXHRResponseHeaders(xhr); + } catch (e) { + __DEBUG_BUILD__ && logger.log('Could not extract headers from response'); + } + + requestHeaders = headers; + } + + const event = this._createEvent({ + url: xhr.responseURL, + method: method, + status: xhr.status, + requestHeaders, + // Can't access request cookies from XHR + responseHeaders, + responseCookies, + }); + + hub.captureEvent(event); + } + } + + /** + * Extracts response size from `Content-Length` header when possible + * + * @param headers + * @returns The response size in bytes or undefined + */ + private _getResponseSizeFromHeaders(headers?: Record): number | undefined { + if (headers) { + const contentLength = headers['Content-Length'] || headers['content-length']; + + if (contentLength) { + return parseInt(contentLength, 10); + } + } + + return undefined; + } + + /** + * Creates an object containing cookies from the given cookie string + * + * @param cookieString The cookie string to parse + * @returns The parsed cookies + */ + private _parseCookieString(cookieString: string): Record { + return cookieString.split('; ').reduce((acc: Record, cookie: string) => { + const [key, value] = cookie.split('='); + acc[key] = value; + return acc; + }, {}); + } + + /** + * Extracts the headers as an object from the given Fetch API request or response object + * + * @param headers The headers to extract + * @returns The extracted headers as an object + */ + private _extractFetchHeaders(headers: Headers): Record { + const result: Record = {}; + + headers.forEach((value, key) => { + result[key] = value; + }); + + return result; + } + + /** + * Extracts the response headers as an object from the given XHR object + * + * @param xhr The XHR object to extract the response headers from + * @returns The response headers as an object + */ + private _getXHRResponseHeaders(xhr: XMLHttpRequest): Record { + const headers = xhr.getAllResponseHeaders(); + + if (!headers) { + return {}; + } + + return headers.split('\r\n').reduce((acc: Record, line: string) => { + const [key, value] = line.split(': '); + acc[key] = value; + return acc; + }, {}); + } + + /** + * Checks if the given target url is in the given list of targets + * + * @param target The target url to check + * @returns true if the target url is in the given list of targets, false otherwise + */ + private _isInGivenRequestTargets(target: string): boolean { + if (!this._options.failedRequestTargets) { + return false; + } + + return this._options.failedRequestTargets.some((givenRequestTarget: HttpRequestTarget) => { + if (typeof givenRequestTarget === 'string') { + return target.includes(givenRequestTarget); + } + + return givenRequestTarget.test(target); + }); + } + + /** + * Checks if the given status code is in the given range + * + * @param status The status code to check + * @returns true if the status code is in the given range, false otherwise + */ + private _isInGivenStatusRanges(status: number): boolean { + if (!this._options.failedRequestStatusCodes) { + return false; + } + + return this._options.failedRequestStatusCodes.some((range: HttpStatusCodeRange) => { + if (typeof range === 'number') { + return range === status; + } + + return status >= range[0] && status <= range[1]; + }); + } + + /** + * Wraps `fetch` function to capture request and response data + */ + private _wrapFetch(): void { + if (!supportsNativeFetch()) { + return; + } + + // eslint-disable-next-line @typescript-eslint/no-this-alias + const self = this; + + fill(GLOBAL_OBJ, 'fetch', function (originalFetch: (...args: unknown[]) => Promise) { + return function (this: Window, ...args: unknown[]): Promise { + const [requestInfo, requestInit] = args as [RequestInfo, RequestInit | undefined]; + const responsePromise: Promise = originalFetch.apply(this, args); + + responsePromise + .then((response: Response) => { + self._fetchResponseHandler(requestInfo, response, requestInit); + return response; + }) + .catch((error: Error) => { + throw error; + }); + + return responsePromise; + }; + }); + } + + /** + * Wraps XMLHttpRequest to capture request and response data + */ + private _wrapXHR(): void { + if (!('XMLHttpRequest' in GLOBAL_OBJ)) { + return; + } + + // eslint-disable-next-line @typescript-eslint/no-this-alias + const self = this; + + fill(XMLHttpRequest.prototype, 'open', function (originalOpen: (...openArgs: unknown[]) => void): () => void { + return function (this: XMLHttpRequest, ...openArgs: unknown[]): void { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const xhr = this; + const method = openArgs[0] as string; + const headers: Record = {}; + + // Intercepting `setRequestHeader` to access the request headers of XHR instance. + // This will only work for user/library defined headers, not for the default/browser-assigned headers. + // Request cookies are also unavailable for XHR, as `Cookie` header can't be defined by `setRequestHeader`. + fill( + xhr, + 'setRequestHeader', + // eslint-disable-next-line @typescript-eslint/ban-types + function (originalSetRequestHeader: (...setRequestHeaderArgs: unknown[]) => void): Function { + return function (...setRequestHeaderArgs: unknown[]): void { + const [header, value] = setRequestHeaderArgs as [string, string]; + + headers[header] = value; + + return originalSetRequestHeader.apply(xhr, setRequestHeaderArgs); + }; + }, + ); + + // eslint-disable-next-line @typescript-eslint/ban-types + fill(xhr, 'onloadend', function (original?: (...onloadendArgs: unknown[]) => void): Function { + return function (...onloadendArgs: unknown[]): void { + try { + self._xhrResponseHandler(xhr, method, headers); + } catch (e) { + __DEBUG_BUILD__ && logger.warn('Error while extracting response event form XHR response', e); + } + + if (original) { + return original.apply(xhr, onloadendArgs); + } + }; + }); + + return originalOpen.apply(this, openArgs); + }; + }); + } + + /** + * Checks whether given url points to Sentry server + * + * @param url url to verify + */ + private _isSentryRequest(url: string): boolean { + const client = this._getCurrentHub && this._getCurrentHub().getClient(); + + if (!client) { + return false; + } + + const dsn = client.getDsn(); + return dsn ? url.includes(dsn.host) : false; + } + + /** + * Checks whether to capture given response as an event + * + * @param status response status code + * @param url response url + */ + private _shouldCaptureResponse(status: number, url: string): boolean { + return this._isInGivenStatusRanges(status) && this._isInGivenRequestTargets(url) && !this._isSentryRequest(url); + } + + /** + * Creates a synthetic Sentry event from given response data + * + * @param data response data + * @returns event + */ + private _createEvent(data: { + url: string; + method: string; + status: number; + responseHeaders?: Record; + responseCookies?: Record; + requestHeaders?: Record; + requestCookies?: Record; + }): SentryEvent { + const message = `HTTP Client Error with status code: ${data.status}`; + + const event: SentryEvent = { + message, + exception: { + values: [ + { + type: 'Error', + value: message, + }, + ], + }, + request: { + url: data.url, + method: data.method, + headers: data.requestHeaders, + cookies: data.requestCookies, + }, + contexts: { + response: { + status_code: data.status, + headers: data.responseHeaders, + cookies: data.responseCookies, + body_size: this._getResponseSizeFromHeaders(data.responseHeaders), + }, + }, + }; + + addExceptionMechanism(event, { + type: 'http.client', + }); + + return event; + } +} diff --git a/packages/integrations/src/index.ts b/packages/integrations/src/index.ts index 9a2573ee5a44..2f3708075ac1 100644 --- a/packages/integrations/src/index.ts +++ b/packages/integrations/src/index.ts @@ -7,3 +7,4 @@ export { ReportingObserver } from './reportingobserver'; export { RewriteFrames } from './rewriteframes'; export { SessionTiming } from './sessiontiming'; export { Transaction } from './transaction'; +export { HttpClient } from './httpclient'; diff --git a/packages/integrations/src/offline.ts b/packages/integrations/src/offline.ts index 2f2029b67336..e20ea96d8e14 100644 --- a/packages/integrations/src/offline.ts +++ b/packages/integrations/src/offline.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ -import { Event, EventProcessor, Hub, Integration } from '@sentry/types'; +import type { Event, EventProcessor, Hub, Integration } from '@sentry/types'; import { GLOBAL_OBJ, logger, normalize, uuid4 } from '@sentry/utils'; import localForage from 'localforage'; diff --git a/packages/integrations/src/reportingobserver.ts b/packages/integrations/src/reportingobserver.ts index 4b8a7d92881c..87f8763cb28a 100644 --- a/packages/integrations/src/reportingobserver.ts +++ b/packages/integrations/src/reportingobserver.ts @@ -1,4 +1,4 @@ -import { EventProcessor, Hub, Integration } from '@sentry/types'; +import type { EventProcessor, Hub, Integration } from '@sentry/types'; import { GLOBAL_OBJ, supportsReportingObserver } from '@sentry/utils'; const WINDOW = GLOBAL_OBJ as typeof GLOBAL_OBJ & Window; diff --git a/packages/integrations/src/rewriteframes.ts b/packages/integrations/src/rewriteframes.ts index b5b830eb557e..afe084f50411 100644 --- a/packages/integrations/src/rewriteframes.ts +++ b/packages/integrations/src/rewriteframes.ts @@ -1,4 +1,4 @@ -import { Event, EventProcessor, Hub, Integration, StackFrame, Stacktrace } from '@sentry/types'; +import type { Event, EventProcessor, Hub, Integration, StackFrame, Stacktrace } from '@sentry/types'; import { basename, relative } from '@sentry/utils'; type StackFrameIteratee = (frame: StackFrame) => StackFrame; diff --git a/packages/integrations/src/sessiontiming.ts b/packages/integrations/src/sessiontiming.ts index 0e45588e0f31..269ca4148ac2 100644 --- a/packages/integrations/src/sessiontiming.ts +++ b/packages/integrations/src/sessiontiming.ts @@ -1,4 +1,4 @@ -import { Event, EventProcessor, Hub, Integration } from '@sentry/types'; +import type { Event, EventProcessor, Hub, Integration } from '@sentry/types'; /** This function adds duration since Sentry was initialized till the time event was sent */ export class SessionTiming implements Integration { diff --git a/packages/integrations/src/transaction.ts b/packages/integrations/src/transaction.ts index e6d0ac24f770..8fbd52a8a871 100644 --- a/packages/integrations/src/transaction.ts +++ b/packages/integrations/src/transaction.ts @@ -1,4 +1,4 @@ -import { Event, EventProcessor, Hub, Integration, StackFrame } from '@sentry/types'; +import type { Event, EventProcessor, Hub, Integration, StackFrame } from '@sentry/types'; /** Add node transaction to the event */ export class Transaction implements Integration { diff --git a/packages/integrations/test/captureconsole.test.ts b/packages/integrations/test/captureconsole.test.ts index 34b715cfc7a1..6ff08df2e46f 100644 --- a/packages/integrations/test/captureconsole.test.ts +++ b/packages/integrations/test/captureconsole.test.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/unbound-method */ -import { Event, Hub, Integration } from '@sentry/types'; +import type { Event, Hub, Integration } from '@sentry/types'; import { CaptureConsole } from '../src/captureconsole'; diff --git a/packages/integrations/test/debug.test.ts b/packages/integrations/test/debug.test.ts index eed7e52d509e..268c03dcc4d5 100644 --- a/packages/integrations/test/debug.test.ts +++ b/packages/integrations/test/debug.test.ts @@ -1,4 +1,4 @@ -import { EventProcessor, Integration } from '@sentry/types'; +import type { EventProcessor, Integration } from '@sentry/types'; import { Debug } from '../src/debug'; diff --git a/packages/integrations/test/dedupe.test.ts b/packages/integrations/test/dedupe.test.ts index 8bc354ffa620..33704907dc83 100644 --- a/packages/integrations/test/dedupe.test.ts +++ b/packages/integrations/test/dedupe.test.ts @@ -1,4 +1,4 @@ -import { Event as SentryEvent, Exception, StackFrame, Stacktrace } from '@sentry/types'; +import type { Event as SentryEvent, Exception, StackFrame, Stacktrace } from '@sentry/types'; import { _shouldDropEvent } from '../src/dedupe'; diff --git a/packages/integrations/test/extraerrordata.test.ts b/packages/integrations/test/extraerrordata.test.ts index 68e38720f761..2ecee1faae19 100644 --- a/packages/integrations/test/extraerrordata.test.ts +++ b/packages/integrations/test/extraerrordata.test.ts @@ -1,4 +1,4 @@ -import { Event as SentryEvent, ExtendedError } from '@sentry/types'; +import type { Event as SentryEvent, ExtendedError } from '@sentry/types'; import { ExtraErrorData } from '../src/extraerrordata'; diff --git a/packages/integrations/test/offline.test.ts b/packages/integrations/test/offline.test.ts index 02e9ccf4457c..d7ca8099be31 100644 --- a/packages/integrations/test/offline.test.ts +++ b/packages/integrations/test/offline.test.ts @@ -1,7 +1,8 @@ import { WINDOW } from '@sentry/browser'; -import { Event, EventProcessor, Hub, Integration, IntegrationClass } from '@sentry/types'; +import type { Event, EventProcessor, Hub, Integration, IntegrationClass } from '@sentry/types'; -import { Item, Offline } from '../src/offline'; +import type { Item } from '../src/offline'; +import { Offline } from '../src/offline'; // mock localforage methods jest.mock('localforage', () => ({ diff --git a/packages/integrations/test/reportingobserver.test.ts b/packages/integrations/test/reportingobserver.test.ts index 91547d5572f8..1298fb617e56 100644 --- a/packages/integrations/test/reportingobserver.test.ts +++ b/packages/integrations/test/reportingobserver.test.ts @@ -1,4 +1,4 @@ -import { Hub, Integration } from '@sentry/types'; +import type { Hub, Integration } from '@sentry/types'; import { ReportingObserver } from '../src/reportingobserver'; diff --git a/packages/integrations/test/rewriteframes.test.ts b/packages/integrations/test/rewriteframes.test.ts index 78aeec7949d4..b19e70143273 100644 --- a/packages/integrations/test/rewriteframes.test.ts +++ b/packages/integrations/test/rewriteframes.test.ts @@ -1,4 +1,4 @@ -import { Event, StackFrame } from '@sentry/types'; +import type { Event, StackFrame } from '@sentry/types'; import { RewriteFrames } from '../src/rewriteframes'; diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 43d1483e5007..1b5ebf5da4c1 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/nextjs", - "version": "7.29.0", + "version": "7.30.0", "description": "Official Sentry SDK for Next.js", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/nextjs", @@ -9,30 +9,27 @@ "engines": { "node": ">=8" }, - "main": "build/cjs/index.server.js", - "module": "build/esm/index.server.js", - "browser": "build/esm/index.client.js", - "types": "build/types/index.server.d.ts", + "main": "build/cjs/index.js", + "module": "build/esm/index.js", + "types": "build/types/index.types.d.ts", "publishConfig": { "access": "public" }, "dependencies": { - "@rollup/plugin-sucrase": "4.0.4", - "@rollup/plugin-virtual": "3.0.0", - "@sentry/core": "7.29.0", - "@sentry/integrations": "7.29.0", - "@sentry/node": "7.29.0", - "@sentry/react": "7.29.0", - "@sentry/tracing": "7.29.0", - "@sentry/types": "7.29.0", - "@sentry/utils": "7.29.0", + "@rollup/plugin-commonjs": "24.0.0", + "@sentry/core": "7.30.0", + "@sentry/integrations": "7.30.0", + "@sentry/node": "7.30.0", + "@sentry/react": "7.30.0", + "@sentry/tracing": "7.30.0", + "@sentry/types": "7.30.0", + "@sentry/utils": "7.30.0", "@sentry/webpack-plugin": "1.20.0", "chalk": "3.0.0", "rollup": "2.78.0", "tslib": "^1.9.3" }, "devDependencies": { - "@sentry/nextjs": "7.29.0", "@types/webpack": "^4.41.31", "eslint-plugin-react": "^7.31.11", "next": "10.1.3" @@ -48,16 +45,16 @@ } }, "scripts": { - "build": "run-p build:rollup build:types", + "build": "run-p build:transpile build:types", "build:dev": "run-s build", - "build:rollup": "ts-node scripts/buildRollup.ts", + "build:transpile": "ts-node scripts/buildRollup.ts", "build:types": "tsc -p tsconfig.types.json", - "build:watch": "run-p build:rollup:watch build:types:watch", + "build:watch": "run-p build:transpile:watch build:types:watch", "build:dev:watch": "run-s build:watch", - "build:rollup:watch": "nodemon --ext ts --watch src scripts/buildRollup.ts", + "build:transpile:watch": "nodemon --ext ts --watch src scripts/buildRollup.ts", "build:types:watch": "tsc -p tsconfig.types.json --watch", - "build:npm": "ts-node ../../scripts/prepack.ts && npm pack ./build", - "circularDepCheck": "madge --circular src/index.client.ts && madge --circular --exclude 'config/types\\.ts' src/index.server.ts # see https://github.com/pahen/madge/issues/306", + "build:tarball": "ts-node ../../scripts/prepack.ts && npm pack ./build", + "circularDepCheck": "madge --circular src/index.ts && madge --circular src/client/index.ts && madge --circular src/index.types.ts", "clean": "rimraf build coverage sentry-nextjs-*.tgz", "fix": "run-s fix:eslint fix:prettier", "fix:eslint": "eslint . --format stylish --fix", @@ -77,10 +74,5 @@ }, "volta": { "extends": "../../package.json" - }, - "sideEffects": [ - "./cjs/index.server.js", - "./esm/index.server.js", - "./src/index.server.ts" - ] + } } diff --git a/packages/nextjs/rollup.npm.config.js b/packages/nextjs/rollup.npm.config.js index 32100ef278be..db1fa9c1fde1 100644 --- a/packages/nextjs/rollup.npm.config.js +++ b/packages/nextjs/rollup.npm.config.js @@ -5,24 +5,16 @@ export default [ makeBaseNPMConfig({ // We need to include `instrumentServer.ts` separately because it's only conditionally required, and so rollup // doesn't automatically include it when calculating the module dependency tree. - entrypoints: [ - 'src/index.server.ts', - 'src/index.client.ts', - 'src/utils/instrumentServer.ts', - 'src/config/webpack.ts', - ], + entrypoints: ['src/index.ts', 'src/client/index.ts', 'src/edge/index.ts', 'src/config/webpack.ts'], // prevent this internal nextjs code from ending up in our built package (this doesn't happen automatially because // the name doesn't match an SDK dependency) - packageSpecificConfig: { external: ['next/router'] }, + packageSpecificConfig: { external: ['next/router', 'next/constants'] }, }), ), ...makeNPMConfigVariants( makeBaseNPMConfig({ - entrypoints: [ - 'src/config/templates/pageProxyLoaderTemplate.ts', - 'src/config/templates/apiProxyLoaderTemplate.ts', - ], + entrypoints: ['src/config/templates/pageWrapperTemplate.ts', 'src/config/templates/apiWrapperTemplate.ts'], packageSpecificConfig: { output: { @@ -37,15 +29,13 @@ export default [ // make it so Rollup calms down about the fact that we're combining default and named exports exports: 'named', }, - external: ['@sentry/nextjs', '__RESOURCE_PATH__'], + external: ['@sentry/nextjs', '__SENTRY_WRAPPING_TARGET__'], }, }), ), ...makeNPMConfigVariants( makeBaseNPMConfig({ entrypoints: ['src/config/loaders/index.ts'], - // Needed in order to successfully import sucrase - esModuleInterop: true, packageSpecificConfig: { output: { @@ -55,7 +45,7 @@ export default [ // make it so Rollup calms down about the fact that we're combining default and named exports exports: 'named', }, - external: ['@rollup/plugin-sucrase', 'rollup'], + external: ['@rollup/plugin-commonjs', 'rollup'], }, }), ), diff --git a/packages/nextjs/src/index.client.ts b/packages/nextjs/src/client/index.ts similarity index 68% rename from packages/nextjs/src/index.client.ts rename to packages/nextjs/src/client/index.ts index 2d9e29d48ac8..3ea78374290d 100644 --- a/packages/nextjs/src/index.client.ts +++ b/packages/nextjs/src/client/index.ts @@ -1,18 +1,17 @@ import { RewriteFrames } from '@sentry/integrations'; +import type { BrowserOptions } from '@sentry/react'; import { configureScope, init as reactInit, Integrations } from '@sentry/react'; import { BrowserTracing, defaultRequestInstrumentationOptions, hasTracingEnabled } from '@sentry/tracing'; -import { EventProcessor } from '@sentry/types'; -import { logger } from '@sentry/utils'; +import type { EventProcessor } from '@sentry/types'; -import { nextRouterInstrumentation } from './performance/client'; -import { buildMetadata } from './utils/metadata'; -import { NextjsOptions } from './utils/nextjsOptions'; -import { applyTunnelRouteOption } from './utils/tunnelRoute'; -import { addOrUpdateIntegration } from './utils/userIntegrations'; +import { buildMetadata } from '../common/metadata'; +import { addOrUpdateIntegration } from '../common/userIntegrations'; +import { nextRouterInstrumentation } from './performance'; +import { applyTunnelRouteOption } from './tunnelRoute'; export * from '@sentry/react'; -export { nextRouterInstrumentation } from './performance/client'; -export { captureUnderscoreErrorException } from './utils/_error'; +export { nextRouterInstrumentation } from './performance'; +export { captureUnderscoreErrorException } from '../common/_error'; export { Integrations }; @@ -31,26 +30,12 @@ export { BrowserTracing }; // Treeshakable guard to remove all code related to tracing declare const __SENTRY_TRACING__: boolean; -// This is a variable that Next.js will string replace during build with a string if run in an edge runtime from Next.js -// v12.2.1-canary.3 onwards: -// https://github.com/vercel/next.js/blob/166e5fb9b92f64c4b5d1f6560a05e2b9778c16fb/packages/next/build/webpack-config.ts#L206 -// https://edge-runtime.vercel.sh/features/available-apis#addressing-the-runtime -declare const EdgeRuntime: string | undefined; - const globalWithInjectedValues = global as typeof global & { __rewriteFramesAssetPrefixPath__: string; }; /** Inits the Sentry NextJS SDK on the browser with the React SDK. */ -export function init(options: NextjsOptions): void { - if (typeof EdgeRuntime === 'string') { - // If the SDK is imported when using the Vercel Edge Runtime, it will import the browser SDK, even though it is - // running the server part of a Next.js application. We can use the `EdgeRuntime` to check for that case and make - // the init call a no-op. This will prevent the SDK from crashing on the Edge Runtime. - __DEBUG_BUILD__ && logger.log('Vercel Edge Runtime detected. Will not initialize SDK.'); - return; - } - +export function init(options: BrowserOptions): void { applyTunnelRouteOption(options); buildMetadata(options, ['nextjs', 'react']); options.environment = options.environment || process.env.NODE_ENV; @@ -67,7 +52,7 @@ export function init(options: NextjsOptions): void { }); } -function addClientIntegrations(options: NextjsOptions): void { +function addClientIntegrations(options: BrowserOptions): void { let integrations = options.integrations || []; // This value is injected at build time, based on the output directory specified in the build config. Though a default diff --git a/packages/nextjs/src/performance/client.ts b/packages/nextjs/src/client/performance.ts similarity index 98% rename from packages/nextjs/src/performance/client.ts rename to packages/nextjs/src/client/performance.ts index 39c52f15356f..2212092d5045 100644 --- a/packages/nextjs/src/performance/client.ts +++ b/packages/nextjs/src/client/performance.ts @@ -1,6 +1,6 @@ import { getCurrentHub } from '@sentry/core'; import { WINDOW } from '@sentry/react'; -import { Primitive, TraceparentData, Transaction, TransactionContext, TransactionSource } from '@sentry/types'; +import type { Primitive, TraceparentData, Transaction, TransactionContext, TransactionSource } from '@sentry/types'; import { baggageHeaderToDynamicSamplingContext, extractTraceparentData, diff --git a/packages/nextjs/src/utils/tunnelRoute.ts b/packages/nextjs/src/client/tunnelRoute.ts similarity index 88% rename from packages/nextjs/src/utils/tunnelRoute.ts rename to packages/nextjs/src/client/tunnelRoute.ts index 16d4ce9b71cc..81f9f936cc82 100644 --- a/packages/nextjs/src/utils/tunnelRoute.ts +++ b/packages/nextjs/src/client/tunnelRoute.ts @@ -1,7 +1,6 @@ +import type { BrowserOptions } from '@sentry/react'; import { dsnFromString, logger } from '@sentry/utils'; -import { NextjsOptions } from './nextjsOptions'; - const globalWithInjectedValues = global as typeof global & { __sentryRewritesTunnelPath__?: string; }; @@ -9,7 +8,7 @@ const globalWithInjectedValues = global as typeof global & { /** * Applies the `tunnel` option to the Next.js SDK options based on `withSentryConfig`'s `tunnelRoute` option. */ -export function applyTunnelRouteOption(options: NextjsOptions): void { +export function applyTunnelRouteOption(options: BrowserOptions): void { const tunnelRouteOption = globalWithInjectedValues.__sentryRewritesTunnelPath__; if (tunnelRouteOption && options.dsn) { const dsnComponents = dsnFromString(options.dsn); diff --git a/packages/nextjs/src/utils/_error.ts b/packages/nextjs/src/common/_error.ts similarity index 98% rename from packages/nextjs/src/utils/_error.ts rename to packages/nextjs/src/common/_error.ts index 6508eb39ba6e..624aabdb3b98 100644 --- a/packages/nextjs/src/utils/_error.ts +++ b/packages/nextjs/src/common/_error.ts @@ -1,6 +1,6 @@ import { captureException, getCurrentHub, withScope } from '@sentry/core'; import { addExceptionMechanism } from '@sentry/utils'; -import { NextPageContext } from 'next'; +import type { NextPageContext } from 'next'; type ContextOrProps = { req?: NextPageContext['req']; diff --git a/packages/nextjs/src/utils/metadata.ts b/packages/nextjs/src/common/metadata.ts similarity index 92% rename from packages/nextjs/src/utils/metadata.ts rename to packages/nextjs/src/common/metadata.ts index 7985ad6aa9cb..9216c6eaec07 100644 --- a/packages/nextjs/src/utils/metadata.ts +++ b/packages/nextjs/src/common/metadata.ts @@ -1,5 +1,5 @@ import { SDK_VERSION } from '@sentry/core'; -import { Options, SdkInfo } from '@sentry/types'; +import type { Options, SdkInfo } from '@sentry/types'; const PACKAGE_NAME_PREFIX = 'npm:@sentry/'; diff --git a/packages/nextjs/src/utils/userIntegrations.ts b/packages/nextjs/src/common/userIntegrations.ts similarity index 98% rename from packages/nextjs/src/utils/userIntegrations.ts rename to packages/nextjs/src/common/userIntegrations.ts index 3f84a58fa41b..119d5db500dd 100644 --- a/packages/nextjs/src/utils/userIntegrations.ts +++ b/packages/nextjs/src/common/userIntegrations.ts @@ -1,4 +1,4 @@ -import { Integration } from '@sentry/types'; +import type { Integration } from '@sentry/types'; export type UserIntegrationsFunction = (integrations: Integration[]) => Integration[]; export type UserIntegrations = Integration[] | UserIntegrationsFunction; diff --git a/packages/nextjs/src/config/index.ts b/packages/nextjs/src/config/index.ts new file mode 100644 index 000000000000..f537125a75f3 --- /dev/null +++ b/packages/nextjs/src/config/index.ts @@ -0,0 +1,2 @@ +export type { SentryWebpackPluginOptions } from './types'; +export { withSentryConfig } from './withSentryConfig'; diff --git a/packages/nextjs/src/config/loaders/index.ts b/packages/nextjs/src/config/loaders/index.ts index 150e00bf1ca4..27620e004f39 100644 --- a/packages/nextjs/src/config/loaders/index.ts +++ b/packages/nextjs/src/config/loaders/index.ts @@ -1,3 +1,4 @@ export { default as valueInjectionLoader } from './valueInjectionLoader'; export { default as prefixLoader } from './prefixLoader'; -export { default as proxyLoader } from './proxyLoader'; +export { default as wrappingLoader } from './wrappingLoader'; +export { default as sdkMultiplexerLoader } from './sdkMultiplexerLoader'; diff --git a/packages/nextjs/src/config/loaders/prefixLoader.ts b/packages/nextjs/src/config/loaders/prefixLoader.ts index f5aa562b5290..19e728f3b6b3 100644 --- a/packages/nextjs/src/config/loaders/prefixLoader.ts +++ b/packages/nextjs/src/config/loaders/prefixLoader.ts @@ -2,7 +2,7 @@ import { escapeStringForRegex } from '@sentry/utils'; import * as fs from 'fs'; import * as path from 'path'; -import { LoaderThis } from './types'; +import type { LoaderThis } from './types'; type LoaderOptions = { templatePrefix: string; diff --git a/packages/nextjs/src/config/loaders/proxyLoader.ts b/packages/nextjs/src/config/loaders/proxyLoader.ts deleted file mode 100644 index c6bee0ec9f3a..000000000000 --- a/packages/nextjs/src/config/loaders/proxyLoader.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { escapeStringForRegex, logger, stringMatchesSomePattern } from '@sentry/utils'; -import * as fs from 'fs'; -import * as path from 'path'; - -import { rollupize } from './rollup'; -import { LoaderThis } from './types'; - -type LoaderOptions = { - pagesDir: string; - pageExtensionRegex: string; - excludeServerRoutes: Array; -}; - -/** - * Replace the loaded file with a proxy module "wrapping" the original file. In the proxy, the original file is loaded, - * any data-fetching functions (`getInitialProps`, `getStaticProps`, and `getServerSideProps`) it contains are wrapped, - * and then everything is re-exported. - */ -export default async function proxyLoader(this: LoaderThis, userCode: string): Promise { - // We know one or the other will be defined, depending on the version of webpack being used - const { - pagesDir, - pageExtensionRegex, - excludeServerRoutes = [], - } = 'getOptions' in this ? this.getOptions() : this.query; - - // Get the parameterized route name from this page's filepath - const parameterizedRoute = path - // Get the path of the file insde of the pages directory - .relative(pagesDir, this.resourcePath) - // Add a slash at the beginning - .replace(/(.*)/, '/$1') - // Pull off the file extension - .replace(new RegExp(`\\.(${pageExtensionRegex})`), '') - // Any page file named `index` corresponds to root of the directory its in, URL-wise, so turn `/xyz/index` into - // just `/xyz` - .replace(/\/index$/, '') - // In case all of the above have left us with an empty string (which will happen if we're dealing with the - // homepage), sub back in the root route - .replace(/^$/, '/'); - - // Skip explicitly-ignored pages - if (stringMatchesSomePattern(parameterizedRoute, excludeServerRoutes, true)) { - return userCode; - } - - // We don't want to wrap twice (or infinitely), so in the proxy we add this query string onto references to the - // wrapped file, so that we know that it's already been processed. (Adding this query string is also necessary to - // convince webpack that it's a different file than the one it's in the middle of loading now, so that the originals - // themselves will have a chance to load.) - if (this.resourceQuery.includes('__sentry_wrapped__')) { - return userCode; - } - - const templateFile = parameterizedRoute.startsWith('/api') - ? 'apiProxyLoaderTemplate.js' - : 'pageProxyLoaderTemplate.js'; - const templatePath = path.resolve(__dirname, `../templates/${templateFile}`); - let templateCode = fs.readFileSync(templatePath).toString(); - // Make sure the template is included when running `webpack watch` - this.addDependency(templatePath); - - // Inject the route and the path to the file we're wrapping into the template - templateCode = templateCode.replace(/__ROUTE__/g, parameterizedRoute.replace(/\\/g, '\\\\')); - templateCode = templateCode.replace(/__RESOURCE_PATH__/g, this.resourcePath.replace(/\\/g, '\\\\')); - - // Run the proxy module code through Rollup, in order to split the `export * from ''` out into - // individual exports (which nextjs seems to require). - let proxyCode; - try { - proxyCode = await rollupize(templateCode, this.resourcePath); - } catch (err) { - __DEBUG_BUILD__ && - logger.warn( - `Could not wrap ${this.resourcePath}. An error occurred while processing the proxy module template:\n${err}`, - ); - return userCode; - } - - // Add a query string onto all references to the wrapped file, so that webpack will consider it different from the - // non-query-stringged version (which we're already in the middle of loading as we speak), and load it separately from - // this. When the second load happens this loader will run again, but we'll be able to see the query string and will - // know to immediately return without processing. This avoids an infinite loop. - const resourceFilename = path.basename(this.resourcePath); - proxyCode = proxyCode.replace( - new RegExp(`/${escapeStringForRegex(resourceFilename)}'`, 'g'), - `/${resourceFilename}?__sentry_wrapped__'`, - ); - - return proxyCode; -} diff --git a/packages/nextjs/src/config/loaders/rollup.ts b/packages/nextjs/src/config/loaders/rollup.ts deleted file mode 100644 index 6c7861827b95..000000000000 --- a/packages/nextjs/src/config/loaders/rollup.ts +++ /dev/null @@ -1,104 +0,0 @@ -import sucrase from '@rollup/plugin-sucrase'; -import virtual from '@rollup/plugin-virtual'; -import { escapeStringForRegex } from '@sentry/utils'; -import * as path from 'path'; -import type { InputOptions as RollupInputOptions, OutputOptions as RollupOutputOptions } from 'rollup'; -import { rollup } from 'rollup'; - -const SENTRY_PROXY_MODULE_NAME = 'sentry-proxy-module'; - -const getRollupInputOptions = (templateCode: string, userModulePath: string): RollupInputOptions => ({ - input: SENTRY_PROXY_MODULE_NAME, - - plugins: [ - virtual({ - [SENTRY_PROXY_MODULE_NAME]: templateCode, - }), - - sucrase({ - transforms: ['jsx', 'typescript'], - }), - ], - - // We want to process as few files as possible, so as not to slow down the build any more than we have to. We need the - // proxy module (living in the temporary file we've created) and the file we're wrapping not to be external, because - // otherwise they won't be processed. (We need Rollup to process the former so that we can use the code, and we need - // it to process the latter so it knows what exports to re-export from the proxy module.) Past that, we don't care, so - // don't bother to process anything else. - external: importPath => importPath !== SENTRY_PROXY_MODULE_NAME && importPath !== userModulePath, - - // Prevent rollup from stressing out about TS's use of global `this` when polyfilling await. (TS will polyfill if the - // user's tsconfig `target` is set to anything before `es2017`. See https://stackoverflow.com/a/72822340 and - // https://stackoverflow.com/a/60347490.) - context: 'this', - - // Rollup's path-resolution logic when handling re-exports can go wrong when wrapping pages which aren't at the root - // level of the `pages` directory. This may be a bug, as it doesn't match the behavior described in the docs, but what - // seems to happen is this: - // - // - We try to wrap `pages/xyz/userPage.js`, which contains `export { helperFunc } from '../../utils/helper'` - // - Rollup converts '../../utils/helper' into an absolute path - // - We mark the helper module as external - // - Rollup then converts it back to a relative path, but relative to `pages/` rather than `pages/xyz/`. (This is - // the part which doesn't match the docs. They say that Rollup will use the common ancestor of all modules in the - // bundle as the basis for the relative path calculation, but both our temporary file and the page being wrapped - // live in `pages/xyz/`, and they're the only two files in the bundle, so `pages/xyz/`` should be used as the - // root. Unclear why it's not.) - // - As a result of the miscalculation, our proxy module will include `export { helperFunc } from '../utils/helper'` - // rather than the expected `export { helperFunc } from '../../utils/helper'`, thereby causing a build error in - // nextjs.. - // - // Setting `makeAbsoluteExternalsRelative` to `false` prevents all of the above by causing Rollup to ignore imports of - // externals entirely, with the result that their paths remain untouched (which is what we want). - makeAbsoluteExternalsRelative: false, -}); - -const rollupOutputOptions: RollupOutputOptions = { - format: 'esm', - - // Don't create a bundle - we just want the transformed entrypoint file - preserveModules: true, -}; - -/** - * Use Rollup to process the proxy module code, in order to split its `export * from ''` call into - * individual exports (which nextjs seems to need). - * - * Note: Any errors which occur are handled by the proxy loader which calls this function. - * - * @param templateCode The proxy module code - * @param userModulePath The path to the file being wrapped - * @returns The processed proxy module code - */ -export async function rollupize(templateCode: string, userModulePath: string): Promise { - const intermediateBundle = await rollup(getRollupInputOptions(templateCode, userModulePath)); - const finalBundle = await intermediateBundle.generate(rollupOutputOptions); - - // The module at index 0 is always the entrypoint, which in this case is the proxy module. - let { code } = finalBundle.output[0]; - - // In addition to doing the desired work, Rollup also does a few things we *don't* want. Specifically, in messes up - // the path in both `import * as origModule from ''` and `export * from ''`. - // - // - It turns the square brackets surrounding each parameterized path segment into underscores. - // - It always adds `.js` to the end of the filename. - // - It converts the path from aboslute to relative, which would be fine except that when used with the virual plugin, - // it uses an incorrect (and not entirely predicable) base for that relative path. - // - // To fix this, we overwrite the messed up path with what we know it should be: `./`. (We can - // find the value of the messed up path by looking at what `import * as origModule from ''` becomes. - // Because it's the first line of the template, it's also the first line of the result, and is therefore easy to - // find.) - - const importStarStatement = code.split('\n')[0]; - // This regex should always match (we control both the input and the process which generates it, so we can guarantee - // the outcome of that processing), but just in case it somehow doesn't, we need it to throw an error so that the - // proxy loader will know to return the user's code untouched rather than returning proxy module code including a - // broken path. The non-null assertion asserts that a match has indeed been found. - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const messedUpPath = /^import \* as .* from '(.*)';$/.exec(importStarStatement)![1]; - - code = code.replace(new RegExp(escapeStringForRegex(messedUpPath), 'g'), `./${path.basename(userModulePath)}`); - - return code; -} diff --git a/packages/nextjs/src/config/loaders/sdkMultiplexerLoader.ts b/packages/nextjs/src/config/loaders/sdkMultiplexerLoader.ts new file mode 100644 index 000000000000..9241def48a80 --- /dev/null +++ b/packages/nextjs/src/config/loaders/sdkMultiplexerLoader.ts @@ -0,0 +1,24 @@ +import type { LoaderThis } from './types'; + +type LoaderOptions = { + importTarget: string; +}; + +/** + * This loader allows us to multiplex SDKs depending on what is passed to the `importTarget` loader option. + * If this loader encounters a file that contains the string "__SENTRY_SDK_MULTIPLEXER__" it will replace it's entire + * content with an "export all"-statement that points to `importTarget`. + * + * In our case we use this to multiplex different SDKs depending on whether we're bundling browser code, server code, + * or edge-runtime code. + */ +export default function sdkMultiplexerLoader(this: LoaderThis, userCode: string): string { + if (!userCode.includes('__SENTRY_SDK_MULTIPLEXER__')) { + return userCode; + } + + // We know one or the other will be defined, depending on the version of webpack being used + const { importTarget } = 'getOptions' in this ? this.getOptions() : this.query; + + return `export * from "${importTarget}";`; +} diff --git a/packages/nextjs/src/config/loaders/types.ts b/packages/nextjs/src/config/loaders/types.ts index 5db902abeced..14766f077a12 100644 --- a/packages/nextjs/src/config/loaders/types.ts +++ b/packages/nextjs/src/config/loaders/types.ts @@ -1,3 +1,5 @@ +import type webpack from 'webpack'; + export type LoaderThis = { /** Path to the file being loaded */ resourcePath: string; @@ -7,6 +9,12 @@ export type LoaderThis = { // Function to add outside file used by loader to `watch` process addDependency: (filepath: string) => void; + + // Marks a loader as asynchronous + async: webpack.loader.LoaderContext['async']; + + // Return errors, code, and sourcemaps from an asynchronous loader + callback: webpack.loader.LoaderContext['callback']; } & ( | { // Loader options in Webpack 4 diff --git a/packages/nextjs/src/config/loaders/valueInjectionLoader.ts b/packages/nextjs/src/config/loaders/valueInjectionLoader.ts index be8340dccdf3..6a645b0e798d 100644 --- a/packages/nextjs/src/config/loaders/valueInjectionLoader.ts +++ b/packages/nextjs/src/config/loaders/valueInjectionLoader.ts @@ -1,4 +1,4 @@ -import { LoaderThis } from './types'; +import type { LoaderThis } from './types'; type LoaderOptions = { values: Record; diff --git a/packages/nextjs/src/config/loaders/wrappingLoader.ts b/packages/nextjs/src/config/loaders/wrappingLoader.ts new file mode 100644 index 000000000000..f092216bbf1d --- /dev/null +++ b/packages/nextjs/src/config/loaders/wrappingLoader.ts @@ -0,0 +1,197 @@ +import commonjs from '@rollup/plugin-commonjs'; +import { stringMatchesSomePattern } from '@sentry/utils'; +import * as fs from 'fs'; +import * as path from 'path'; +import { rollup } from 'rollup'; + +import type { LoaderThis } from './types'; + +const apiWrapperTemplatePath = path.resolve(__dirname, '..', 'templates', 'apiWrapperTemplate.js'); +const apiWrapperTemplateCode = fs.readFileSync(apiWrapperTemplatePath, { encoding: 'utf8' }); + +const pageWrapperTemplatePath = path.resolve(__dirname, '..', 'templates', 'pageWrapperTemplate.js'); +const pageWrapperTemplateCode = fs.readFileSync(pageWrapperTemplatePath, { encoding: 'utf8' }); + +// Just a simple placeholder to make referencing module consistent +const SENTRY_WRAPPER_MODULE_NAME = 'sentry-wrapper-module'; + +// Needs to end in .cjs in order for the `commonjs` plugin to pick it up +const WRAPPING_TARGET_MODULE_NAME = '__SENTRY_WRAPPING_TARGET__.cjs'; + +type LoaderOptions = { + pagesDir: string; + pageExtensionRegex: string; + excludeServerRoutes: Array; + isEdgeRuntime: boolean; +}; + +/** + * Replace the loaded file with a wrapped version the original file. In the wrapped version, the original file is loaded, + * any data-fetching functions (`getInitialProps`, `getStaticProps`, and `getServerSideProps`) or API routes it contains + * are wrapped, and then everything is re-exported. + */ +export default function wrappingLoader( + this: LoaderThis, + userCode: string, + userModuleSourceMap: any, +): void | string { + // We know one or the other will be defined, depending on the version of webpack being used + const { + pagesDir, + pageExtensionRegex, + excludeServerRoutes = [], + isEdgeRuntime, + } = 'getOptions' in this ? this.getOptions() : this.query; + + // We currently don't support the edge runtime + if (isEdgeRuntime) { + return userCode; + } + + this.async(); + + // Get the parameterized route name from this page's filepath + const parameterizedRoute = path + // Get the path of the file insde of the pages directory + .relative(pagesDir, this.resourcePath) + // Add a slash at the beginning + .replace(/(.*)/, '/$1') + // Pull off the file extension + .replace(new RegExp(`\\.(${pageExtensionRegex})`), '') + // Any page file named `index` corresponds to root of the directory its in, URL-wise, so turn `/xyz/index` into + // just `/xyz` + .replace(/\/index$/, '') + // In case all of the above have left us with an empty string (which will happen if we're dealing with the + // homepage), sub back in the root route + .replace(/^$/, '/'); + + // Skip explicitly-ignored pages + if (stringMatchesSomePattern(parameterizedRoute, excludeServerRoutes, true)) { + this.callback(null, userCode, userModuleSourceMap); + return; + } + + let templateCode = parameterizedRoute.startsWith('/api') ? apiWrapperTemplateCode : pageWrapperTemplateCode; + + // Inject the route and the path to the file we're wrapping into the template + templateCode = templateCode.replace(/__ROUTE__/g, parameterizedRoute.replace(/\\/g, '\\\\')); + + // Replace the import path of the wrapping target in the template with a path that the `wrapUserCode` function will understand. + templateCode = templateCode.replace(/__SENTRY_WRAPPING_TARGET__/g, WRAPPING_TARGET_MODULE_NAME); + + // Run the proxy module code through Rollup, in order to split the `export * from ''` out into + // individual exports (which nextjs seems to require). + wrapUserCode(templateCode, userCode, userModuleSourceMap) + .then(({ code: wrappedCode, map: wrappedCodeSourceMap }) => { + this.callback(null, wrappedCode, wrappedCodeSourceMap); + }) + .catch(err => { + // eslint-disable-next-line no-console + console.warn( + `[@sentry/nextjs] Could not instrument ${this.resourcePath}. An error occurred while auto-wrapping:\n${err}`, + ); + this.callback(null, userCode, userModuleSourceMap); + return; + }); +} + +/** + * Use Rollup to process the proxy module code, in order to split its `export * from ''` call into + * individual exports (which nextjs seems to need). + * + * Wraps provided user code (located under the import defined via WRAPPING_TARGET_MODULE_NAME) with provided wrapper + * code. Under the hood, this function uses rollup to bundle the modules together. Rollup is convenient for us because + * it turns `export * from ''` (which Next.js doesn't allow) into individual named exports. + * + * Note: This function may throw in case something goes wrong while bundling. + * + * @param wrapperCode The wrapper module code + * @param userModuleCode The user module code + * @returns The wrapped user code and a source map that describes the transformations done by this function + */ +async function wrapUserCode( + wrapperCode: string, + userModuleCode: string, + userModuleSourceMap: any, +): Promise<{ code: string; map?: any }> { + const rollupBuild = await rollup({ + input: SENTRY_WRAPPER_MODULE_NAME, + + plugins: [ + // We're using a simple custom plugin that virtualizes our wrapper module and the user module, so we don't have to + // mess around with file paths and so that we can pass the original user module source map to rollup so that + // rollup gives us a bundle with correct source mapping to the original file + { + name: 'virtualize-sentry-wrapper-modules', + resolveId: id => { + if (id === SENTRY_WRAPPER_MODULE_NAME || id === WRAPPING_TARGET_MODULE_NAME) { + return id; + } else { + return null; + } + }, + load(id) { + if (id === SENTRY_WRAPPER_MODULE_NAME) { + return wrapperCode; + } else if (id === WRAPPING_TARGET_MODULE_NAME) { + return { + code: userModuleCode, + map: userModuleSourceMap, // give rollup acces to original user module source map + }; + } else { + return null; + } + }, + }, + + // People may use `module.exports` in their API routes or page files. Next.js allows that and we also need to + // handle that correctly so we let a plugin to take care of bundling cjs exports for us. + commonjs({ + transformMixedEsModules: true, + sourceMap: true, + }), + ], + + // We only want to bundle our wrapper module and the wrappee module into one, so we mark everything else as external. + external: sourceId => sourceId !== SENTRY_WRAPPER_MODULE_NAME && sourceId !== WRAPPING_TARGET_MODULE_NAME, + + // Prevent rollup from stressing out about TS's use of global `this` when polyfilling await. (TS will polyfill if the + // user's tsconfig `target` is set to anything before `es2017`. See https://stackoverflow.com/a/72822340 and + // https://stackoverflow.com/a/60347490.) + context: 'this', + + // Rollup's path-resolution logic when handling re-exports can go wrong when wrapping pages which aren't at the root + // level of the `pages` directory. This may be a bug, as it doesn't match the behavior described in the docs, but what + // seems to happen is this: + // + // - We try to wrap `pages/xyz/userPage.js`, which contains `export { helperFunc } from '../../utils/helper'` + // - Rollup converts '../../utils/helper' into an absolute path + // - We mark the helper module as external + // - Rollup then converts it back to a relative path, but relative to `pages/` rather than `pages/xyz/`. (This is + // the part which doesn't match the docs. They say that Rollup will use the common ancestor of all modules in the + // bundle as the basis for the relative path calculation, but both our temporary file and the page being wrapped + // live in `pages/xyz/`, and they're the only two files in the bundle, so `pages/xyz/`` should be used as the + // root. Unclear why it's not.) + // - As a result of the miscalculation, our proxy module will include `export { helperFunc } from '../utils/helper'` + // rather than the expected `export { helperFunc } from '../../utils/helper'`, thereby causing a build error in + // nextjs.. + // + // Setting `makeAbsoluteExternalsRelative` to `false` prevents all of the above by causing Rollup to ignore imports of + // externals entirely, with the result that their paths remain untouched (which is what we want). + makeAbsoluteExternalsRelative: false, + + onwarn: (_warning, _warn) => { + // Suppress all warnings - we don't want to bother people with this output + // Might be stuff like "you have unused imports" + // _warn(_warning); // uncomment to debug + }, + }); + + const finalBundle = await rollupBuild.generate({ + format: 'esm', + sourcemap: 'hidden', // put source map data in the bundle but don't generate a source map commment in the output + }); + + // The module at index 0 is always the entrypoint, which in this case is the proxy module. + return finalBundle.output[0]; +} diff --git a/packages/nextjs/src/config/templates/apiProxyLoaderTemplate.ts b/packages/nextjs/src/config/templates/apiWrapperTemplate.ts similarity index 73% rename from packages/nextjs/src/config/templates/apiProxyLoaderTemplate.ts rename to packages/nextjs/src/config/templates/apiWrapperTemplate.ts index 230eb55e457f..2f8dd2184301 100644 --- a/packages/nextjs/src/config/templates/apiProxyLoaderTemplate.ts +++ b/packages/nextjs/src/config/templates/apiWrapperTemplate.ts @@ -8,13 +8,14 @@ // @ts-ignore See above // eslint-disable-next-line import/no-unresolved -import * as origModule from '__RESOURCE_PATH__'; +import * as origModule from '__SENTRY_WRAPPING_TARGET__'; +// eslint-disable-next-line import/no-extraneous-dependencies import * as Sentry from '@sentry/nextjs'; import type { PageConfig } from 'next'; // We import this from `wrappers` rather than directly from `next` because our version can work simultaneously with // multiple versions of next. See note in `wrappers/types` for more. -import type { NextApiHandler } from '../wrappers'; +import type { NextApiHandler } from '../../server/types'; type NextApiModule = ( | { @@ -23,7 +24,7 @@ type NextApiModule = ( } // CJS export | NextApiHandler -) & { config?: PageConfig & { runtime?: string } }; +) & { config?: PageConfig }; const userApiModule = origModule as NextApiModule; @@ -53,24 +54,10 @@ export const config = { }, }; -// This is a variable that Next.js will string replace during build with a string if run in an edge runtime from Next.js -// v12.2.1-canary.3 onwards: -// https://github.com/vercel/next.js/blob/166e5fb9b92f64c4b5d1f6560a05e2b9778c16fb/packages/next/build/webpack-config.ts#L206 -// https://edge-runtime.vercel.sh/features/available-apis#addressing-the-runtime -declare const EdgeRuntime: string | undefined; - -let exportedHandler; - -if (typeof EdgeRuntime === 'string') { - exportedHandler = userProvidedHandler; -} else { - exportedHandler = userProvidedHandler ? Sentry.withSentryAPI(userProvidedHandler, '__ROUTE__') : undefined; -} - -export default exportedHandler; +export default userProvidedHandler ? Sentry.withSentryAPI(userProvidedHandler, '__ROUTE__') : undefined; // Re-export anything exported by the page module we're wrapping. When processing this code, Rollup is smart enough to // not include anything whose name matchs something we've explicitly exported above. // @ts-ignore See above // eslint-disable-next-line import/no-unresolved -export * from '__RESOURCE_PATH__'; +export * from '__SENTRY_WRAPPING_TARGET__'; diff --git a/packages/nextjs/src/config/templates/pageProxyLoaderTemplate.ts b/packages/nextjs/src/config/templates/pageWrapperTemplate.ts similarity index 93% rename from packages/nextjs/src/config/templates/pageProxyLoaderTemplate.ts rename to packages/nextjs/src/config/templates/pageWrapperTemplate.ts index 9979ae814c49..e3b6b4e7e296 100644 --- a/packages/nextjs/src/config/templates/pageProxyLoaderTemplate.ts +++ b/packages/nextjs/src/config/templates/pageWrapperTemplate.ts @@ -8,7 +8,8 @@ // @ts-ignore See above // eslint-disable-next-line import/no-unresolved -import * as wrapee from '__RESOURCE_PATH__'; +import * as wrapee from '__SENTRY_WRAPPING_TARGET__'; +// eslint-disable-next-line import/no-extraneous-dependencies import * as Sentry from '@sentry/nextjs'; import type { GetServerSideProps, GetStaticProps, NextPage as NextPageComponent } from 'next'; @@ -53,4 +54,4 @@ export default pageComponent; // not include anything whose name matchs something we've explicitly exported above. // @ts-ignore See above // eslint-disable-next-line import/no-unresolved -export * from '__RESOURCE_PATH__'; +export * from '__SENTRY_WRAPPING_TARGET__'; diff --git a/packages/nextjs/src/config/types.ts b/packages/nextjs/src/config/types.ts index f596252fd472..b79243627432 100644 --- a/packages/nextjs/src/config/types.ts +++ b/packages/nextjs/src/config/types.ts @@ -1,6 +1,6 @@ -import { GLOBAL_OBJ } from '@sentry/utils'; -import { SentryCliPluginOptions } from '@sentry/webpack-plugin'; -import { WebpackPluginInstance } from 'webpack'; +import type { GLOBAL_OBJ } from '@sentry/utils'; +import type { SentryCliPluginOptions } from '@sentry/webpack-plugin'; +import type { WebpackPluginInstance } from 'webpack'; export type SentryWebpackPluginOptions = SentryCliPluginOptions; export type SentryWebpackPlugin = WebpackPluginInstance & { options: SentryWebpackPluginOptions }; @@ -137,7 +137,7 @@ export type BuildContext = { // eslint-disable-next-line @typescript-eslint/no-explicit-any defaultLoaders: any; totalPages: number; - nextRuntime?: 'nodejs' | 'edge'; + nextRuntime?: 'nodejs' | 'edge'; // Added in Next.js 12+ }; /** diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index 988c6cc8b4b3..b5a8d07db2fd 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -85,6 +85,18 @@ export function constructWebpackConfigFunction( // Add a loader which will inject code that sets global values addValueInjectionLoader(newConfig, userNextConfig, userSentryOptions); + newConfig.module.rules.push({ + test: /node_modules\/@sentry\/nextjs/, + use: [ + { + loader: path.resolve(__dirname, 'loaders/sdkMultiplexerLoader.js'), + options: { + importTarget: buildContext.nextRuntime === 'edge' ? './edge' : './client', + }, + }, + ], + }); + if (isServer) { if (userSentryOptions.autoInstrumentServerFunctions !== false) { const pagesDir = newConfig.resolve?.alias?.['private-next-pages'] as string; @@ -93,15 +105,17 @@ export function constructWebpackConfigFunction( const pageExtensions = userNextConfig.pageExtensions || ['tsx', 'ts', 'jsx', 'js']; const pageExtensionRegex = pageExtensions.map(escapeStringForRegex).join('|'); - newConfig.module.rules.push({ + // It is very important that we insert our loader at the beginning of the array because we expect any sort of transformations/transpilations (e.g. TS -> JS) to already have happened. + newConfig.module.rules.unshift({ test: new RegExp(`^${escapeStringForRegex(pagesDir)}.*\\.(${pageExtensionRegex})$`), use: [ { - loader: path.resolve(__dirname, 'loaders/proxyLoader.js'), + loader: path.resolve(__dirname, 'loaders/wrappingLoader.js'), options: { pagesDir, pageExtensionRegex, excludeServerRoutes: userSentryOptions.excludeServerRoutes, + isEdgeRuntime: buildContext.nextRuntime === 'edge', }, }, ], @@ -292,16 +306,21 @@ 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, dev: isDev } = buildContext; + const { isServer, dir: projectDir, dev: isDev, nextRuntime } = buildContext; const newEntryProperty = typeof currentEntryProperty === 'function' ? await currentEntryProperty() : { ...currentEntryProperty }; // `sentry.server.config.js` or `sentry.client.config.js` (or their TS equivalents) - const userConfigFile = isServer ? getUserConfigFile(projectDir, 'server') : getUserConfigFile(projectDir, 'client'); + const userConfigFile = + nextRuntime === 'edge' + ? getUserConfigFile(projectDir, 'edge') + : isServer + ? getUserConfigFile(projectDir, 'server') + : getUserConfigFile(projectDir, 'client'); // we need to turn the filename into a path so webpack can find it - const filesToInject = [`./${userConfigFile}`]; + const filesToInject = userConfigFile ? [`./${userConfigFile}`] : []; // inject into all entry points which might contain user's code for (const entryPointName in newEntryProperty) { @@ -328,10 +347,11 @@ async function addSentryToEntryProperty( * TypeScript or JavaScript file. * * @param projectDir The root directory of the project, where the file should be located - * @param platform Either "server" or "client", so that we know which file to look for - * @returns The name of the relevant file. If no file is found, this method throws an error. + * @param platform Either "server", "client" or "edge", so that we know which file to look for + * @returns The name of the relevant file. If the server or client file is not found, this method throws an error. The + * edge file is optional, if it is not found this function will return `undefined`. */ -export function getUserConfigFile(projectDir: string, platform: 'server' | 'client'): string { +export function getUserConfigFile(projectDir: string, platform: 'server' | 'client' | 'edge'): string | undefined { const possibilities = [`sentry.${platform}.config.ts`, `sentry.${platform}.config.js`]; for (const filename of possibilities) { @@ -340,7 +360,16 @@ export function getUserConfigFile(projectDir: string, platform: 'server' | 'clie } } - throw new Error(`Cannot find '${possibilities[0]}' or '${possibilities[1]}' in '${projectDir}'.`); + // Edge config file is optional + if (platform === 'edge') { + // eslint-disable-next-line no-console + console.warn( + '[@sentry/nextjs] You are using Next.js features that run on the Edge Runtime. Please add a "sentry.edge.config.js" or a "sentry.edge.config.ts" file to your project root in which you initialize the Sentry SDK with "Sentry.init()".', + ); + return; + } else { + throw new Error(`Cannot find '${possibilities[0]}' or '${possibilities[1]}' in '${projectDir}'.`); + } } /** @@ -433,6 +462,10 @@ function shouldAddSentryToEntryPoint( excludeServerRoutes: Array = [], isDev: boolean, ): boolean { + if (entryPointName === 'middleware') { + return true; + } + // On the server side, by default we inject the `Sentry.init()` code into every page (with a few exceptions). if (isServer) { const entryPointRoute = entryPointName.replace(/^pages/, ''); @@ -456,9 +489,6 @@ function shouldAddSentryToEntryPoint( // versions.) entryPointRoute === '/_app' || entryPointRoute === '/_document' || - // While middleware was in beta, it could be anywhere (at any level) in the `pages` directory, and would be called - // `_middleware.js`. Until the SDK runs successfully in the lambda edge environment, we have to exclude these. - entryPointName.includes('_middleware') || // Newer versions of nextjs are starting to introduce things outside the `pages/` folder (middleware, an `app/` // directory, etc), but until those features are stable and we know how we want to support them, the safest bet is // not to inject anywhere but inside `pages/`. diff --git a/packages/nextjs/src/config/withSentryConfig.ts b/packages/nextjs/src/config/withSentryConfig.ts index 2b8d12792470..f10f801e1702 100644 --- a/packages/nextjs/src/config/withSentryConfig.ts +++ b/packages/nextjs/src/config/withSentryConfig.ts @@ -1,10 +1,12 @@ -import { NEXT_PHASE_DEVELOPMENT_SERVER, NEXT_PHASE_PRODUCTION_BUILD } from '../utils/phases'; +import { PHASE_DEVELOPMENT_SERVER, PHASE_PRODUCTION_BUILD } from 'next/constants'; + import type { ExportedNextConfig, NextConfigFunction, NextConfigObject, NextConfigObjectWithSentry, SentryWebpackPluginOptions, + UserSentryOptions, } from './types'; /** @@ -12,19 +14,21 @@ import type { * * @param exportedUserNextConfig The existing config to be exported prior to adding Sentry * @param userSentryWebpackPluginOptions Configuration for SentryWebpackPlugin + * @param sentryOptions Optional additional options to add as alternative to `sentry` property of config * @returns The modified config to be exported */ export function withSentryConfig( exportedUserNextConfig: ExportedNextConfig = {}, userSentryWebpackPluginOptions: Partial = {}, + sentryOptions?: UserSentryOptions, ): NextConfigFunction | NextConfigObject { return function (phase: string, defaults: { defaultConfig: NextConfigObject }): NextConfigObject { - if (typeof exportedUserNextConfig === 'function') { - const userNextConfigObject = exportedUserNextConfig(phase, defaults); - return getFinalConfigObject(phase, userNextConfigObject, userSentryWebpackPluginOptions); - } else { - return getFinalConfigObject(phase, exportedUserNextConfig, userSentryWebpackPluginOptions); - } + const userNextConfigObject = + typeof exportedUserNextConfig === 'function' ? exportedUserNextConfig(phase, defaults) : exportedUserNextConfig; + // Inserts additional `sentry` options into the existing config, allows for backwards compatability + // in case nothing is passed into the optional `sentryOptions` argument + userNextConfigObject.sentry = { ...userNextConfigObject.sentry, ...sentryOptions }; + return getFinalConfigObject(phase, userNextConfigObject, userSentryWebpackPluginOptions); }; } @@ -50,7 +54,7 @@ function getFinalConfigObject( // In order to prevent all of our build-time code from being bundled in people's route-handling serverless functions, // we exclude `webpack.ts` and all of its dependencies from nextjs's `@vercel/nft` filetracing. We therefore need to // make sure that we only require it at build time or in development mode. - if (phase === NEXT_PHASE_PRODUCTION_BUILD || phase === NEXT_PHASE_DEVELOPMENT_SERVER) { + if (phase === PHASE_PRODUCTION_BUILD || phase === PHASE_DEVELOPMENT_SERVER) { // eslint-disable-next-line @typescript-eslint/no-var-requires const { constructWebpackConfigFunction } = require('./webpack'); return { diff --git a/packages/nextjs/src/config/wrappers/index.ts b/packages/nextjs/src/config/wrappers/index.ts deleted file mode 100644 index aa406b970e77..000000000000 --- a/packages/nextjs/src/config/wrappers/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export type { AugmentedNextApiResponse, NextApiHandler, WrappedNextApiHandler } from './types'; - -export { withSentryGetStaticProps } from './withSentryGetStaticProps'; -export { withSentryServerSideGetInitialProps } from './withSentryServerSideGetInitialProps'; -export { withSentryServerSideAppGetInitialProps } from './withSentryServerSideAppGetInitialProps'; -export { withSentryServerSideDocumentGetInitialProps } from './withSentryServerSideDocumentGetInitialProps'; -export { withSentryServerSideErrorGetInitialProps } from './withSentryServerSideErrorGetInitialProps'; -export { withSentryGetServerSideProps } from './withSentryGetServerSideProps'; -export { withSentry, withSentryAPI } from './withSentryAPI'; diff --git a/packages/nextjs/src/edge/edgeclient.ts b/packages/nextjs/src/edge/edgeclient.ts new file mode 100644 index 000000000000..a5b38d651aed --- /dev/null +++ b/packages/nextjs/src/edge/edgeclient.ts @@ -0,0 +1,69 @@ +import type { Scope } from '@sentry/core'; +import { BaseClient, SDK_VERSION } from '@sentry/core'; +import type { ClientOptions, Event, EventHint, Severity, SeverityLevel } from '@sentry/types'; + +import { eventFromMessage, eventFromUnknownInput } from './eventbuilder'; +import type { EdgeTransportOptions } from './transport'; + +export type EdgeClientOptions = ClientOptions; + +/** + * The Sentry Edge SDK Client. + */ +export class EdgeClient extends BaseClient { + /** + * Creates a new Edge SDK instance. + * @param options Configuration options for this SDK. + */ + public constructor(options: EdgeClientOptions) { + options._metadata = options._metadata || {}; + options._metadata.sdk = options._metadata.sdk || { + name: 'sentry.javascript.nextjs', + packages: [ + { + name: 'npm:@sentry/nextjs', + version: SDK_VERSION, + }, + ], + version: SDK_VERSION, + }; + + super(options); + } + + /** + * @inheritDoc + */ + public eventFromException(exception: unknown, hint?: EventHint): PromiseLike { + return Promise.resolve(eventFromUnknownInput(this._options.stackParser, exception, hint)); + } + + /** + * @inheritDoc + */ + public eventFromMessage( + message: string, + // eslint-disable-next-line deprecation/deprecation + level: Severity | SeverityLevel = 'info', + hint?: EventHint, + ): PromiseLike { + return Promise.resolve( + eventFromMessage(this._options.stackParser, message, level, hint, this._options.attachStacktrace), + ); + } + + /** + * @inheritDoc + */ + protected _prepareEvent(event: Event, hint: EventHint, scope?: Scope): PromiseLike { + event.platform = event.platform || 'edge'; + event.contexts = { + ...event.contexts, + runtime: event.contexts?.runtime || { + name: 'edge', + }, + }; + event.server_name = event.server_name || process.env.SENTRY_NAME; + return super._prepareEvent(event, hint, scope); + } +} diff --git a/packages/nextjs/src/edge/eventbuilder.ts b/packages/nextjs/src/edge/eventbuilder.ts new file mode 100644 index 000000000000..4e483fce3ff7 --- /dev/null +++ b/packages/nextjs/src/edge/eventbuilder.ts @@ -0,0 +1,130 @@ +import { getCurrentHub } from '@sentry/core'; +import type { + Event, + EventHint, + Exception, + Mechanism, + Severity, + SeverityLevel, + StackFrame, + StackParser, +} from '@sentry/types'; +import { + addExceptionMechanism, + addExceptionTypeValue, + extractExceptionKeysForMessage, + isError, + isPlainObject, + normalizeToSize, +} from '@sentry/utils'; + +/** + * Extracts stack frames from the error.stack string + */ +export function parseStackFrames(stackParser: StackParser, error: Error): StackFrame[] { + return stackParser(error.stack || '', 1); +} + +/** + * Extracts stack frames from the error and builds a Sentry Exception + */ +export function exceptionFromError(stackParser: StackParser, error: Error): Exception { + const exception: Exception = { + type: error.name || error.constructor.name, + value: error.message, + }; + + const frames = parseStackFrames(stackParser, error); + if (frames.length) { + exception.stacktrace = { frames }; + } + + return exception; +} + +/** + * Builds and Event from a Exception + * @hidden + */ +export function eventFromUnknownInput(stackParser: StackParser, exception: unknown, hint?: EventHint): Event { + let ex: unknown = exception; + const providedMechanism: Mechanism | undefined = + hint && hint.data && (hint.data as { mechanism: Mechanism }).mechanism; + const mechanism: Mechanism = providedMechanism || { + handled: true, + type: 'generic', + }; + + if (!isError(exception)) { + if (isPlainObject(exception)) { + // This will allow us to group events based on top-level keys + // which is much better than creating new group when any key/value change + const message = `Non-Error exception captured with keys: ${extractExceptionKeysForMessage(exception)}`; + + const hub = getCurrentHub(); + const client = hub.getClient(); + const normalizeDepth = client && client.getOptions().normalizeDepth; + hub.configureScope(scope => { + scope.setExtra('__serialized__', normalizeToSize(exception, normalizeDepth)); + }); + + ex = (hint && hint.syntheticException) || new Error(message); + (ex as Error).message = message; + } else { + // This handles when someone does: `throw "something awesome";` + // We use synthesized Error here so we can extract a (rough) stack trace. + ex = (hint && hint.syntheticException) || new Error(exception as string); + (ex as Error).message = exception as string; + } + mechanism.synthetic = true; + } + + const event = { + exception: { + values: [exceptionFromError(stackParser, ex as Error)], + }, + }; + + addExceptionTypeValue(event, undefined, undefined); + addExceptionMechanism(event, mechanism); + + return { + ...event, + event_id: hint && hint.event_id, + }; +} + +/** + * Builds and Event from a Message + * @hidden + */ +export function eventFromMessage( + stackParser: StackParser, + message: string, + // eslint-disable-next-line deprecation/deprecation + level: Severity | SeverityLevel = 'info', + hint?: EventHint, + attachStacktrace?: boolean, +): Event { + const event: Event = { + event_id: hint && hint.event_id, + level, + message, + }; + + if (attachStacktrace && hint && hint.syntheticException) { + const frames = parseStackFrames(stackParser, hint.syntheticException); + if (frames.length) { + event.exception = { + values: [ + { + value: message, + stacktrace: { frames }, + }, + ], + }; + } + } + + return event; +} diff --git a/packages/nextjs/src/edge/index.ts b/packages/nextjs/src/edge/index.ts new file mode 100644 index 000000000000..42b48094d966 --- /dev/null +++ b/packages/nextjs/src/edge/index.ts @@ -0,0 +1,148 @@ +import '@sentry/tracing'; // Allow people to call tracing API methods without explicitly importing the tracing package. + +import { getCurrentHub, getIntegrationsToSetup, initAndBind, Integrations as CoreIntegrations } from '@sentry/core'; +import type { Options } from '@sentry/types'; +import { + createStackParser, + GLOBAL_OBJ, + logger, + nodeStackLineParser, + stackParserFromStackParserOptions, +} from '@sentry/utils'; + +import { EdgeClient } from './edgeclient'; +import { makeEdgeTransport } from './transport'; + +const nodeStackParser = createStackParser(nodeStackLineParser()); + +export const defaultIntegrations = [new CoreIntegrations.InboundFilters(), new CoreIntegrations.FunctionToString()]; + +export type EdgeOptions = Options; + +/** Inits the Sentry NextJS SDK on the Edge Runtime. */ +export function init(options: EdgeOptions = {}): void { + if (options.defaultIntegrations === undefined) { + options.defaultIntegrations = defaultIntegrations; + } + + if (options.dsn === undefined && process.env.SENTRY_DSN) { + options.dsn = process.env.SENTRY_DSN; + } + + if (options.tracesSampleRate === undefined && process.env.SENTRY_TRACES_SAMPLE_RATE) { + const tracesSampleRate = parseFloat(process.env.SENTRY_TRACES_SAMPLE_RATE); + if (isFinite(tracesSampleRate)) { + options.tracesSampleRate = tracesSampleRate; + } + } + + if (options.release === undefined) { + const detectedRelease = getSentryRelease(); + if (detectedRelease !== undefined) { + options.release = detectedRelease; + } else { + // If release is not provided, then we should disable autoSessionTracking + options.autoSessionTracking = false; + } + } + + if (options.environment === undefined && process.env.SENTRY_ENVIRONMENT) { + options.environment = process.env.SENTRY_ENVIRONMENT; + } + + if (options.autoSessionTracking === undefined && options.dsn !== undefined) { + options.autoSessionTracking = true; + } + + if (options.instrumenter === undefined) { + options.instrumenter = 'sentry'; + } + + const clientOptions = { + ...options, + stackParser: stackParserFromStackParserOptions(options.stackParser || nodeStackParser), + integrations: getIntegrationsToSetup(options), + transport: options.transport || makeEdgeTransport, + }; + + initAndBind(EdgeClient, clientOptions); + + // TODO?: Sessiontracking +} + +/** + * Returns a release dynamically from environment variables. + */ +export function getSentryRelease(fallback?: string): string | undefined { + // Always read first as Sentry takes this as precedence + if (process.env.SENTRY_RELEASE) { + return process.env.SENTRY_RELEASE; + } + + // This supports the variable that sentry-webpack-plugin injects + if (GLOBAL_OBJ.SENTRY_RELEASE && GLOBAL_OBJ.SENTRY_RELEASE.id) { + return GLOBAL_OBJ.SENTRY_RELEASE.id; + } + + return ( + // GitHub Actions - https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables + process.env.GITHUB_SHA || + // Netlify - https://docs.netlify.com/configure-builds/environment-variables/#build-metadata + process.env.COMMIT_REF || + // Vercel - https://vercel.com/docs/v2/build-step#system-environment-variables + process.env.VERCEL_GIT_COMMIT_SHA || + process.env.VERCEL_GITHUB_COMMIT_SHA || + process.env.VERCEL_GITLAB_COMMIT_SHA || + process.env.VERCEL_BITBUCKET_COMMIT_SHA || + // Zeit (now known as Vercel) + process.env.ZEIT_GITHUB_COMMIT_SHA || + process.env.ZEIT_GITLAB_COMMIT_SHA || + process.env.ZEIT_BITBUCKET_COMMIT_SHA || + fallback + ); +} + +/** + * Call `close()` on the current client, if there is one. See {@link Client.close}. + * + * @param timeout Maximum time in ms the client should wait to flush its event queue before shutting down. Omitting this + * parameter will cause the client to wait until all events are sent before disabling itself. + * @returns A promise which resolves to `true` if the queue successfully drains before the timeout, or `false` if it + * doesn't (or if there's no client defined). + */ +export async function close(timeout?: number): Promise { + const client = getCurrentHub().getClient(); + if (client) { + return client.close(timeout); + } + __DEBUG_BUILD__ && logger.warn('Cannot flush events and disable SDK. No client defined.'); + return Promise.resolve(false); +} + +/** + * Call `flush()` on the current client, if there is one. See {@link Client.flush}. + * + * @param timeout Maximum time in ms the client should wait to flush its event queue. Omitting this parameter will cause + * the client to wait until all events are sent before resolving the promise. + * @returns A promise which resolves to `true` if the queue successfully drains before the timeout, or `false` if it + * doesn't (or if there's no client defined). + */ +export async function flush(timeout?: number): Promise { + const client = getCurrentHub().getClient(); + if (client) { + return client.flush(timeout); + } + __DEBUG_BUILD__ && logger.warn('Cannot flush events. No client defined.'); + return Promise.resolve(false); +} + +/** + * This is the getter for lastEventId. + * + * @returns The last event id of a captured event. + */ +export function lastEventId(): string | undefined { + return getCurrentHub().lastEventId(); +} + +export * from '@sentry/core'; diff --git a/packages/nextjs/src/edge/transport.ts b/packages/nextjs/src/edge/transport.ts new file mode 100644 index 000000000000..3fc4b8e101c3 --- /dev/null +++ b/packages/nextjs/src/edge/transport.ts @@ -0,0 +1,38 @@ +import { createTransport } from '@sentry/core'; +import type { BaseTransportOptions, Transport, TransportMakeRequestResponse, TransportRequest } from '@sentry/types'; + +export interface EdgeTransportOptions extends BaseTransportOptions { + /** Fetch API init parameters. Used by the FetchTransport */ + fetchOptions?: RequestInit; + /** Custom headers for the transport. Used by the XHRTransport and FetchTransport */ + headers?: { [key: string]: string }; +} + +/** + * Creates a Transport that uses the Edge Runtimes native fetch API to send events to Sentry. + */ +export function makeEdgeTransport(options: EdgeTransportOptions): Transport { + function makeRequest(request: TransportRequest): PromiseLike { + const requestOptions: RequestInit = { + body: request.body, + method: 'POST', + referrerPolicy: 'origin', + headers: options.headers, + ...options.fetchOptions, + }; + + try { + return fetch(options.url, requestOptions).then(response => ({ + statusCode: response.status, + headers: { + 'x-sentry-rate-limits': response.headers.get('X-Sentry-Rate-Limits'), + 'retry-after': response.headers.get('Retry-After'), + }, + })); + } catch (e) { + return Promise.reject(e); + } + } + + return createTransport(options, makeRequest); +} diff --git a/packages/nextjs/src/index.ts b/packages/nextjs/src/index.ts new file mode 100644 index 000000000000..4133c06089d5 --- /dev/null +++ b/packages/nextjs/src/index.ts @@ -0,0 +1,4 @@ +export * from './config'; +export * from './server'; + +// __SENTRY_SDK_MULTIPLEXER__ diff --git a/packages/nextjs/src/index.types.ts b/packages/nextjs/src/index.types.ts new file mode 100644 index 000000000000..fcce6708a293 --- /dev/null +++ b/packages/nextjs/src/index.types.ts @@ -0,0 +1,31 @@ +/* eslint-disable import/export */ + +// We export everything from both the client part of the SDK and from the server part. Some of the exports collide, +// which is not allowed, unless we redifine the colliding exports in this file - which we do below. +export * from './config'; +export * from './client'; +export * from './server'; +export * from './edge'; + +import type { Integration, Options, StackParser } from '@sentry/types'; + +import type { BrowserOptions } from './client'; +import * as clientSdk from './client'; +import type { EdgeOptions } from './edge'; +import * as edgeSdk from './edge'; +import type { NodeOptions } from './server'; +import * as serverSdk from './server'; + +/** Initializes Sentry Next.js SDK */ +export declare function init(options: Options | BrowserOptions | NodeOptions | EdgeOptions): void; + +// We export a merged Integrations object so that users can (at least typing-wise) use all integrations everywhere. +export const Integrations = { ...clientSdk.Integrations, ...serverSdk.Integrations, ...edgeSdk.Integrations }; + +export declare const defaultIntegrations: Integration[]; +export declare const defaultStackParser: StackParser; + +export declare function close(timeout?: number | undefined): PromiseLike; +export declare function flush(timeout?: number | undefined): PromiseLike; +export declare function lastEventId(): string | undefined; +export declare function getSentryRelease(fallback?: string): string | undefined; diff --git a/packages/nextjs/src/index.server.ts b/packages/nextjs/src/server/index.ts similarity index 60% rename from packages/nextjs/src/index.server.ts rename to packages/nextjs/src/server/index.ts index 0c5982dc747d..340f4100d611 100644 --- a/packages/nextjs/src/index.server.ts +++ b/packages/nextjs/src/server/index.ts @@ -1,24 +1,21 @@ -import { Carrier, getHubFromCarrier, getMainCarrier } from '@sentry/core'; +import type { Carrier } from '@sentry/core'; +import { getHubFromCarrier, getMainCarrier } from '@sentry/core'; import { RewriteFrames } from '@sentry/integrations'; +import type { NodeOptions } from '@sentry/node'; import { configureScope, getCurrentHub, init as nodeInit, Integrations } from '@sentry/node'; import { hasTracingEnabled } from '@sentry/tracing'; -import { EventProcessor } from '@sentry/types'; +import type { EventProcessor } from '@sentry/types'; import { escapeStringForRegex, logger } from '@sentry/utils'; import * as domainModule from 'domain'; import * as path from 'path'; +import { buildMetadata } from '../common/metadata'; +import type { IntegrationWithExclusionOption } from '../common/userIntegrations'; +import { addOrUpdateIntegration } from '../common/userIntegrations'; import { isBuild } from './utils/isBuild'; -import { buildMetadata } from './utils/metadata'; -import { NextjsOptions } from './utils/nextjsOptions'; -import { addOrUpdateIntegration, IntegrationWithExclusionOption } from './utils/userIntegrations'; export * from '@sentry/node'; -export { captureUnderscoreErrorException } from './utils/_error'; - -// Exporting the Replay integration also from index.server.ts because TS only recognizes types from index.server.ts -// If we didn't export this, TS would complain that it can't find `Sentry.Replay` in the package, -// causing a build failure, when users initialize Replay in their sentry.client.config.js/ts file. -export { Replay } from './index.client'; +export { captureUnderscoreErrorException } from '../common/_error'; // Here we want to make sure to only include what doesn't have browser specifics // because or SSR of next.js we can only use this. @@ -30,32 +27,20 @@ const globalWithInjectedValues = global as typeof global & { const domain = domainModule as typeof domainModule & { active: (domainModule.Domain & Carrier) | null }; -// This is a variable that Next.js will string replace during build with a string if run in an edge runtime from Next.js -// v12.2.1-canary.3 onwards: -// https://github.com/vercel/next.js/blob/166e5fb9b92f64c4b5d1f6560a05e2b9778c16fb/packages/next/build/webpack-config.ts#L206 -// https://edge-runtime.vercel.sh/features/available-apis#addressing-the-runtime -declare const EdgeRuntime: string | undefined; - -// Exporting this constant means we can compute it without the linter complaining, even if we stop directly using it in -// this file. It's important that it be computed as early as possible, because one of its indicators is seeing 'build' -// (as in the CLI command `next build`) in `process.argv`. Later on in the build process, everything's been spun out -// into child threads and `argv` turns into ['node', 'path/to/childProcess.js'], so the original indicator is lost. We -// thus want to compute it as soon as the SDK is loaded for the first time, which is normally when the user imports -// `withSentryConfig` into `next.config.js`. +// TODO (v8): Remove this +/** + * @deprecated This constant will be removed in the next major update. + */ export const IS_BUILD = isBuild(); + const IS_VERCEL = !!process.env.VERCEL; /** Inits the Sentry NextJS SDK on node. */ -export function init(options: NextjsOptions): void { +export function init(options: NodeOptions): void { if (__DEBUG_BUILD__ && options.debug) { logger.enable(); } - if (typeof EdgeRuntime === 'string') { - __DEBUG_BUILD__ && logger.log('Vercel Edge Runtime detected. Will not initialize SDK.'); - return; - } - __DEBUG_BUILD__ && logger.log('Initializing SDK...'); if (sdkAlreadyInitialized()) { @@ -118,7 +103,7 @@ function sdkAlreadyInitialized(): boolean { return !!hub.getClient(); } -function addServerIntegrations(options: NextjsOptions): void { +function addServerIntegrations(options: NodeOptions): void { let integrations = options.integrations || []; // This value is injected at build time, based on the output directory specified in the build config. Though a default @@ -157,37 +142,16 @@ function addServerIntegrations(options: NextjsOptions): void { // TODO (v8): Remove this /** - * @deprecated Use the constant `IS_BUILD` instead. + * @deprecated This constant will be removed in the next major update. */ const deprecatedIsBuild = (): boolean => isBuild(); // eslint-disable-next-line deprecation/deprecation export { deprecatedIsBuild as isBuild }; -export type { SentryWebpackPluginOptions } from './config/types'; -export { withSentryConfig } from './config/withSentryConfig'; -export { - withSentryGetServerSideProps, - withSentryGetStaticProps, - withSentryServerSideGetInitialProps, - withSentryServerSideAppGetInitialProps, - withSentryServerSideDocumentGetInitialProps, - withSentryServerSideErrorGetInitialProps, - withSentryAPI, - withSentry, -} from './config/wrappers'; - -// Wrap various server methods to enable error monitoring and tracing. (Note: This only happens for non-Vercel -// deployments, because the current method of doing the wrapping a) crashes Next 12 apps deployed to Vercel and -// b) doesn't work on those apps anyway. We also don't do it during build, because there's no server running in that -// phase.) -if (!IS_BUILD && !IS_VERCEL) { - // Dynamically require the file because even importing from it causes Next 12 to crash on Vercel. - // In environments where the JS file doesn't exist, such as testing, import the TS file. - try { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const { instrumentServer } = require('./utils/instrumentServer.js'); - instrumentServer(); - } catch (err) { - __DEBUG_BUILD__ && logger.warn(`Error: Unable to instrument server for tracing. Got ${err}.`); - } -} +export { withSentryGetStaticProps } from './withSentryGetStaticProps'; +export { withSentryServerSideGetInitialProps } from './withSentryServerSideGetInitialProps'; +export { withSentryServerSideAppGetInitialProps } from './withSentryServerSideAppGetInitialProps'; +export { withSentryServerSideDocumentGetInitialProps } from './withSentryServerSideDocumentGetInitialProps'; +export { withSentryServerSideErrorGetInitialProps } from './withSentryServerSideErrorGetInitialProps'; +export { withSentryGetServerSideProps } from './withSentryGetServerSideProps'; +export { withSentry, withSentryAPI } from './withSentryAPI'; diff --git a/packages/nextjs/src/config/wrappers/types.ts b/packages/nextjs/src/server/types.ts similarity index 92% rename from packages/nextjs/src/config/wrappers/types.ts rename to packages/nextjs/src/server/types.ts index 16197e4cedc7..a411c4ea62cf 100644 --- a/packages/nextjs/src/config/wrappers/types.ts +++ b/packages/nextjs/src/server/types.ts @@ -23,14 +23,19 @@ import type { NextApiRequest, NextApiResponse } from 'next'; // wrapped route from being wrapped again by the auto-wrapper. export type NextApiHandler = { - __sentry_route__?: string; (req: NextApiRequest, res: NextApiResponse): void | Promise | unknown | Promise; + __sentry_route__?: string; + + /** + * A property we set in our integration tests to simulate running an API route on platforms that don't support streaming. + */ + __sentry_test_doesnt_support_streaming__?: true; }; export type WrappedNextApiHandler = { + (req: NextApiRequest, res: NextApiResponse): Promise | Promise; __sentry_route__?: string; __sentry_wrapped__?: boolean; - (req: NextApiRequest, res: NextApiResponse): Promise | Promise; }; export type AugmentedNextApiRequest = NextApiRequest & { diff --git a/packages/nextjs/src/server/utils/isBuild.ts b/packages/nextjs/src/server/utils/isBuild.ts new file mode 100644 index 000000000000..92b9808f75b9 --- /dev/null +++ b/packages/nextjs/src/server/utils/isBuild.ts @@ -0,0 +1,8 @@ +import { PHASE_PRODUCTION_BUILD } from 'next/constants'; + +/** + * Decide if the currently running process is part of the build phase or happening at runtime. + */ +export function isBuild(): boolean { + return process.env.NEXT_PHASE === PHASE_PRODUCTION_BUILD; +} diff --git a/packages/nextjs/src/utils/nextLogger.ts b/packages/nextjs/src/server/utils/nextLogger.ts similarity index 100% rename from packages/nextjs/src/utils/nextLogger.ts rename to packages/nextjs/src/server/utils/nextLogger.ts diff --git a/packages/nextjs/src/utils/platformSupportsStreaming.ts b/packages/nextjs/src/server/utils/platformSupportsStreaming.ts similarity index 100% rename from packages/nextjs/src/utils/platformSupportsStreaming.ts rename to packages/nextjs/src/server/utils/platformSupportsStreaming.ts diff --git a/packages/nextjs/src/config/wrappers/utils/responseEnd.ts b/packages/nextjs/src/server/utils/responseEnd.ts similarity index 89% rename from packages/nextjs/src/config/wrappers/utils/responseEnd.ts rename to packages/nextjs/src/server/utils/responseEnd.ts index 21d5f5d850a5..ee2d9f803d3b 100644 --- a/packages/nextjs/src/config/wrappers/utils/responseEnd.ts +++ b/packages/nextjs/src/server/utils/responseEnd.ts @@ -1,9 +1,9 @@ import { flush } from '@sentry/node'; -import { Transaction } from '@sentry/types'; +import type { Transaction } from '@sentry/types'; import { fill, logger } from '@sentry/utils'; -import { ServerResponse } from 'http'; +import type { ServerResponse } from 'http'; -import { ResponseEndMethod, WrappedResponseEndMethod } from '../types'; +import type { ResponseEndMethod, WrappedResponseEndMethod } from '../types'; /** * Wrap `res.end()` so that it closes the transaction and flushes events before letting the request finish. @@ -31,7 +31,8 @@ export function autoEndTransactionOnResponseEnd(transaction: Transaction, res: S }; // Prevent double-wrapping - if (!(res.end as WrappedResponseEndMethod).__sentry_original__) { + // res.end may be undefined during build when using `next export` to statically export a Next.js app + if (res.end && !(res.end as WrappedResponseEndMethod).__sentry_original__) { fill(res, 'end', wrapEndMethod); } } diff --git a/packages/nextjs/src/config/wrappers/wrapperUtils.ts b/packages/nextjs/src/server/utils/wrapperUtils.ts similarity index 96% rename from packages/nextjs/src/config/wrappers/wrapperUtils.ts rename to packages/nextjs/src/server/utils/wrapperUtils.ts index 18665040d45d..ae05b3f16b8d 100644 --- a/packages/nextjs/src/config/wrappers/wrapperUtils.ts +++ b/packages/nextjs/src/server/utils/wrapperUtils.ts @@ -1,12 +1,12 @@ import { captureException, getCurrentHub, startTransaction } from '@sentry/core'; import { getActiveTransaction } from '@sentry/tracing'; -import { Transaction } from '@sentry/types'; +import type { Transaction } from '@sentry/types'; import { baggageHeaderToDynamicSamplingContext, extractTraceparentData } from '@sentry/utils'; import * as domain from 'domain'; -import { IncomingMessage, ServerResponse } from 'http'; +import type { IncomingMessage, ServerResponse } from 'http'; -import { platformSupportsStreaming } from '../../utils/platformSupportsStreaming'; -import { autoEndTransactionOnResponseEnd, flushQueue } from './utils/responseEnd'; +import { platformSupportsStreaming } from './platformSupportsStreaming'; +import { autoEndTransactionOnResponseEnd, flushQueue } from './responseEnd'; declare module 'http' { interface IncomingMessage { diff --git a/packages/nextjs/src/config/wrappers/withSentryAPI.ts b/packages/nextjs/src/server/withSentryAPI.ts similarity index 90% rename from packages/nextjs/src/config/wrappers/withSentryAPI.ts rename to packages/nextjs/src/server/withSentryAPI.ts index e047be166ed4..5d5180dd741e 100644 --- a/packages/nextjs/src/config/wrappers/withSentryAPI.ts +++ b/packages/nextjs/src/server/withSentryAPI.ts @@ -1,6 +1,6 @@ import { captureException, getCurrentHub, startTransaction } from '@sentry/node'; import { extractTraceparentData, hasTracingEnabled } from '@sentry/tracing'; -import { Transaction } from '@sentry/types'; +import type { Transaction } from '@sentry/types'; import { addExceptionMechanism, baggageHeaderToDynamicSamplingContext, @@ -11,9 +11,9 @@ import { } from '@sentry/utils'; import * as domain from 'domain'; -import { formatAsCode, nextLogger } from '../../utils/nextLogger'; -import { platformSupportsStreaming } from '../../utils/platformSupportsStreaming'; import type { AugmentedNextApiRequest, AugmentedNextApiResponse, NextApiHandler, WrappedNextApiHandler } from './types'; +import { formatAsCode, nextLogger } from './utils/nextLogger'; +import { platformSupportsStreaming } from './utils/platformSupportsStreaming'; import { autoEndTransactionOnResponseEnd, finishTransaction, flushQueue } from './utils/responseEnd'; /** @@ -130,31 +130,11 @@ export function withSentry(origHandler: NextApiHandler, parameterizedRoute?: str ); currentScope.setSpan(transaction); - if (platformSupportsStreaming()) { + if (platformSupportsStreaming() && !origHandler.__sentry_test_doesnt_support_streaming__) { autoEndTransactionOnResponseEnd(transaction, res); } else { - // If we're not on a platform that supports streaming, we're blocking all response-ending methods until the - // queue is flushed. - - const origResSend = res.send; - res.send = async function (this: unknown, ...args: unknown[]) { - if (transaction) { - await finishTransaction(transaction, res); - await flushQueue(); - } - - origResSend.apply(this, args); - }; - - const origResJson = res.json; - res.json = async function (this: unknown, ...args: unknown[]) { - if (transaction) { - await finishTransaction(transaction, res); - await flushQueue(); - } - - origResJson.apply(this, args); - }; + // If we're not on a platform that supports streaming, we're blocking res.end() until the queue is flushed. + // res.json() and res.send() will implicitly call res.end(), so it is enough to wrap res.end(). // eslint-disable-next-line @typescript-eslint/unbound-method const origResEnd = res.end; @@ -223,7 +203,7 @@ export function withSentry(origHandler: NextApiHandler, parameterizedRoute?: str // moment they detect an error, so it's important to get this done before rethrowing the error. Apps not // deployed serverlessly will run into this cleanup code again in `res.end(), but the transaction will already // be finished and the queue will already be empty, so effectively it'll just no-op.) - if (platformSupportsStreaming()) { + if (platformSupportsStreaming() && !origHandler.__sentry_test_doesnt_support_streaming__) { void finishTransaction(transaction, res); } else { await finishTransaction(transaction, res); diff --git a/packages/nextjs/src/config/wrappers/withSentryGetServerSideProps.ts b/packages/nextjs/src/server/withSentryGetServerSideProps.ts similarity index 89% rename from packages/nextjs/src/config/wrappers/withSentryGetServerSideProps.ts rename to packages/nextjs/src/server/withSentryGetServerSideProps.ts index 8aa2244ea90e..1f41e91ab3e9 100644 --- a/packages/nextjs/src/config/wrappers/withSentryGetServerSideProps.ts +++ b/packages/nextjs/src/server/withSentryGetServerSideProps.ts @@ -1,9 +1,13 @@ import { hasTracingEnabled } from '@sentry/tracing'; import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils'; -import { GetServerSideProps } from 'next'; - -import { isBuild } from '../../utils/isBuild'; -import { getTransactionFromRequest, withErrorInstrumentation, withTracedServerSideDataFetcher } from './wrapperUtils'; +import type { GetServerSideProps } from 'next'; + +import { isBuild } from './utils/isBuild'; +import { + getTransactionFromRequest, + withErrorInstrumentation, + withTracedServerSideDataFetcher, +} from './utils/wrapperUtils'; /** * Create a wrapped version of the user's exported `getServerSideProps` function diff --git a/packages/nextjs/src/config/wrappers/withSentryGetStaticProps.ts b/packages/nextjs/src/server/withSentryGetStaticProps.ts similarity index 89% rename from packages/nextjs/src/config/wrappers/withSentryGetStaticProps.ts rename to packages/nextjs/src/server/withSentryGetStaticProps.ts index 396c57c0652c..96e6f2b81db9 100644 --- a/packages/nextjs/src/config/wrappers/withSentryGetStaticProps.ts +++ b/packages/nextjs/src/server/withSentryGetStaticProps.ts @@ -1,7 +1,7 @@ -import { GetStaticProps } from 'next'; +import type { GetStaticProps } from 'next'; -import { isBuild } from '../../utils/isBuild'; -import { callDataFetcherTraced, withErrorInstrumentation } from './wrapperUtils'; +import { isBuild } from './utils/isBuild'; +import { callDataFetcherTraced, withErrorInstrumentation } from './utils/wrapperUtils'; type Props = { [key: string]: unknown }; diff --git a/packages/nextjs/src/config/wrappers/withSentryServerSideAppGetInitialProps.ts b/packages/nextjs/src/server/withSentryServerSideAppGetInitialProps.ts similarity index 93% rename from packages/nextjs/src/config/wrappers/withSentryServerSideAppGetInitialProps.ts rename to packages/nextjs/src/server/withSentryServerSideAppGetInitialProps.ts index a136bbcdc96a..1508661db73e 100644 --- a/packages/nextjs/src/config/wrappers/withSentryServerSideAppGetInitialProps.ts +++ b/packages/nextjs/src/server/withSentryServerSideAppGetInitialProps.ts @@ -1,9 +1,13 @@ import { hasTracingEnabled } from '@sentry/tracing'; import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils'; -import App from 'next/app'; +import type App from 'next/app'; -import { isBuild } from '../../utils/isBuild'; -import { getTransactionFromRequest, withErrorInstrumentation, withTracedServerSideDataFetcher } from './wrapperUtils'; +import { isBuild } from './utils/isBuild'; +import { + getTransactionFromRequest, + withErrorInstrumentation, + withTracedServerSideDataFetcher, +} from './utils/wrapperUtils'; type AppGetInitialProps = typeof App['getInitialProps']; diff --git a/packages/nextjs/src/config/wrappers/withSentryServerSideDocumentGetInitialProps.ts b/packages/nextjs/src/server/withSentryServerSideDocumentGetInitialProps.ts similarity index 94% rename from packages/nextjs/src/config/wrappers/withSentryServerSideDocumentGetInitialProps.ts rename to packages/nextjs/src/server/withSentryServerSideDocumentGetInitialProps.ts index a04aa11a3398..95c45da095aa 100644 --- a/packages/nextjs/src/config/wrappers/withSentryServerSideDocumentGetInitialProps.ts +++ b/packages/nextjs/src/server/withSentryServerSideDocumentGetInitialProps.ts @@ -1,8 +1,8 @@ import { hasTracingEnabled } from '@sentry/tracing'; -import Document from 'next/document'; +import type Document from 'next/document'; -import { isBuild } from '../../utils/isBuild'; -import { withErrorInstrumentation, withTracedServerSideDataFetcher } from './wrapperUtils'; +import { isBuild } from './utils/isBuild'; +import { withErrorInstrumentation, withTracedServerSideDataFetcher } from './utils/wrapperUtils'; type DocumentGetInitialProps = typeof Document.getInitialProps; diff --git a/packages/nextjs/src/config/wrappers/withSentryServerSideErrorGetInitialProps.ts b/packages/nextjs/src/server/withSentryServerSideErrorGetInitialProps.ts similarity index 90% rename from packages/nextjs/src/config/wrappers/withSentryServerSideErrorGetInitialProps.ts rename to packages/nextjs/src/server/withSentryServerSideErrorGetInitialProps.ts index 8ceae817e1bc..8208c46762a9 100644 --- a/packages/nextjs/src/config/wrappers/withSentryServerSideErrorGetInitialProps.ts +++ b/packages/nextjs/src/server/withSentryServerSideErrorGetInitialProps.ts @@ -1,10 +1,14 @@ import { hasTracingEnabled } from '@sentry/tracing'; import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils'; -import { NextPageContext } from 'next'; -import { ErrorProps } from 'next/error'; +import type { NextPageContext } from 'next'; +import type { ErrorProps } from 'next/error'; -import { isBuild } from '../../utils/isBuild'; -import { getTransactionFromRequest, withErrorInstrumentation, withTracedServerSideDataFetcher } from './wrapperUtils'; +import { isBuild } from './utils/isBuild'; +import { + getTransactionFromRequest, + withErrorInstrumentation, + withTracedServerSideDataFetcher, +} from './utils/wrapperUtils'; type ErrorGetInitialProps = (context: NextPageContext) => Promise; diff --git a/packages/nextjs/src/config/wrappers/withSentryServerSideGetInitialProps.ts b/packages/nextjs/src/server/withSentryServerSideGetInitialProps.ts similarity index 91% rename from packages/nextjs/src/config/wrappers/withSentryServerSideGetInitialProps.ts rename to packages/nextjs/src/server/withSentryServerSideGetInitialProps.ts index 06d0f7766fec..9cf7cef3db03 100644 --- a/packages/nextjs/src/config/wrappers/withSentryServerSideGetInitialProps.ts +++ b/packages/nextjs/src/server/withSentryServerSideGetInitialProps.ts @@ -1,9 +1,13 @@ import { hasTracingEnabled } from '@sentry/tracing'; import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils'; -import { NextPage } from 'next'; - -import { isBuild } from '../../utils/isBuild'; -import { getTransactionFromRequest, withErrorInstrumentation, withTracedServerSideDataFetcher } from './wrapperUtils'; +import type { NextPage } from 'next'; + +import { isBuild } from './utils/isBuild'; +import { + getTransactionFromRequest, + withErrorInstrumentation, + withTracedServerSideDataFetcher, +} from './utils/wrapperUtils'; type GetInitialProps = Required['getInitialProps']; diff --git a/packages/nextjs/src/utils/instrumentServer.ts b/packages/nextjs/src/utils/instrumentServer.ts deleted file mode 100644 index 393c85fed014..000000000000 --- a/packages/nextjs/src/utils/instrumentServer.ts +++ /dev/null @@ -1,369 +0,0 @@ -/* eslint-disable max-lines */ -import { captureException, configureScope, deepReadDirSync, getCurrentHub, startTransaction } from '@sentry/node'; -import { extractTraceparentData, getActiveTransaction, hasTracingEnabled } from '@sentry/tracing'; -import { - addExceptionMechanism, - baggageHeaderToDynamicSamplingContext, - fill, - isString, - logger, - stripUrlQueryAndFragment, -} from '@sentry/utils'; -import * as domain from 'domain'; -import * as http from 'http'; -import { default as createNextServer } from 'next'; -import * as querystring from 'querystring'; -import * as url from 'url'; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type PlainObject = { [key: string]: T }; - -// class used by `next` as a proxy to the real server; see -// https://github.com/vercel/next.js/blob/4443d6f3d36b107e833376c2720c1e206eee720d/packages/next/server/next.ts#L32 -interface NextServer { - server: Server; - getServer: () => Promise; - createServer: (options: PlainObject) => Server; -} - -// `next`'s main server class; see -// https://github.com/vercel/next.js/blob/4443d6f3d36b107e833376c2720c1e206eee720d/packages/next/next-server/server/next-server.ts#L132 -interface Server { - dir: string; - publicDir: string; -} - -export type NextRequest = ( - | http.IncomingMessage // in nextjs versions < 12.0.9, `NextRequest` extends `http.IncomingMessage` - | { - _req: http.IncomingMessage; // in nextjs versions >= 12.0.9, `NextRequest` wraps `http.IncomingMessage` - } -) & { - cookies: Record; - url: string; - query: { [key: string]: string }; - headers: { [key: string]: string }; - body: string | { [key: string]: unknown }; - method: string; -}; - -type NextResponse = - // in nextjs versions < 12.0.9, `NextResponse` extends `http.ServerResponse` - | http.ServerResponse - // in nextjs versions >= 12.0.9, `NextResponse` wraps `http.ServerResponse` - | { - _res: http.ServerResponse; - }; - -// the methods we'll wrap -type HandlerGetter = () => Promise; -type ReqHandler = (nextReq: NextRequest, nextRes: NextResponse, parsedUrl?: url.UrlWithParsedQuery) => Promise; -type ErrorLogger = (err: Error) => void; -type ApiPageEnsurer = (path: string) => Promise; -type PageComponentFinder = ( - pathname: string, - query: querystring.ParsedUrlQuery, - params: { [key: string]: unknown } | null, -) => Promise<{ [key: string]: unknown } | null>; - -// these aliases are purely to make the function signatures more easily understandable -type WrappedHandlerGetter = HandlerGetter; -type WrappedErrorLogger = ErrorLogger; -type WrappedReqHandler = ReqHandler; -type WrappedApiPageEnsurer = ApiPageEnsurer; -type WrappedPageComponentFinder = PageComponentFinder; - -let liveServer: Server; -let sdkSetupComplete = false; - -const globalWithInjectedValues = global as typeof global & { - __sentryRewritesTunnelPath__?: string; -}; - -/** - * Do the monkeypatching and wrapping necessary to catch errors in page routes and record transactions for both page and - * API routes. - */ -export function instrumentServer(): void { - // The full implementation here involves a lot of indirection and multiple layers of callbacks and wrapping, and is - // therefore potentially a little hard to follow. Here's the overall idea: - - // Next.js uses two server classes, `NextServer` and `Server`, with the former proxying calls to the latter, which - // then does the all real work. The only access we have to either is through Next's default export, - // `createNextServer()`, which returns a `NextServer` instance. - - // At server startup: - // `next.config.js` imports SDK -> - // SDK's `index.ts` runs -> - // `instrumentServer()` (the function we're in right now) -> - // `createNextServer()` -> - // `NextServer` instance -> - // `NextServer` prototype -> - // Wrap `NextServer.getServerRequestHandler()`, purely to get us to the next step - - // At time of first request: - // Wrapped `getServerRequestHandler` runs for the first time -> - // Live `NextServer` instance(via`this`) -> - // Live `Server` instance (via `NextServer.server`) -> - // `Server` prototype -> - // Wrap `Server.logError`, `Server.handleRequest`, `Server.ensureApiPage`, and `Server.findPageComponents` methods, - // then fulfill original purpose of function by passing wrapped version of `handleRequest` to caller - - // Whenever caller of `NextServer.getServerRequestHandler` calls the wrapped `Server.handleRequest`: - // Trace request - - // Whenever something calls the wrapped `Server.logError`: - // Capture error - - // Whenever an API request is handled and the wrapped `Server.ensureApiPage` is called, or whenever a page request is - // handled and the wrapped `Server.findPageComponents` is called: - // Replace URL in transaction name with parameterized version - - const nextServerPrototype = Object.getPrototypeOf(createNextServer({})); - fill(nextServerPrototype, 'getServerRequestHandler', makeWrappedHandlerGetter); -} - -/** - * Create a wrapped version of Nextjs's `NextServer.getServerRequestHandler` method, as a way to access the running - * `Server` instance and monkeypatch its prototype. - * - * @param origHandlerGetter Nextjs's `NextServer.getServerRequestHandler` method - * @returns A wrapped version of the same method, to monkeypatch in at server startup - */ -function makeWrappedHandlerGetter(origHandlerGetter: HandlerGetter): WrappedHandlerGetter { - // We wrap this purely in order to be able to grab data and do further monkeypatching the first time it runs. - // Otherwise, it's just a pass-through to the original method. - const wrappedHandlerGetter = async function (this: NextServer): Promise { - if (!sdkSetupComplete) { - // stash this in the closure so that `makeWrappedReqHandler` can use it - liveServer = await this.getServer(); - const serverPrototype = Object.getPrototypeOf(liveServer); - - // Wrap for error capturing (`logError` gets called by `next` for all server-side errors) - fill(serverPrototype, 'logError', makeWrappedErrorLogger); - - // Wrap for request transaction creation (`handleRequest` is called for all incoming requests, and dispatches them - // to the appropriate handlers) - fill(serverPrototype, 'handleRequest', makeWrappedReqHandler); - - // Wrap as a way to grab the parameterized request URL to use as the transaction name for API requests and page - // requests, respectively. These methods are chosen because they're the first spot in the request-handling process - // where the parameterized path is provided as an argument, so it's easy to grab. - fill(serverPrototype, 'ensureApiPage', makeWrappedMethodForGettingParameterizedPath); - fill(serverPrototype, 'findPageComponents', makeWrappedMethodForGettingParameterizedPath); - - sdkSetupComplete = true; - } - - return origHandlerGetter.call(this); - }; - - return wrappedHandlerGetter; -} - -/** - * Wrap the error logger used by the server to capture exceptions which arise from functions like `getServerSideProps`. - * - * @param origErrorLogger The original logger from the `Server` class - * @returns A wrapped version of that logger - */ -function makeWrappedErrorLogger(origErrorLogger: ErrorLogger): WrappedErrorLogger { - return function (this: Server, err: Error): void { - // TODO add more context data here - - // We can use `configureScope` rather than `withScope` here because we're using domains to ensure that each request - // gets its own scope. (`configureScope` has the advantage of not creating a clone of the current scope before - // modifying it, which in this case is unnecessary.) - configureScope(scope => { - scope.addEventProcessor(event => { - addExceptionMechanism(event, { - type: 'instrument', - handled: true, - data: { - function: 'logError', - }, - }); - return event; - }); - }); - - captureException(err); - - return origErrorLogger.call(this, err); - }; -} - -// inspired by next's public file routing; see -// https://github.com/vercel/next.js/blob/4443d6f3d36b107e833376c2720c1e206eee720d/packages/next/next-server/server/next-server.ts#L1166 -function getPublicDirFiles(): Set { - try { - // we need the paths here to match the format of a request url, which means they must: - // - start with a slash - // - use forward slashes rather than backslashes - // - be URL-encoded - const dirContents = deepReadDirSync(liveServer.publicDir).map(filepath => - encodeURI(`/${filepath.replace(/\\/g, '/')}`), - ); - return new Set(dirContents); - } catch (_) { - return new Set(); - } -} - -/** - * Wrap the server's request handler to be able to create request transactions. - * - * @param origReqHandler The original request handler from the `Server` class - * @returns A wrapped version of that handler - */ -function makeWrappedReqHandler(origReqHandler: ReqHandler): WrappedReqHandler { - const publicDirFiles = getPublicDirFiles(); - // add transaction start and stop to the normal request handling - const wrappedReqHandler = async function ( - this: Server, - nextReq: NextRequest, - nextRes: NextResponse, - parsedUrl?: url.UrlWithParsedQuery, - ): Promise { - // Starting with version 12.0.9, nextjs wraps the incoming request in a `NodeNextRequest` object and the outgoing - // response in a `NodeNextResponse` object before passing them to the handler. (This is necessary here but not in - // `withSentry` because by the time nextjs passes them to an API handler, it's unwrapped them again.) - const req = '_req' in nextReq ? nextReq._req : nextReq; - const res = '_res' in nextRes ? nextRes._res : nextRes; - - // wrap everything in a domain in order to prevent scope bleed between requests - const local = domain.create(); - local.add(req); - local.add(res); - // TODO could this replace wrapping the error logger? - // local.on('error', Sentry.captureException); - - local.run(() => { - const currentScope = getCurrentHub().getScope(); - - if (currentScope) { - // Store the request on the scope so we can pull data from it and add it to the event - currentScope.setSDKProcessingMetadata({ request: req }); - - // We only want to record page and API requests - if (hasTracingEnabled() && shouldTraceRequest(nextReq.url, publicDirFiles)) { - // If there is a trace header set, extract the data from it (parentSpanId, traceId, and sampling decision) - let traceparentData; - if (nextReq.headers && isString(nextReq.headers['sentry-trace'])) { - traceparentData = extractTraceparentData(nextReq.headers['sentry-trace']); - __DEBUG_BUILD__ && logger.log(`[Tracing] Continuing trace ${traceparentData?.traceId}.`); - } - - const baggageHeader = nextReq.headers && nextReq.headers.baggage; - const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(baggageHeader); - - // pull off query string, if any - const reqPath = stripUrlQueryAndFragment(nextReq.url); - - // requests for pages will only ever be GET requests, so don't bother to include the method in the transaction - // name; requests to API routes could be GET, POST, PUT, etc, so do include it there - const namePrefix = nextReq.url.startsWith('/api') ? `${nextReq.method.toUpperCase()} ` : ''; - - const transaction = startTransaction( - { - name: `${namePrefix}${reqPath}`, - op: 'http.server', - metadata: { - dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, - requestPath: reqPath, - // TODO: Investigate if there's a way to tell if this is a dynamic route, so that we can make this more - // like `source: isDynamicRoute? 'url' : 'route'` - // TODO: What happens when `withSentry` is used also? Which values of `name` and `source` win? - source: 'url', - request: req, - }, - ...traceparentData, - }, - // Extra context passed to the `tracesSampler` (Note: We're combining `nextReq` and `req` this way in order - // to not break people's `tracesSampler` functions, even though the format of `nextReq` has changed (see - // note above re: nextjs 12.0.9). If `nextReq === req` (pre 12.0.9), then spreading `req` is a no-op - we're - // just spreading the same stuff twice. If `nextReq` contains `req` (12.0.9 and later), then spreading `req` - // mimics the old format by flattening the data.) - { request: { ...nextReq, ...req } }, - ); - - currentScope.setSpan(transaction); - - res.once('finish', () => { - const transaction = getActiveTransaction(); - if (transaction) { - transaction.setHttpStatus(res.statusCode); - - // Push `transaction.finish` to the next event loop so open spans have a chance to finish before the - // transaction closes - setImmediate(() => { - transaction.finish(); - }); - } - }); - } - } - - return origReqHandler.call(this, nextReq, nextRes, parsedUrl); - }); - }; - - return wrappedReqHandler; -} - -/** - * Wrap the given method in order to use the parameterized path passed to it in the transaction name. - * - * @param origMethod Either `ensureApiPage` (called for every API request) or `findPageComponents` (called for every - * page request), both from the `Server` class - * @returns A wrapped version of the given method - */ -function makeWrappedMethodForGettingParameterizedPath( - origMethod: ApiPageEnsurer | PageComponentFinder, -): WrappedApiPageEnsurer | WrappedPageComponentFinder { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const wrappedMethod = async function ( - this: Server, - parameterizedPath: - | string // `ensureAPIPage`, `findPageComponents` before nextjs 12.2.6 - | { pathname: string }, // `findPageComponents` from nextjs 12.2.6 onward - ...args: any[] - ): Promise { - const transaction = getActiveTransaction(); - - // replace specific URL with parameterized version - if (transaction && transaction.metadata.requestPath) { - const origPath = transaction.metadata.requestPath; - const newPath = typeof parameterizedPath === 'string' ? parameterizedPath : parameterizedPath.pathname; - if (newPath) { - const newName = transaction.name.replace(origPath, newPath); - transaction.setName(newName, 'route'); - } - } - - return origMethod.call(this, parameterizedPath, ...args); - }; - - return wrappedMethod; -} - -/** - * Determine if the request should be traced, by filtering out requests for internal next files and static resources. - * - * @param url The URL of the request - * @param publicDirFiles A set containing relative paths to all available static resources (note that this does not - * include static *pages*, but rather images and the like) - * @returns false if the URL is for an internal or static resource - */ -function shouldTraceRequest(url: string, publicDirFiles: Set): boolean { - // Don't trace tunneled sentry events - const tunnelPath = globalWithInjectedValues.__sentryRewritesTunnelPath__; - const pathname = new URL(url, 'http://example.com/').pathname; // `url` is relative so we need to define a base to be able to parse with URL - if (tunnelPath && pathname === tunnelPath) { - __DEBUG_BUILD__ && logger.log(`Tunneling Sentry event received on "${url}"`); - return false; - } - - // `static` is a deprecated but still-functional location for static resources - return !url.startsWith('/_next/') && !url.startsWith('/static/') && !publicDirFiles.has(url); -} diff --git a/packages/nextjs/src/utils/isBuild.ts b/packages/nextjs/src/utils/isBuild.ts deleted file mode 100644 index 49dd6d103543..000000000000 --- a/packages/nextjs/src/utils/isBuild.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { NEXT_PHASE_PRODUCTION_BUILD } from './phases'; - -/** - * Decide if the currently running process is part of the build phase or happening at runtime. - */ -export function isBuild(): boolean { - if ( - // During build, the main process is invoked by - // `node next build` - // and child processes are invoked as - // `node /node_modules/.../jest-worker/processChild.js`. - // The former is (obviously) easy to recognize, but the latter could happen at runtime as well. Fortunately, the main - // process hits this file before any of the child processes do, so we're able to set an env variable which the child - // processes can then check. During runtime, the main process is invoked as - // `node next start` - // or - // `node /var/runtime/index.js`, - // so we never drop into the `if` in the first place. - process.argv.includes('build') || - process.env.SENTRY_BUILD_PHASE || - // This is set by next, but not until partway through the build process, which is why we need the above checks. That - // said, in case this function isn't called until we're in a child process, it can serve as a good backup. - process.env.NEXT_PHASE === NEXT_PHASE_PRODUCTION_BUILD - ) { - process.env.SENTRY_BUILD_PHASE = 'true'; - return true; - } - - return false; -} diff --git a/packages/nextjs/src/utils/isESM.ts b/packages/nextjs/src/utils/isESM.ts deleted file mode 100644 index 30ce7f4e87de..000000000000 --- a/packages/nextjs/src/utils/isESM.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Determine if the given source code represents a file written using ES6 modules. - * - * The regexes used are from https://github.com/component/is-module, which got them from - * https://github.com/formatjs/js-module-formats, which says it got them from - * https://github.com/ModuleLoader/es-module-loader, though the originals are now nowhere to be found. - * - * @param moduleSource The source code of the module - * @returns True if the module contains ESM-patterned code, false otherwise. - */ -export function isESM(moduleSource: string): boolean { - const importExportRegex = - /(?:^\s*|[}{();,\n]\s*)(import\s+['"]|(import|module)\s+[^"'()\n;]+\s+from\s+['"]|export\s+(\*|\{|default|function|var|const|let|[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*))/; - const exportStarRegex = /(?:^\s*|[}{();,\n]\s*)(export\s*\*\s*from\s*(?:'([^']+)'|"([^"]+)"))/; - - return importExportRegex.test(moduleSource) || exportStarRegex.test(moduleSource); -} diff --git a/packages/nextjs/src/utils/nextjsOptions.ts b/packages/nextjs/src/utils/nextjsOptions.ts deleted file mode 100644 index ded1fe3f1d23..000000000000 --- a/packages/nextjs/src/utils/nextjsOptions.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { NodeOptions } from '@sentry/node'; -import { BrowserOptions } from '@sentry/react'; -import { Options } from '@sentry/types'; - -export type NextjsOptions = Options | BrowserOptions | NodeOptions; diff --git a/packages/nextjs/src/utils/phases.ts b/packages/nextjs/src/utils/phases.ts deleted file mode 100644 index 17e11d404be1..000000000000 --- a/packages/nextjs/src/utils/phases.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const NEXT_PHASE_PRODUCTION_BUILD = 'phase-production-build'; -export const NEXT_PHASE_PRODUCTION_SERVER = 'phase-production-server'; -export const NEXT_PHASE_DEVELOPMENT_SERVER = 'phase-development-server'; diff --git a/packages/nextjs/test/index.client.test.ts b/packages/nextjs/test/clientSdk.test.ts similarity index 98% rename from packages/nextjs/test/index.client.test.ts rename to packages/nextjs/test/clientSdk.test.ts index 9ed02519e722..044da248dc41 100644 --- a/packages/nextjs/test/index.client.test.ts +++ b/packages/nextjs/test/clientSdk.test.ts @@ -2,12 +2,12 @@ import { BaseClient, getCurrentHub } from '@sentry/core'; import * as SentryReact from '@sentry/react'; import { WINDOW } from '@sentry/react'; import { Integrations as TracingIntegrations } from '@sentry/tracing'; -import { Integration } from '@sentry/types'; +import type { Integration } from '@sentry/types'; import { logger } from '@sentry/utils'; import { JSDOM } from 'jsdom'; -import { init, Integrations, nextRouterInstrumentation } from '../src/index.client'; -import { UserIntegrationsFunction } from '../src/utils/userIntegrations'; +import { init, Integrations, nextRouterInstrumentation } from '../src/client'; +import type { UserIntegrationsFunction } from '../src/common/userIntegrations'; const { BrowserTracing } = TracingIntegrations; diff --git a/packages/nextjs/test/config/fixtures.ts b/packages/nextjs/test/config/fixtures.ts index 9accd4a0ba64..f747edbc2be9 100644 --- a/packages/nextjs/test/config/fixtures.ts +++ b/packages/nextjs/test/config/fixtures.ts @@ -1,4 +1,4 @@ -import { +import type { BuildContext, EntryPropertyFunction, ExportedNextConfig, @@ -9,6 +9,7 @@ import { export const SERVER_SDK_CONFIG_FILE = 'sentry.server.config.js'; export const CLIENT_SDK_CONFIG_FILE = 'sentry.client.config.js'; +export const EDGE_SDK_CONFIG_FILE = 'sentry.edge.config.js'; /** Mock next config object */ export const userNextConfig: NextConfigObject = { @@ -43,7 +44,7 @@ export const serverWebpackConfig: WebpackConfigObject = { 'pages/_error': 'private-next-pages/_error.js', 'pages/_app': 'private-next-pages/_app.js', 'pages/sniffTour': ['./node_modules/smellOVision/index.js', 'private-next-pages/sniffTour.js'], - 'pages/api/_middleware': 'private-next-pages/api/_middleware.js', + middleware: 'private-next-pages/middleware.js', 'pages/api/simulator/dogStats/[name]': { import: 'private-next-pages/api/simulator/dogStats/[name].js' }, 'pages/simulator/leaderboard': { import: ['./node_modules/dogPoints/converter.js', 'private-next-pages/simulator/leaderboard.js'], @@ -84,7 +85,7 @@ export const clientWebpackConfig: WebpackConfigObject = { * @returns A mock build context for the given target */ export function getBuildContext( - buildTarget: 'server' | 'client', + buildTarget: 'server' | 'client' | 'edge', materializedNextConfig: ExportedNextConfig, webpackVersion: string = '5.4.15', ): BuildContext { @@ -101,9 +102,11 @@ export function getBuildContext( webpack: { version: webpackVersion }, defaultLoaders: true, totalPages: 2, - isServer: buildTarget === 'server', + isServer: buildTarget === 'server' || buildTarget === 'edge', + nextRuntime: ({ server: 'nodejs', client: undefined, edge: 'edge' } as const)[buildTarget], }; } export const serverBuildContext = getBuildContext('server', exportedNextConfig); export const clientBuildContext = getBuildContext('client', exportedNextConfig); +export const edgeBuildContext = getBuildContext('edge', exportedNextConfig); diff --git a/packages/nextjs/test/config/mocks.ts b/packages/nextjs/test/config/mocks.ts index 581b7d2bbbd1..ddf4ce4d1553 100644 --- a/packages/nextjs/test/config/mocks.ts +++ b/packages/nextjs/test/config/mocks.ts @@ -6,7 +6,7 @@ import * as os from 'os'; import * as path from 'path'; import * as rimraf from 'rimraf'; -import { CLIENT_SDK_CONFIG_FILE, SERVER_SDK_CONFIG_FILE } from './fixtures'; +import { CLIENT_SDK_CONFIG_FILE, EDGE_SDK_CONFIG_FILE, SERVER_SDK_CONFIG_FILE } from './fixtures'; // We use `fs.existsSync()` in `getUserConfigFile()`. When we're not testing `getUserConfigFile()` specifically, all we // need is for it to give us any valid answer, so make it always find what it's looking for. Since this is a core node @@ -14,7 +14,11 @@ import { CLIENT_SDK_CONFIG_FILE, SERVER_SDK_CONFIG_FILE } from './fixtures'; // function also lets us restore the original when we do want to test `getUserConfigFile()`. export const realExistsSync = jest.requireActual('fs').existsSync; export const mockExistsSync = (path: fs.PathLike): ReturnType => { - if ((path as string).endsWith(SERVER_SDK_CONFIG_FILE) || (path as string).endsWith(CLIENT_SDK_CONFIG_FILE)) { + if ( + (path as string).endsWith(SERVER_SDK_CONFIG_FILE) || + (path as string).endsWith(CLIENT_SDK_CONFIG_FILE) || + (path as string).endsWith(EDGE_SDK_CONFIG_FILE) + ) { return true; } diff --git a/packages/nextjs/test/config/testUtils.ts b/packages/nextjs/test/config/testUtils.ts index 889ac4bb54da..74f854fa08b8 100644 --- a/packages/nextjs/test/config/testUtils.ts +++ b/packages/nextjs/test/config/testUtils.ts @@ -1,6 +1,6 @@ -import { WebpackPluginInstance } from 'webpack'; +import type { WebpackPluginInstance } from 'webpack'; -import { +import type { BuildContext, EntryPropertyFunction, ExportedNextConfig, @@ -9,7 +9,8 @@ import { WebpackConfigObject, WebpackConfigObjectWithModuleRules, } from '../../src/config/types'; -import { constructWebpackConfigFunction, SentryWebpackPlugin } from '../../src/config/webpack'; +import type { SentryWebpackPlugin } from '../../src/config/webpack'; +import { constructWebpackConfigFunction } from '../../src/config/webpack'; import { withSentryConfig } from '../../src/config/withSentryConfig'; import { defaultRuntimePhase, defaultsObject } from './fixtures'; diff --git a/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts b/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts index 206050d56d38..bee971f104e6 100644 --- a/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts +++ b/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts @@ -6,6 +6,8 @@ import { CLIENT_SDK_CONFIG_FILE, clientBuildContext, clientWebpackConfig, + EDGE_SDK_CONFIG_FILE, + edgeBuildContext, exportedNextConfig, SERVER_SDK_CONFIG_FILE, serverBuildContext, @@ -87,6 +89,7 @@ describe('constructWebpackConfigFunction()', () => { describe('webpack `entry` property config', () => { const serverConfigFilePath = `./${SERVER_SDK_CONFIG_FILE}`; const clientConfigFilePath = `./${CLIENT_SDK_CONFIG_FILE}`; + const edgeConfigFilePath = `./${EDGE_SDK_CONFIG_FILE}`; it('handles various entrypoint shapes', async () => { const finalWebpackConfig = await materializeFinalWebpackConfig({ @@ -207,17 +210,16 @@ describe('constructWebpackConfigFunction()', () => { ); }); - it('does not inject user config file into API middleware', async () => { + it('injects user config file into API middleware', async () => { const finalWebpackConfig = await materializeFinalWebpackConfig({ exportedNextConfig, incomingWebpackConfig: serverWebpackConfig, - incomingWebpackBuildContext: serverBuildContext, + incomingWebpackBuildContext: edgeBuildContext, }); expect(finalWebpackConfig.entry).toEqual( expect.objectContaining({ - // no injected file - 'pages/api/_middleware': 'private-next-pages/api/_middleware.js', + middleware: [edgeConfigFilePath, 'private-next-pages/middleware.js'], }), ); }); diff --git a/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts b/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts index bd8314663bb7..220e041c3fda 100644 --- a/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts +++ b/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts @@ -2,7 +2,7 @@ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; -import { BuildContext, ExportedNextConfig } from '../../../src/config/types'; +import type { BuildContext, ExportedNextConfig } from '../../../src/config/types'; import { getUserConfigFile, getWebpackPluginOptions, SentryWebpackPlugin } from '../../../src/config/webpack'; import { clientBuildContext, diff --git a/packages/nextjs/test/config/withSentry.test.ts b/packages/nextjs/test/config/withSentry.test.ts index fa468b32670b..6cbf4ac02c09 100644 --- a/packages/nextjs/test/config/withSentry.test.ts +++ b/packages/nextjs/test/config/withSentry.test.ts @@ -1,9 +1,10 @@ import * as hub from '@sentry/core'; import * as Sentry from '@sentry/node'; -import { Client, ClientOptions } from '@sentry/types'; -import { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'; +import type { Client, ClientOptions } from '@sentry/types'; +import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'; -import { AugmentedNextApiResponse, withSentry, WrappedNextApiHandler } from '../../src/config/wrappers'; +import { withSentry } from '../../src/server'; +import type { AugmentedNextApiResponse, WrappedNextApiHandler } from '../../src/server/types'; const FLUSH_DURATION = 200; diff --git a/packages/nextjs/test/config/wrappers.test.ts b/packages/nextjs/test/config/wrappers.test.ts index abbefd93c3e8..8edf2de57567 100644 --- a/packages/nextjs/test/config/wrappers.test.ts +++ b/packages/nextjs/test/config/wrappers.test.ts @@ -1,17 +1,8 @@ import * as SentryCore from '@sentry/core'; import * as SentryTracing from '@sentry/tracing'; -import { IncomingMessage, ServerResponse } from 'http'; +import type { IncomingMessage, ServerResponse } from 'http'; -import { - withSentryGetServerSideProps, - withSentryServerSideGetInitialProps, - // TODO: Leaving `withSentryGetStaticProps` out for now until we figure out what to do with it - // withSentryGetStaticProps, - // TODO: Leaving these out for now until we figure out pages with no data fetchers - // withSentryServerSideAppGetInitialProps, - // withSentryServerSideDocumentGetInitialProps, - // withSentryServerSideErrorGetInitialProps, -} from '../../src/config/wrappers'; +import { withSentryGetServerSideProps, withSentryServerSideGetInitialProps } from '../../src/server'; const startTransactionSpy = jest.spyOn(SentryCore, 'startTransaction'); diff --git a/packages/nextjs/test/edge/edgeclient.test.ts b/packages/nextjs/test/edge/edgeclient.test.ts new file mode 100644 index 000000000000..cba4a751c71e --- /dev/null +++ b/packages/nextjs/test/edge/edgeclient.test.ts @@ -0,0 +1,57 @@ +import { createTransport } from '@sentry/core'; +import type { Event, EventHint } from '@sentry/types'; + +import type { EdgeClientOptions } from '../../src/edge/edgeclient'; +import { EdgeClient } from '../../src/edge/edgeclient'; + +const PUBLIC_DSN = 'https://username@domain/123'; + +function getDefaultEdgeClientOptions(options: Partial = {}): EdgeClientOptions { + return { + integrations: [], + transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => Promise.resolve({})), + stackParser: () => [], + instrumenter: 'sentry', + ...options, + }; +} + +describe('NodeClient', () => { + describe('_prepareEvent', () => { + test('adds platform to event', () => { + const options = getDefaultEdgeClientOptions({ dsn: PUBLIC_DSN }); + const client = new EdgeClient(options); + + const event: Event = {}; + const hint: EventHint = {}; + (client as any)._prepareEvent(event, hint); + + expect(event.platform).toEqual('edge'); + }); + + test('adds runtime context to event', () => { + const options = getDefaultEdgeClientOptions({ dsn: PUBLIC_DSN }); + const client = new EdgeClient(options); + + const event: Event = {}; + const hint: EventHint = {}; + (client as any)._prepareEvent(event, hint); + + expect(event.contexts?.runtime).toEqual({ + name: 'edge', + }); + }); + + test("doesn't clobber existing runtime data", () => { + const options = getDefaultEdgeClientOptions({ dsn: PUBLIC_DSN }); + const client = new EdgeClient(options); + + const event: Event = { contexts: { runtime: { name: 'foo', version: '1.2.3' } } }; + const hint: EventHint = {}; + (client as any)._prepareEvent(event, hint); + + expect(event.contexts?.runtime).toEqual({ name: 'foo', version: '1.2.3' }); + expect(event.contexts?.runtime).not.toEqual({ name: 'edge' }); + }); + }); +}); diff --git a/packages/nextjs/test/edge/transport.test.ts b/packages/nextjs/test/edge/transport.test.ts new file mode 100644 index 000000000000..abb925184685 --- /dev/null +++ b/packages/nextjs/test/edge/transport.test.ts @@ -0,0 +1,110 @@ +import type { EventEnvelope, EventItem } from '@sentry/types'; +import { createEnvelope, serializeEnvelope } from '@sentry/utils'; +import { TextEncoder } from 'util'; + +import type { EdgeTransportOptions } from '../../src/edge/transport'; +import { makeEdgeTransport } from '../../src/edge/transport'; + +const DEFAULT_EDGE_TRANSPORT_OPTIONS: EdgeTransportOptions = { + url: 'https://sentry.io/api/42/store/?sentry_key=123&sentry_version=7', + recordDroppedEvent: () => undefined, + textEncoder: new TextEncoder(), +}; + +const ERROR_ENVELOPE = createEnvelope({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [ + [{ type: 'event' }, { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }] as EventItem, +]); + +class Headers { + headers: { [key: string]: string } = {}; + get(key: string) { + return this.headers[key] || null; + } + set(key: string, value: string) { + this.headers[key] = value; + } +} + +const mockFetch = jest.fn(); + +// @ts-ignore fetch is not on global +const oldFetch = global.fetch; +// @ts-ignore fetch is not on global +global.fetch = mockFetch; + +afterAll(() => { + // @ts-ignore fetch is not on global + global.fetch = oldFetch; +}); + +describe('Edge Transport', () => { + it('calls fetch with the given URL', async () => { + mockFetch.mockImplementationOnce(() => + Promise.resolve({ + headers: new Headers(), + status: 200, + text: () => Promise.resolve({}), + }), + ); + + const transport = makeEdgeTransport(DEFAULT_EDGE_TRANSPORT_OPTIONS); + + expect(mockFetch).toHaveBeenCalledTimes(0); + await transport.send(ERROR_ENVELOPE); + expect(mockFetch).toHaveBeenCalledTimes(1); + + expect(mockFetch).toHaveBeenLastCalledWith(DEFAULT_EDGE_TRANSPORT_OPTIONS.url, { + body: serializeEnvelope(ERROR_ENVELOPE, new TextEncoder()), + method: 'POST', + referrerPolicy: 'origin', + }); + }); + + it('sets rate limit headers', async () => { + const headers = { + get: jest.fn(), + }; + + mockFetch.mockImplementationOnce(() => + Promise.resolve({ + headers, + status: 200, + text: () => Promise.resolve({}), + }), + ); + + const transport = makeEdgeTransport(DEFAULT_EDGE_TRANSPORT_OPTIONS); + + expect(headers.get).toHaveBeenCalledTimes(0); + await transport.send(ERROR_ENVELOPE); + + expect(headers.get).toHaveBeenCalledTimes(2); + expect(headers.get).toHaveBeenCalledWith('X-Sentry-Rate-Limits'); + expect(headers.get).toHaveBeenCalledWith('Retry-After'); + }); + + it('allows for custom options to be passed in', async () => { + mockFetch.mockImplementationOnce(() => + Promise.resolve({ + headers: new Headers(), + status: 200, + text: () => Promise.resolve({}), + }), + ); + + const REQUEST_OPTIONS: RequestInit = { + referrerPolicy: 'strict-origin', + keepalive: false, + referrer: 'http://example.org', + }; + + const transport = makeEdgeTransport({ ...DEFAULT_EDGE_TRANSPORT_OPTIONS, fetchOptions: REQUEST_OPTIONS }); + + await transport.send(ERROR_ENVELOPE); + expect(mockFetch).toHaveBeenLastCalledWith(DEFAULT_EDGE_TRANSPORT_OPTIONS.url, { + body: serializeEnvelope(ERROR_ENVELOPE, new TextEncoder()), + method: 'POST', + ...REQUEST_OPTIONS, + }); + }); +}); diff --git a/packages/nextjs/test/integration/pages/api/doubleEndMethodOnVercel.ts b/packages/nextjs/test/integration/pages/api/doubleEndMethodOnVercel.ts new file mode 100644 index 000000000000..f32fcf55fafd --- /dev/null +++ b/packages/nextjs/test/integration/pages/api/doubleEndMethodOnVercel.ts @@ -0,0 +1,11 @@ +import { NextApiRequest, NextApiResponse } from 'next'; + +const handler = async (_req: NextApiRequest, res: NextApiResponse): Promise => { + // This handler calls .end twice. We test this to verify that this still doesn't throw because we're wrapping `.end`. + res.status(200).json({ success: true }); + res.end(); +}; + +handler.__sentry_test_doesnt_support_streaming__ = true; + +export default handler; diff --git a/packages/nextjs/test/integration/test/server/doubleEndMethodOnVercel.js b/packages/nextjs/test/integration/test/server/doubleEndMethodOnVercel.js new file mode 100644 index 000000000000..fa2b0e7cbeb4 --- /dev/null +++ b/packages/nextjs/test/integration/test/server/doubleEndMethodOnVercel.js @@ -0,0 +1,10 @@ +const assert = require('assert'); +const { getAsync } = require('../utils/server'); + +// This test asserts that our wrapping of `res.end` doesn't break API routes on Vercel if people call `res.json` or +// `res.send` multiple times in one request handler. +// https://github.com/getsentry/sentry-javascript/issues/6670 +module.exports = async ({ url: urlBase }) => { + const response = await getAsync(`${urlBase}/api/doubleEndMethodOnVercel`); + assert.equal(response, '{"success":true}'); +}; diff --git a/packages/nextjs/test/performance/client.test.ts b/packages/nextjs/test/performance/client.test.ts index 590a8254e109..0b3ae0e7437e 100644 --- a/packages/nextjs/test/performance/client.test.ts +++ b/packages/nextjs/test/performance/client.test.ts @@ -1,10 +1,10 @@ import { WINDOW } from '@sentry/react'; -import { Transaction } from '@sentry/types'; +import type { Transaction } from '@sentry/types'; import { JSDOM } from 'jsdom'; -import { NEXT_DATA as NextData } from 'next/dist/next-server/lib/utils'; +import type { NEXT_DATA as NextData } from 'next/dist/next-server/lib/utils'; import { default as Router } from 'next/router'; -import { nextRouterInstrumentation } from '../../src/performance/client'; +import { nextRouterInstrumentation } from '../../src/client/performance'; const globalObject = WINDOW as typeof WINDOW & { __BUILD_MANIFEST?: { diff --git a/packages/nextjs/test/index.server.test.ts b/packages/nextjs/test/serverSdk.test.ts similarity index 98% rename from packages/nextjs/test/index.server.test.ts rename to packages/nextjs/test/serverSdk.test.ts index 232d3b746f1e..1a32551317e5 100644 --- a/packages/nextjs/test/index.server.test.ts +++ b/packages/nextjs/test/serverSdk.test.ts @@ -1,10 +1,10 @@ import * as SentryNode from '@sentry/node'; import { getCurrentHub, NodeClient } from '@sentry/node'; -import { Integration } from '@sentry/types'; +import type { Integration } from '@sentry/types'; import { GLOBAL_OBJ, logger } from '@sentry/utils'; import * as domain from 'domain'; -import { init } from '../src/index.server'; +import { init } from '../src/index'; const { Integrations } = SentryNode; diff --git a/packages/nextjs/test/types/next.config.ts b/packages/nextjs/test/types/next.config.ts index a447cb167249..36e50a276125 100644 --- a/packages/nextjs/test/types/next.config.ts +++ b/packages/nextjs/test/types/next.config.ts @@ -1,4 +1,4 @@ -import { NextConfig } from 'next'; +import type { NextConfig } from 'next'; import { withSentryConfig } from '../../src/config/withSentryConfig'; diff --git a/packages/nextjs/test/utils/isBuild.test.ts b/packages/nextjs/test/utils/isBuild.test.ts deleted file mode 100644 index 0bc1523b041a..000000000000 --- a/packages/nextjs/test/utils/isBuild.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { isBuild } from '../../src/utils/isBuild'; - -let originalEnv: typeof process.env; -let originalArgv: typeof process.argv; - -function assertNoMagicValues(): void { - if (Object.keys(process.env).includes('SENTRY_BUILD_PHASE') || process.argv.includes('build')) { - throw new Error('Not starting test with a clean setup'); - } -} - -describe('isBuild()', () => { - beforeEach(() => { - assertNoMagicValues(); - originalEnv = { ...process.env }; - originalArgv = [...process.argv]; - }); - - afterEach(() => { - process.env = originalEnv; - process.argv = originalArgv; - assertNoMagicValues(); - }); - - it("detects 'build' in argv", () => { - // the result of calling `next build` - process.argv = ['/abs/path/to/node', '/abs/path/to/nextjs/excecutable', 'build']; - expect(isBuild()).toBe(true); - }); - - it("sets env var when 'build' in argv", () => { - // the result of calling `next build` - process.argv = ['/abs/path/to/node', '/abs/path/to/nextjs/excecutable', 'build']; - isBuild(); - expect(Object.keys(process.env).includes('SENTRY_BUILD_PHASE')).toBe(true); - }); - - it("does not set env var when 'build' not in argv", () => { - isBuild(); - expect(Object.keys(process.env).includes('SENTRY_BUILD_PHASE')).toBe(false); - }); - - it('detects env var', () => { - process.env.SENTRY_BUILD_PHASE = 'true'; - expect(isBuild()).toBe(true); - }); - - it("returns false when 'build' not in `argv` and env var not present", () => { - expect(isBuild()).toBe(false); - }); -}); diff --git a/packages/nextjs/test/utils/isESM.test.ts b/packages/nextjs/test/utils/isESM.test.ts deleted file mode 100644 index 22169377e730..000000000000 --- a/packages/nextjs/test/utils/isESM.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { isESM } from '../../src/utils/isESM'; - -// Based on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import -describe('import syntax', function () { - it('recognizes import syntax', function () { - expect(isESM("import dogs from 'dogs';")).toBe(true); - expect(isESM("import * as dogs from 'dogs';")).toBe(true); - expect(isESM("import { maisey } from 'dogs';")).toBe(true); - expect(isESM("import { charlie as goofball } from 'dogs';")).toBe(true); - expect(isESM("import { default as maisey } from 'dogs';")).toBe(true); - expect(isESM("import { charlie, masiey } from 'dogs';")).toBe(true); - expect(isESM("import { masiey, charlie as pickle } from 'dogs';")).toBe(true); - expect(isESM("import charlie, { maisey } from 'dogs';")).toBe(true); - expect(isESM("import maisey, * as dogs from 'dogs';")).toBe(true); - expect(isESM("import 'dogs';")).toBe(true); - }); -}); - -// Based on https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/export -describe('export syntax', function () { - it('recognizes exported declarations', () => { - expect(isESM('export var maisey, charlie;')).toBe(true); - expect(isESM('export let charlie, maisey;')).toBe(true); - expect(isESM("export var maisey = 'silly', charlie = 'goofy';")).toBe(true); - expect(isESM("export let charlie = 'goofy', maisey = 'silly';")).toBe(true); - expect(isESM("export const maisey = 'silly', charlie = 'goofy';")).toBe(true); - expect(isESM('export function doDogStuff() { /* ... */ }')).toBe(true); - expect(isESM('export class Dog { /* ... */ }')).toBe(true); - expect(isESM('export function* generateWayTooManyPhotosOnMyPhone() { /* ... */ }')).toBe(true); - expect(isESM('export const { maisey, charlie } = dogObject;')).toBe(true); - expect(isESM('export const { charlie, masiey: maiseyTheDog } = dogObject;')).toBe(true); - expect(isESM('export const [ maisey, charlie ] = dogArray;')).toBe(true); - }); - - it('recognizes lists of exports', () => { - expect(isESM('export { maisey, charlie };')).toBe(true); - expect(isESM('export { charlie as charlieMcCharlerson, masiey as theMaiseyMaiseyDog };')).toBe(true); - expect(isESM('export { charlie as default };')).toBe(true); - }); - - it('recognizes default exports', () => { - expect(isESM("export default 'dogs are great';")).toBe(true); - expect(isESM('export default function doDogStuff() { /* ... */ }')).toBe(true); - expect(isESM('export default class Dog { /* ... */ }')).toBe(true); - expect(isESM('export default function* generateWayTooManyPhotosOnMyPhone() { /* ... */ }')).toBe(true); - expect(isESM('export default function () { /* ... */ }')).toBe(true); - expect(isESM('export default class { /* ... */ }')).toBe(true); - expect(isESM('export default function* () { /* ... */ }')).toBe(true); - }); - - it('recognizes exports directly from another module', () => { - expect(isESM("export * from 'dogs';")).toBe(true); - expect(isESM("export * as dogs from 'dogs';")).toBe(true); - expect(isESM("export { maisey, charlie } from 'dogs';")).toBe(true); - expect( - isESM("export { maisey as goodGirl, charlie as omgWouldYouJustPeeAlreadyIWantToGoToBed } from 'dogs';"), - ).toBe(true); - expect(isESM("export { default } from 'dogs';")).toBe(true); - expect(isESM("export { default, maisey } from 'dogs';")).toBe(true); - }); -}); - -describe('potential false positives', () => { - it("doesn't get fooled by look-alikes", () => { - expect(isESM("'this is an import statement'")).toBe(false); - expect(isESM("'this is an export statement'")).toBe(false); - expect(isESM('import(dogs)')).toBe(false); - }); -}); diff --git a/packages/nextjs/test/utils/tunnelRoute.test.ts b/packages/nextjs/test/utils/tunnelRoute.test.ts index 8a46f8b174ea..0e0a6d48f47c 100644 --- a/packages/nextjs/test/utils/tunnelRoute.test.ts +++ b/packages/nextjs/test/utils/tunnelRoute.test.ts @@ -1,5 +1,6 @@ -import { NextjsOptions } from '../../src/utils/nextjsOptions'; -import { applyTunnelRouteOption } from '../../src/utils/tunnelRoute'; +import type { BrowserOptions } from '@sentry/react'; + +import { applyTunnelRouteOption } from '../../src/client/tunnelRoute'; const globalWithInjectedValues = global as typeof global & { __sentryRewritesTunnelPath__?: string; @@ -14,7 +15,7 @@ describe('applyTunnelRouteOption()', () => { globalWithInjectedValues.__sentryRewritesTunnelPath__ = '/my-error-monitoring-route'; const options: any = { dsn: 'https://11111111111111111111111111111111@o2222222.ingest.sentry.io/3333333', - } as NextjsOptions; + } as BrowserOptions; applyTunnelRouteOption(options); @@ -25,7 +26,7 @@ describe('applyTunnelRouteOption()', () => { globalWithInjectedValues.__sentryRewritesTunnelPath__ = '/my-error-monitoring-route'; const options: any = { // no dsn - } as NextjsOptions; + } as BrowserOptions; applyTunnelRouteOption(options); @@ -35,7 +36,7 @@ describe('applyTunnelRouteOption()', () => { it("should not apply `tunnelRoute` option when `tunnelRoute` option wasn't injected", () => { const options: any = { dsn: 'https://11111111111111111111111111111111@o2222222.ingest.sentry.io/3333333', - } as NextjsOptions; + } as BrowserOptions; applyTunnelRouteOption(options); @@ -46,7 +47,7 @@ describe('applyTunnelRouteOption()', () => { globalWithInjectedValues.__sentryRewritesTunnelPath__ = '/my-error-monitoring-route'; const options: any = { dsn: 'https://11111111111111111111111111111111@example.com/3333333', - } as NextjsOptions; + } as BrowserOptions; applyTunnelRouteOption(options); diff --git a/packages/nextjs/test/utils/userIntegrations.test.ts b/packages/nextjs/test/utils/userIntegrations.test.ts index 7a8693a8a524..431caa4071a3 100644 --- a/packages/nextjs/test/utils/userIntegrations.test.ts +++ b/packages/nextjs/test/utils/userIntegrations.test.ts @@ -1,5 +1,8 @@ -import type { IntegrationWithExclusionOption as Integration } from '../../src/utils/userIntegrations'; -import { addOrUpdateIntegration, UserIntegrations } from '../../src/utils/userIntegrations'; +import type { + IntegrationWithExclusionOption as Integration, + UserIntegrations, +} from '../../src/common/userIntegrations'; +import { addOrUpdateIntegration } from '../../src/common/userIntegrations'; type MockIntegrationOptions = { name: string; diff --git a/packages/node-integration-tests/package.json b/packages/node-integration-tests/package.json index 797ccb250ec6..1ed9cb62e8fd 100644 --- a/packages/node-integration-tests/package.json +++ b/packages/node-integration-tests/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/node-integration-tests", - "version": "7.29.0", + "version": "7.30.0", "license": "MIT", "engines": { "node": ">=10" diff --git a/packages/node-integration-tests/suites/express/sentry-trace/baggage-header-assign/test.ts b/packages/node-integration-tests/suites/express/sentry-trace/baggage-header-assign/test.ts index 2d51f934b7f4..65aea16c60a1 100644 --- a/packages/node-integration-tests/suites/express/sentry-trace/baggage-header-assign/test.ts +++ b/packages/node-integration-tests/suites/express/sentry-trace/baggage-header-assign/test.ts @@ -1,7 +1,7 @@ import * as path from 'path'; import { TestEnv } from '../../../../utils/index'; -import { TestAPIResponse } from '../server'; +import type { TestAPIResponse } from '../server'; test('Should not overwrite baggage if the incoming request already has Sentry baggage data.', async () => { const env = await TestEnv.init(__dirname, `${path.resolve(__dirname, '..')}/server.ts`); diff --git a/packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/test.ts b/packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/test.ts index 2650f1d7afc2..71defa704bba 100644 --- a/packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/test.ts +++ b/packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/test.ts @@ -1,7 +1,7 @@ import * as path from 'path'; import { TestEnv } from '../../../../utils/index'; -import { TestAPIResponse } from '../server'; +import type { TestAPIResponse } from '../server'; test('should attach a `baggage` header to an outgoing request.', async () => { const env = await TestEnv.init(__dirname, `${path.resolve(__dirname, '.')}/server.ts`); diff --git a/packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors-with-sentry-entries/test.ts b/packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors-with-sentry-entries/test.ts index 3de98d14bf06..0c2c2d39c606 100644 --- a/packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors-with-sentry-entries/test.ts +++ b/packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors-with-sentry-entries/test.ts @@ -1,7 +1,7 @@ import * as path from 'path'; import { TestEnv } from '../../../../utils/index'; -import { TestAPIResponse } from '../server'; +import type { TestAPIResponse } from '../server'; test('should ignore sentry-values in `baggage` header of a third party vendor and overwrite them with incoming DSC', async () => { const env = await TestEnv.init(__dirname, `${path.resolve(__dirname, '.')}/server.ts`); diff --git a/packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors/test.ts b/packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors/test.ts index 2f88610509c5..65d1d3d64c10 100644 --- a/packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors/test.ts +++ b/packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors/test.ts @@ -1,7 +1,7 @@ import * as path from 'path'; import { TestEnv } from '../../../../utils/index'; -import { TestAPIResponse } from '../server'; +import type { TestAPIResponse } from '../server'; test('should merge `baggage` header of a third party vendor with the Sentry DSC baggage items', async () => { const env = await TestEnv.init(__dirname, `${path.resolve(__dirname, '.')}/server.ts`); diff --git a/packages/node-integration-tests/suites/express/sentry-trace/baggage-transaction-name/test.ts b/packages/node-integration-tests/suites/express/sentry-trace/baggage-transaction-name/test.ts index d698fd2b7aa6..28e72d92ebe1 100644 --- a/packages/node-integration-tests/suites/express/sentry-trace/baggage-transaction-name/test.ts +++ b/packages/node-integration-tests/suites/express/sentry-trace/baggage-transaction-name/test.ts @@ -1,7 +1,7 @@ import * as path from 'path'; import { TestEnv } from '../../../../utils/index'; -import { TestAPIResponse } from '../server'; +import type { TestAPIResponse } from '../server'; test('Includes transaction in baggage if the transaction name is parameterized', async () => { const env = await TestEnv.init(__dirname, `${path.resolve(__dirname, '.')}/server.ts`); diff --git a/packages/node-integration-tests/suites/express/sentry-trace/trace-header-assign/test.ts b/packages/node-integration-tests/suites/express/sentry-trace/trace-header-assign/test.ts index 366051b9cac8..cb90133b043f 100644 --- a/packages/node-integration-tests/suites/express/sentry-trace/trace-header-assign/test.ts +++ b/packages/node-integration-tests/suites/express/sentry-trace/trace-header-assign/test.ts @@ -2,7 +2,7 @@ import { TRACEPARENT_REGEXP } from '@sentry/utils'; import * as path from 'path'; import { TestEnv } from '../../../../utils/index'; -import { TestAPIResponse } from '../server'; +import type { TestAPIResponse } from '../server'; test('Should assign `sentry-trace` header which sets parent trace id of an outgoing request.', async () => { const env = await TestEnv.init(__dirname, `${path.resolve(__dirname, '..')}/server.ts`); diff --git a/packages/node-integration-tests/suites/express/sentry-trace/trace-header-out/test.ts b/packages/node-integration-tests/suites/express/sentry-trace/trace-header-out/test.ts index bd4e29e7a807..aeccfd23a2e5 100644 --- a/packages/node-integration-tests/suites/express/sentry-trace/trace-header-out/test.ts +++ b/packages/node-integration-tests/suites/express/sentry-trace/trace-header-out/test.ts @@ -2,7 +2,7 @@ import { TRACEPARENT_REGEXP } from '@sentry/utils'; import * as path from 'path'; import { TestEnv } from '../../../../utils/index'; -import { TestAPIResponse } from '../server'; +import type { TestAPIResponse } from '../server'; test('should attach a `sentry-trace` header to an outgoing request.', async () => { const env = await TestEnv.init(__dirname, `${path.resolve(__dirname, '..')}/server.ts`); diff --git a/packages/node-integration-tests/suites/public-api/LocalVariables/local-variables.js b/packages/node-integration-tests/suites/public-api/LocalVariables/local-variables.js new file mode 100644 index 000000000000..db24a014c5a2 --- /dev/null +++ b/packages/node-integration-tests/suites/public-api/LocalVariables/local-variables.js @@ -0,0 +1,35 @@ +/* eslint-disable no-unused-vars */ +const Sentry = require('@sentry/node'); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + _experiments: { includeStackLocals: true }, + beforeSend: event => { + // eslint-disable-next-line no-console + console.log(JSON.stringify(event)); + }, +}); + +process.on('uncaughtException', () => { + // do nothing - this will prevent the Error below from closing this process +}); + +class Some { + two(name) { + throw new Error('Enough!'); + } +} + +function one(name) { + const arr = [1, '2', null]; + const obj = { + name, + num: 5, + }; + + const ty = new Some(); + + ty.two(name); +} + +one('some name'); diff --git a/packages/node-integration-tests/suites/public-api/LocalVariables/no-local-variables.js b/packages/node-integration-tests/suites/public-api/LocalVariables/no-local-variables.js new file mode 100644 index 000000000000..03c9254efea8 --- /dev/null +++ b/packages/node-integration-tests/suites/public-api/LocalVariables/no-local-variables.js @@ -0,0 +1,34 @@ +/* eslint-disable no-unused-vars */ +const Sentry = require('@sentry/node'); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + beforeSend: event => { + // eslint-disable-next-line no-console + console.log(JSON.stringify(event)); + }, +}); + +process.on('uncaughtException', () => { + // do nothing - this will prevent the Error below from closing this process +}); + +class Some { + two(name) { + throw new Error('Enough!'); + } +} + +function one(name) { + const arr = [1, '2', null]; + const obj = { + name, + num: 5, + }; + + const ty = new Some(); + + ty.two(name); +} + +one('some name'); diff --git a/packages/node-integration-tests/suites/public-api/LocalVariables/test.ts b/packages/node-integration-tests/suites/public-api/LocalVariables/test.ts new file mode 100644 index 000000000000..6603202a3013 --- /dev/null +++ b/packages/node-integration-tests/suites/public-api/LocalVariables/test.ts @@ -0,0 +1,54 @@ +import type { Event } from '@sentry/node'; +import * as childProcess from 'child_process'; +import * as path from 'path'; + +describe('LocalVariables integration', () => { + test('Should not include local variables by default', done => { + expect.assertions(2); + + const testScriptPath = path.resolve(__dirname, 'no-local-variables.js'); + + childProcess.exec(`node ${testScriptPath}`, { encoding: 'utf8' }, (_, stdout) => { + const event = JSON.parse(stdout) as Event; + + const frames = event.exception?.values?.[0].stacktrace?.frames || []; + const lastFrame = frames[frames.length - 1]; + + expect(lastFrame.vars).toBeUndefined(); + + const penultimateFrame = frames[frames.length - 2]; + + expect(penultimateFrame.vars).toBeUndefined(); + + done(); + }); + }); + + test('Should include local variables when enabled', done => { + expect.assertions(4); + + const testScriptPath = path.resolve(__dirname, 'local-variables.js'); + + childProcess.exec(`node ${testScriptPath}`, { encoding: 'utf8' }, (_, stdout) => { + const event = JSON.parse(stdout) as Event; + + const frames = event.exception?.values?.[0].stacktrace?.frames || []; + const lastFrame = frames[frames.length - 1]; + + expect(lastFrame.function).toBe('Some.two'); + expect(lastFrame.vars).toEqual({ name: 'some name' }); + + const penultimateFrame = frames[frames.length - 2]; + + expect(penultimateFrame.function).toBe('one'); + expect(penultimateFrame.vars).toEqual({ + name: 'some name', + arr: [1, '2', null], + obj: { name: 'some name', num: 5 }, + ty: '', + }); + + done(); + }); + }); +}); diff --git a/packages/node-integration-tests/suites/public-api/configureScope/clear_scope/test.ts b/packages/node-integration-tests/suites/public-api/configureScope/clear_scope/test.ts index 63c82f7b4054..4891187a10d4 100644 --- a/packages/node-integration-tests/suites/public-api/configureScope/clear_scope/test.ts +++ b/packages/node-integration-tests/suites/public-api/configureScope/clear_scope/test.ts @@ -1,4 +1,4 @@ -import { Event } from '@sentry/node'; +import type { Event } from '@sentry/node'; import { assertSentryEvent, TestEnv } from '../../../../utils'; diff --git a/packages/node-integration-tests/suites/public-api/setContext/multiple-contexts/test.ts b/packages/node-integration-tests/suites/public-api/setContext/multiple-contexts/test.ts index aaf53e26d83f..51ad8a30af17 100644 --- a/packages/node-integration-tests/suites/public-api/setContext/multiple-contexts/test.ts +++ b/packages/node-integration-tests/suites/public-api/setContext/multiple-contexts/test.ts @@ -1,4 +1,4 @@ -import { Event } from '@sentry/node'; +import type { Event } from '@sentry/node'; import { assertSentryEvent, TestEnv } from '../../../../utils'; diff --git a/packages/node-integration-tests/suites/public-api/setContext/non-serializable-context/test.ts b/packages/node-integration-tests/suites/public-api/setContext/non-serializable-context/test.ts index 03c8727d3b7e..85f29fdab4dc 100644 --- a/packages/node-integration-tests/suites/public-api/setContext/non-serializable-context/test.ts +++ b/packages/node-integration-tests/suites/public-api/setContext/non-serializable-context/test.ts @@ -1,4 +1,4 @@ -import { Event } from '@sentry/node'; +import type { Event } from '@sentry/node'; import { assertSentryEvent, TestEnv } from '../../../../utils'; diff --git a/packages/node-integration-tests/suites/public-api/setContext/simple-context/test.ts b/packages/node-integration-tests/suites/public-api/setContext/simple-context/test.ts index 94758652bbfb..89c1128fefb0 100644 --- a/packages/node-integration-tests/suites/public-api/setContext/simple-context/test.ts +++ b/packages/node-integration-tests/suites/public-api/setContext/simple-context/test.ts @@ -1,4 +1,4 @@ -import { Event } from '@sentry/node'; +import type { Event } from '@sentry/node'; import { assertSentryEvent, TestEnv } from '../../../../utils'; diff --git a/packages/node-integration-tests/suites/public-api/setUser/unset_user/test.ts b/packages/node-integration-tests/suites/public-api/setUser/unset_user/test.ts index 1945608623f2..46d4c5180526 100644 --- a/packages/node-integration-tests/suites/public-api/setUser/unset_user/test.ts +++ b/packages/node-integration-tests/suites/public-api/setUser/unset_user/test.ts @@ -1,4 +1,4 @@ -import { Event } from '@sentry/node'; +import type { Event } from '@sentry/node'; import { assertSentryEvent, TestEnv } from '../../../../utils'; diff --git a/packages/node-integration-tests/suites/public-api/withScope/nested-scopes/test.ts b/packages/node-integration-tests/suites/public-api/withScope/nested-scopes/test.ts index a9dcdcf7bd9d..21c842e6a6f7 100644 --- a/packages/node-integration-tests/suites/public-api/withScope/nested-scopes/test.ts +++ b/packages/node-integration-tests/suites/public-api/withScope/nested-scopes/test.ts @@ -1,4 +1,4 @@ -import { Event } from '@sentry/node'; +import type { Event } from '@sentry/node'; import { assertSentryEvent, TestEnv } from '../../../../utils'; diff --git a/packages/node-integration-tests/utils/index.ts b/packages/node-integration-tests/utils/index.ts index 0be096fc65c5..a46e76621e07 100644 --- a/packages/node-integration-tests/utils/index.ts +++ b/packages/node-integration-tests/utils/index.ts @@ -1,11 +1,12 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ import * as Sentry from '@sentry/node'; -import { EnvelopeItemType } from '@sentry/types'; +import type { EnvelopeItemType } from '@sentry/types'; import { logger, parseSemver } from '@sentry/utils'; -import axios, { AxiosRequestConfig } from 'axios'; -import { Express } from 'express'; -import * as http from 'http'; -import { AddressInfo } from 'net'; +import type { AxiosRequestConfig } from 'axios'; +import axios from 'axios'; +import type { Express } from 'express'; +import type * as http from 'http'; +import type { AddressInfo } from 'net'; import nock from 'nock'; import * as path from 'path'; diff --git a/packages/node/package.json b/packages/node/package.json index 5e1cee3e64fb..e41b6e83f22b 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/node", - "version": "7.29.0", + "version": "7.30.0", "description": "Official Sentry SDK for Node.js", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/node", @@ -16,9 +16,9 @@ "access": "public" }, "dependencies": { - "@sentry/core": "7.29.0", - "@sentry/types": "7.29.0", - "@sentry/utils": "7.29.0", + "@sentry/core": "7.30.0", + "@sentry/types": "7.30.0", + "@sentry/utils": "7.30.0", "cookie": "^0.4.1", "https-proxy-agent": "^5.0.0", "lru_map": "^0.3.3", @@ -33,15 +33,15 @@ "nock": "^13.0.5" }, "scripts": { - "build": "run-p build:rollup build:types", + "build": "run-p build:transpile build:types", "build:dev": "run-s build", - "build:rollup": "rollup -c rollup.npm.config.js", + "build:transpile": "rollup -c rollup.npm.config.js", "build:types": "tsc -p tsconfig.types.json", - "build:watch": "run-p build:rollup:watch build:types:watch", + "build:watch": "run-p build:transpile:watch build:types:watch", "build:dev:watch": "run-s build:watch", - "build:rollup:watch": "rollup -c rollup.npm.config.js --watch", + "build:transpile:watch": "rollup -c rollup.npm.config.js --watch", "build:types:watch": "tsc -p tsconfig.types.json --watch", - "build:npm": "ts-node ../../scripts/prepack.ts && npm pack ./build", + "build:tarball": "ts-node ../../scripts/prepack.ts && npm pack ./build", "circularDepCheck": "madge --circular src/index.ts", "clean": "rimraf build coverage sentry-node-*.tgz", "fix": "run-s fix:eslint fix:prettier", diff --git a/packages/node/src/client.ts b/packages/node/src/client.ts index 66dc8fb4ce27..0b3a925d775e 100644 --- a/packages/node/src/client.ts +++ b/packages/node/src/client.ts @@ -1,11 +1,12 @@ -import { BaseClient, Scope, SDK_VERSION, SessionFlusher } from '@sentry/core'; -import { Event, EventHint, Severity, SeverityLevel } from '@sentry/types'; +import type { Scope } from '@sentry/core'; +import { BaseClient, SDK_VERSION, SessionFlusher } from '@sentry/core'; +import type { Event, EventHint, Severity, SeverityLevel } from '@sentry/types'; import { logger, resolvedSyncPromise } from '@sentry/utils'; import * as os from 'os'; import { TextEncoder } from 'util'; import { eventFromMessage, eventFromUnknownInput } from './eventbuilder'; -import { NodeClientOptions } from './types'; +import type { NodeClientOptions } from './types'; /** * The Sentry Node SDK Client. diff --git a/packages/node/src/eventbuilder.ts b/packages/node/src/eventbuilder.ts index 3d97d2a602ab..f2bb1443a40f 100644 --- a/packages/node/src/eventbuilder.ts +++ b/packages/node/src/eventbuilder.ts @@ -1,5 +1,5 @@ import { getCurrentHub } from '@sentry/core'; -import { +import type { Event, EventHint, Exception, diff --git a/packages/node/src/handlers.ts b/packages/node/src/handlers.ts index 7433ad8720dd..1dfd8e4e27c9 100644 --- a/packages/node/src/handlers.ts +++ b/packages/node/src/handlers.ts @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { captureException, getCurrentHub, startTransaction, withScope } from '@sentry/core'; -import { Span } from '@sentry/types'; +import type { Span } from '@sentry/types'; +import type { AddRequestDataToEventOptions } from '@sentry/utils'; import { - AddRequestDataToEventOptions, addRequestDataToTransaction, baggageHeaderToDynamicSamplingContext, dropUndefinedKeys, @@ -12,9 +12,9 @@ import { logger, } from '@sentry/utils'; import * as domain from 'domain'; -import * as http from 'http'; +import type * as http from 'http'; -import { NodeClient } from './client'; +import type { NodeClient } from './client'; import { extractRequestData } from './requestdata'; // TODO (v8 / XXX) Remove this import import type { ParseRequestOptions } from './requestDataDeprecated'; @@ -256,7 +256,7 @@ export function errorHandler(options?: { * Callback method deciding whether error should be captured and sent to Sentry * @param error Captured middleware error */ - shouldHandleError?(error: MiddlewareError): boolean; + shouldHandleError?(this: void, error: MiddlewareError): boolean; }): ( error: MiddlewareError, req: http.IncomingMessage, @@ -269,7 +269,6 @@ export function errorHandler(options?: { res: http.ServerResponse, next: (error: MiddlewareError) => void, ): void { - // eslint-disable-next-line @typescript-eslint/unbound-method const shouldHandleError = (options && options.shouldHandleError) || defaultShouldHandleError; if (shouldHandleError(error)) { diff --git a/packages/node/src/integrations/console.ts b/packages/node/src/integrations/console.ts index 9b032c75cced..da0b684c4992 100644 --- a/packages/node/src/integrations/console.ts +++ b/packages/node/src/integrations/console.ts @@ -1,5 +1,5 @@ import { getCurrentHub } from '@sentry/core'; -import { Integration } from '@sentry/types'; +import type { Integration } from '@sentry/types'; import { fill, severityLevelFromString } from '@sentry/utils'; import * as util from 'util'; diff --git a/packages/node/src/integrations/context.ts b/packages/node/src/integrations/context.ts index c0991720d76e..33282d992716 100644 --- a/packages/node/src/integrations/context.ts +++ b/packages/node/src/integrations/context.ts @@ -1,5 +1,5 @@ /* eslint-disable max-lines */ -import { +import type { AppContext, Contexts, CultureContext, diff --git a/packages/node/src/integrations/contextlines.ts b/packages/node/src/integrations/contextlines.ts index cad196342f28..aa41ae68cdd8 100644 --- a/packages/node/src/integrations/contextlines.ts +++ b/packages/node/src/integrations/contextlines.ts @@ -1,4 +1,4 @@ -import { Event, EventProcessor, Integration, StackFrame } from '@sentry/types'; +import type { Event, EventProcessor, Integration, StackFrame } from '@sentry/types'; import { addContextToFrame } from '@sentry/utils'; import { readFile } from 'fs'; import { LRUMap } from 'lru_map'; diff --git a/packages/node/src/integrations/http.ts b/packages/node/src/integrations/http.ts index 9fbc79c335b4..867c6ad9df24 100644 --- a/packages/node/src/integrations/http.ts +++ b/packages/node/src/integrations/http.ts @@ -1,5 +1,6 @@ -import { getCurrentHub, Hub } from '@sentry/core'; -import { EventProcessor, Integration, Span, TracePropagationTargets } from '@sentry/types'; +import type { Hub } from '@sentry/core'; +import { getCurrentHub } from '@sentry/core'; +import type { EventProcessor, Integration, Span, TracePropagationTargets } from '@sentry/types'; import { dynamicSamplingContextToSentryBaggageHeader, fill, @@ -7,18 +8,12 @@ import { parseSemver, stringMatchesSomePattern, } from '@sentry/utils'; -import * as http from 'http'; -import * as https from 'https'; +import type * as http from 'http'; +import type * as https from 'https'; -import { NodeClient } from '../client'; -import { - cleanSpanDescription, - extractUrl, - isSentryRequest, - normalizeRequestArgs, - RequestMethod, - RequestMethodArgs, -} from './utils/http'; +import type { NodeClient } from '../client'; +import type { RequestMethod, RequestMethodArgs } from './utils/http'; +import { cleanSpanDescription, extractUrl, isSentryRequest, normalizeRequestArgs } from './utils/http'; const NODE_VERSION = parseSemver(process.versions.node); diff --git a/packages/node/src/integrations/index.ts b/packages/node/src/integrations/index.ts index cdb5145b0ea1..167a482e5b5f 100644 --- a/packages/node/src/integrations/index.ts +++ b/packages/node/src/integrations/index.ts @@ -7,3 +7,4 @@ export { Modules } from './modules'; export { ContextLines } from './contextlines'; export { Context } from './context'; export { RequestData } from './requestdata'; +export { LocalVariables } from './localvariables'; diff --git a/packages/node/src/integrations/inspector.d.ts b/packages/node/src/integrations/inspector.d.ts new file mode 100644 index 000000000000..527006910ee9 --- /dev/null +++ b/packages/node/src/integrations/inspector.d.ts @@ -0,0 +1,3359 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/unified-signatures */ +/* eslint-disable @typescript-eslint/explicit-member-accessibility */ +/* eslint-disable max-lines */ +/* eslint-disable @typescript-eslint/ban-types */ +// Type definitions for inspector + +// These definitions were copied from: +// https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/d37bf642ed2f3fe403e405892e2eb4240a191bb0/types/node/inspector.d.ts + +/** + * The `inspector` module provides an API for interacting with the V8 inspector. + * + * It can be accessed using: + * + * ```js + * const inspector = require('inspector'); + * ``` + * @see [source](https://github.com/nodejs/node/blob/v18.0.0/lib/inspector.js) + */ +declare module 'inspector' { + import EventEmitter = require('node:events'); + interface InspectorNotification { + method: string; + params: T; + } + namespace Schema { + /** + * Description of the protocol domain. + */ + interface Domain { + /** + * Domain name. + */ + name: string; + /** + * Domain version. + */ + version: string; + } + interface GetDomainsReturnType { + /** + * List of supported domains. + */ + domains: Domain[]; + } + } + namespace Runtime { + /** + * Unique script identifier. + */ + type ScriptId = string; + /** + * Unique object identifier. + */ + type RemoteObjectId = string; + /** + * Primitive value which cannot be JSON-stringified. + */ + type UnserializableValue = string; + /** + * Mirror object referencing original JavaScript object. + */ + interface RemoteObject { + /** + * Object type. + */ + type: string; + /** + * Object subtype hint. Specified for object type values only. + */ + subtype?: string | undefined; + /** + * Object class (constructor) name. Specified for object type values only. + */ + className?: string | undefined; + /** + * Remote object value in case of primitive values or JSON values (if it was requested). + */ + value?: any; + /** + * Primitive value which can not be JSON-stringified does not have value, but gets this property. + */ + unserializableValue?: UnserializableValue | undefined; + /** + * String representation of the object. + */ + description?: string | undefined; + /** + * Unique object identifier (for non-primitive values). + */ + objectId?: RemoteObjectId | undefined; + /** + * Preview containing abbreviated property values. Specified for object type values only. + * @experimental + */ + preview?: ObjectPreview | undefined; + /** + * @experimental + */ + customPreview?: CustomPreview | undefined; + } + /** + * @experimental + */ + interface CustomPreview { + header: string; + hasBody: boolean; + formatterObjectId: RemoteObjectId; + bindRemoteObjectFunctionId: RemoteObjectId; + configObjectId?: RemoteObjectId | undefined; + } + /** + * Object containing abbreviated remote object value. + * @experimental + */ + interface ObjectPreview { + /** + * Object type. + */ + type: string; + /** + * Object subtype hint. Specified for object type values only. + */ + subtype?: string | undefined; + /** + * String representation of the object. + */ + description?: string | undefined; + /** + * True iff some of the properties or entries of the original object did not fit. + */ + overflow: boolean; + /** + * List of the properties. + */ + properties: PropertyPreview[]; + /** + * List of the entries. Specified for map and set subtype values only. + */ + entries?: EntryPreview[] | undefined; + } + /** + * @experimental + */ + interface PropertyPreview { + /** + * Property name. + */ + name: string; + /** + * Object type. Accessor means that the property itself is an accessor property. + */ + type: string; + /** + * User-friendly property value string. + */ + value?: string | undefined; + /** + * Nested value preview. + */ + valuePreview?: ObjectPreview | undefined; + /** + * Object subtype hint. Specified for object type values only. + */ + subtype?: string | undefined; + } + /** + * @experimental + */ + interface EntryPreview { + /** + * Preview of the key. Specified for map-like collection entries. + */ + key?: ObjectPreview | undefined; + /** + * Preview of the value. + */ + value: ObjectPreview; + } + /** + * Object property descriptor. + */ + interface PropertyDescriptor { + /** + * Property name or symbol description. + */ + name: string; + /** + * The value associated with the property. + */ + value?: RemoteObject | undefined; + /** + * True if the value associated with the property may be changed (data descriptors only). + */ + writable?: boolean | undefined; + /** + * A function which serves as a getter for the property, or undefined if there is no getter (accessor descriptors only). + */ + get?: RemoteObject | undefined; + /** + * A function which serves as a setter for the property, or undefined if there is no setter (accessor descriptors only). + */ + set?: RemoteObject | undefined; + /** + * True if the type of this property descriptor may be changed and if the property may be deleted from the corresponding object. + */ + configurable: boolean; + /** + * True if this property shows up during enumeration of the properties on the corresponding object. + */ + enumerable: boolean; + /** + * True if the result was thrown during the evaluation. + */ + wasThrown?: boolean | undefined; + /** + * True if the property is owned for the object. + */ + isOwn?: boolean | undefined; + /** + * Property symbol object, if the property is of the symbol type. + */ + symbol?: RemoteObject | undefined; + } + /** + * Object internal property descriptor. This property isn't normally visible in JavaScript code. + */ + interface InternalPropertyDescriptor { + /** + * Conventional property name. + */ + name: string; + /** + * The value associated with the property. + */ + value?: RemoteObject | undefined; + } + /** + * Represents function call argument. Either remote object id objectId, primitive value, unserializable primitive value or neither of (for undefined) them should be specified. + */ + interface CallArgument { + /** + * Primitive value or serializable javascript object. + */ + value?: any; + /** + * Primitive value which can not be JSON-stringified. + */ + unserializableValue?: UnserializableValue | undefined; + /** + * Remote object handle. + */ + objectId?: RemoteObjectId | undefined; + } + /** + * Id of an execution context. + */ + type ExecutionContextId = number; + /** + * Description of an isolated world. + */ + interface ExecutionContextDescription { + /** + * Unique id of the execution context. It can be used to specify in which execution context script evaluation should be performed. + */ + id: ExecutionContextId; + /** + * Execution context origin. + */ + origin: string; + /** + * Human readable name describing given context. + */ + name: string; + /** + * Embedder-specific auxiliary data. + */ + auxData?: {} | undefined; + } + /** + * Detailed information about exception (or error) that was thrown during script compilation or execution. + */ + interface ExceptionDetails { + /** + * Exception id. + */ + exceptionId: number; + /** + * Exception text, which should be used together with exception object when available. + */ + text: string; + /** + * Line number of the exception location (0-based). + */ + lineNumber: number; + /** + * Column number of the exception location (0-based). + */ + columnNumber: number; + /** + * Script ID of the exception location. + */ + scriptId?: ScriptId | undefined; + /** + * URL of the exception location, to be used when the script was not reported. + */ + url?: string | undefined; + /** + * JavaScript stack trace if available. + */ + stackTrace?: StackTrace | undefined; + /** + * Exception object if available. + */ + exception?: RemoteObject | undefined; + /** + * Identifier of the context where exception happened. + */ + executionContextId?: ExecutionContextId | undefined; + } + /** + * Number of milliseconds since epoch. + */ + type Timestamp = number; + /** + * Stack entry for runtime errors and assertions. + */ + interface CallFrame { + /** + * JavaScript function name. + */ + functionName: string; + /** + * JavaScript script id. + */ + scriptId: ScriptId; + /** + * JavaScript script name or url. + */ + url: string; + /** + * JavaScript script line number (0-based). + */ + lineNumber: number; + /** + * JavaScript script column number (0-based). + */ + columnNumber: number; + } + /** + * Call frames for assertions or error messages. + */ + interface StackTrace { + /** + * String label of this stack trace. For async traces this may be a name of the function that initiated the async call. + */ + description?: string | undefined; + /** + * JavaScript function name. + */ + callFrames: CallFrame[]; + /** + * Asynchronous JavaScript stack trace that preceded this stack, if available. + */ + parent?: StackTrace | undefined; + /** + * Asynchronous JavaScript stack trace that preceded this stack, if available. + * @experimental + */ + parentId?: StackTraceId | undefined; + } + /** + * Unique identifier of current debugger. + * @experimental + */ + type UniqueDebuggerId = string; + /** + * If debuggerId is set stack trace comes from another debugger and can be resolved there. This allows to track cross-debugger calls. See Runtime.StackTrace and Debugger.paused for usages. + * @experimental + */ + interface StackTraceId { + id: string; + debuggerId?: UniqueDebuggerId | undefined; + } + interface EvaluateParameterType { + /** + * Expression to evaluate. + */ + expression: string; + /** + * Symbolic group name that can be used to release multiple objects. + */ + objectGroup?: string | undefined; + /** + * Determines whether Command Line API should be available during the evaluation. + */ + includeCommandLineAPI?: boolean | undefined; + /** + * In silent mode exceptions thrown during evaluation are not reported and do not pause execution. Overrides setPauseOnException state. + */ + silent?: boolean | undefined; + /** + * Specifies in which execution context to perform evaluation. If the parameter is omitted the evaluation will be performed in the context of the inspected page. + */ + contextId?: ExecutionContextId | undefined; + /** + * Whether the result is expected to be a JSON object that should be sent by value. + */ + returnByValue?: boolean | undefined; + /** + * Whether preview should be generated for the result. + * @experimental + */ + generatePreview?: boolean | undefined; + /** + * Whether execution should be treated as initiated by user in the UI. + */ + userGesture?: boolean | undefined; + /** + * Whether execution should await for resulting value and return once awaited promise is resolved. + */ + awaitPromise?: boolean | undefined; + } + interface AwaitPromiseParameterType { + /** + * Identifier of the promise. + */ + promiseObjectId: RemoteObjectId; + /** + * Whether the result is expected to be a JSON object that should be sent by value. + */ + returnByValue?: boolean | undefined; + /** + * Whether preview should be generated for the result. + */ + generatePreview?: boolean | undefined; + } + interface CallFunctionOnParameterType { + /** + * Declaration of the function to call. + */ + functionDeclaration: string; + /** + * Identifier of the object to call function on. Either objectId or executionContextId should be specified. + */ + objectId?: RemoteObjectId | undefined; + /** + * Call arguments. All call arguments must belong to the same JavaScript world as the target object. + */ + arguments?: CallArgument[] | undefined; + /** + * In silent mode exceptions thrown during evaluation are not reported and do not pause execution. Overrides setPauseOnException state. + */ + silent?: boolean | undefined; + /** + * Whether the result is expected to be a JSON object which should be sent by value. + */ + returnByValue?: boolean | undefined; + /** + * Whether preview should be generated for the result. + * @experimental + */ + generatePreview?: boolean | undefined; + /** + * Whether execution should be treated as initiated by user in the UI. + */ + userGesture?: boolean | undefined; + /** + * Whether execution should await for resulting value and return once awaited promise is resolved. + */ + awaitPromise?: boolean | undefined; + /** + * Specifies execution context which global object will be used to call function on. Either executionContextId or objectId should be specified. + */ + executionContextId?: ExecutionContextId | undefined; + /** + * Symbolic group name that can be used to release multiple objects. If objectGroup is not specified and objectId is, objectGroup will be inherited from object. + */ + objectGroup?: string | undefined; + } + interface GetPropertiesParameterType { + /** + * Identifier of the object to return properties for. + */ + objectId: RemoteObjectId; + /** + * If true, returns properties belonging only to the element itself, not to its prototype chain. + */ + ownProperties?: boolean | undefined; + /** + * If true, returns accessor properties (with getter/setter) only; internal properties are not returned either. + * @experimental + */ + accessorPropertiesOnly?: boolean | undefined; + /** + * Whether preview should be generated for the results. + * @experimental + */ + generatePreview?: boolean | undefined; + } + interface ReleaseObjectParameterType { + /** + * Identifier of the object to release. + */ + objectId: RemoteObjectId; + } + interface ReleaseObjectGroupParameterType { + /** + * Symbolic object group name. + */ + objectGroup: string; + } + interface SetCustomObjectFormatterEnabledParameterType { + enabled: boolean; + } + interface CompileScriptParameterType { + /** + * Expression to compile. + */ + expression: string; + /** + * Source url to be set for the script. + */ + sourceURL: string; + /** + * Specifies whether the compiled script should be persisted. + */ + persistScript: boolean; + /** + * Specifies in which execution context to perform script run. If the parameter is omitted the evaluation will be performed in the context of the inspected page. + */ + executionContextId?: ExecutionContextId | undefined; + } + interface RunScriptParameterType { + /** + * Id of the script to run. + */ + scriptId: ScriptId; + /** + * Specifies in which execution context to perform script run. If the parameter is omitted the evaluation will be performed in the context of the inspected page. + */ + executionContextId?: ExecutionContextId | undefined; + /** + * Symbolic group name that can be used to release multiple objects. + */ + objectGroup?: string | undefined; + /** + * In silent mode exceptions thrown during evaluation are not reported and do not pause execution. Overrides setPauseOnException state. + */ + silent?: boolean | undefined; + /** + * Determines whether Command Line API should be available during the evaluation. + */ + includeCommandLineAPI?: boolean | undefined; + /** + * Whether the result is expected to be a JSON object which should be sent by value. + */ + returnByValue?: boolean | undefined; + /** + * Whether preview should be generated for the result. + */ + generatePreview?: boolean | undefined; + /** + * Whether execution should await for resulting value and return once awaited promise is resolved. + */ + awaitPromise?: boolean | undefined; + } + interface QueryObjectsParameterType { + /** + * Identifier of the prototype to return objects for. + */ + prototypeObjectId: RemoteObjectId; + } + interface GlobalLexicalScopeNamesParameterType { + /** + * Specifies in which execution context to lookup global scope variables. + */ + executionContextId?: ExecutionContextId | undefined; + } + interface EvaluateReturnType { + /** + * Evaluation result. + */ + result: RemoteObject; + /** + * Exception details. + */ + exceptionDetails?: ExceptionDetails | undefined; + } + interface AwaitPromiseReturnType { + /** + * Promise result. Will contain rejected value if promise was rejected. + */ + result: RemoteObject; + /** + * Exception details if stack strace is available. + */ + exceptionDetails?: ExceptionDetails | undefined; + } + interface CallFunctionOnReturnType { + /** + * Call result. + */ + result: RemoteObject; + /** + * Exception details. + */ + exceptionDetails?: ExceptionDetails | undefined; + } + interface GetPropertiesReturnType { + /** + * Object properties. + */ + result: PropertyDescriptor[]; + /** + * Internal object properties (only of the element itself). + */ + internalProperties?: InternalPropertyDescriptor[] | undefined; + /** + * Exception details. + */ + exceptionDetails?: ExceptionDetails | undefined; + } + interface CompileScriptReturnType { + /** + * Id of the script. + */ + scriptId?: ScriptId | undefined; + /** + * Exception details. + */ + exceptionDetails?: ExceptionDetails | undefined; + } + interface RunScriptReturnType { + /** + * Run result. + */ + result: RemoteObject; + /** + * Exception details. + */ + exceptionDetails?: ExceptionDetails | undefined; + } + interface QueryObjectsReturnType { + /** + * Array with objects. + */ + objects: RemoteObject; + } + interface GlobalLexicalScopeNamesReturnType { + names: string[]; + } + interface ExecutionContextCreatedEventDataType { + /** + * A newly created execution context. + */ + context: ExecutionContextDescription; + } + interface ExecutionContextDestroyedEventDataType { + /** + * Id of the destroyed context + */ + executionContextId: ExecutionContextId; + } + interface ExceptionThrownEventDataType { + /** + * Timestamp of the exception. + */ + timestamp: Timestamp; + exceptionDetails: ExceptionDetails; + } + interface ExceptionRevokedEventDataType { + /** + * Reason describing why exception was revoked. + */ + reason: string; + /** + * The id of revoked exception, as reported in exceptionThrown. + */ + exceptionId: number; + } + interface ConsoleAPICalledEventDataType { + /** + * Type of the call. + */ + type: string; + /** + * Call arguments. + */ + args: RemoteObject[]; + /** + * Identifier of the context where the call was made. + */ + executionContextId: ExecutionContextId; + /** + * Call timestamp. + */ + timestamp: Timestamp; + /** + * Stack trace captured when the call was made. + */ + stackTrace?: StackTrace | undefined; + /** + * Console context descriptor for calls on non-default console context (not console.*): 'anonymous#unique-logger-id' for call on unnamed context, 'name#unique-logger-id' for call on named context. + * @experimental + */ + context?: string | undefined; + } + interface InspectRequestedEventDataType { + object: RemoteObject; + hints: {}; + } + } + namespace Debugger { + /** + * Breakpoint identifier. + */ + type BreakpointId = string; + /** + * Call frame identifier. + */ + type CallFrameId = string; + /** + * Location in the source code. + */ + interface Location { + /** + * Script identifier as reported in the Debugger.scriptParsed. + */ + scriptId: Runtime.ScriptId; + /** + * Line number in the script (0-based). + */ + lineNumber: number; + /** + * Column number in the script (0-based). + */ + columnNumber?: number | undefined; + } + /** + * Location in the source code. + * @experimental + */ + interface ScriptPosition { + lineNumber: number; + columnNumber: number; + } + /** + * JavaScript call frame. Array of call frames form the call stack. + */ + interface CallFrame { + /** + * Call frame identifier. This identifier is only valid while the virtual machine is paused. + */ + callFrameId: CallFrameId; + /** + * Name of the JavaScript function called on this call frame. + */ + functionName: string; + /** + * Location in the source code. + */ + functionLocation?: Location | undefined; + /** + * Location in the source code. + */ + location: Location; + /** + * JavaScript script name or url. + */ + url: string; + /** + * Scope chain for this call frame. + */ + scopeChain: Scope[]; + /** + * this object for this call frame. + */ + this: Runtime.RemoteObject; + /** + * The value being returned, if the function is at return point. + */ + returnValue?: Runtime.RemoteObject | undefined; + } + /** + * Scope description. + */ + interface Scope { + /** + * Scope type. + */ + type: string; + /** + * Object representing the scope. For global and with scopes it represents the actual object; for the rest of the scopes, it is artificial transient object enumerating scope variables as its properties. + */ + object: Runtime.RemoteObject; + name?: string | undefined; + /** + * Location in the source code where scope starts + */ + startLocation?: Location | undefined; + /** + * Location in the source code where scope ends + */ + endLocation?: Location | undefined; + } + /** + * Search match for resource. + */ + interface SearchMatch { + /** + * Line number in resource content. + */ + lineNumber: number; + /** + * Line with match content. + */ + lineContent: string; + } + interface BreakLocation { + /** + * Script identifier as reported in the Debugger.scriptParsed. + */ + scriptId: Runtime.ScriptId; + /** + * Line number in the script (0-based). + */ + lineNumber: number; + /** + * Column number in the script (0-based). + */ + columnNumber?: number | undefined; + type?: string | undefined; + } + interface SetBreakpointsActiveParameterType { + /** + * New value for breakpoints active state. + */ + active: boolean; + } + interface SetSkipAllPausesParameterType { + /** + * New value for skip pauses state. + */ + skip: boolean; + } + interface SetBreakpointByUrlParameterType { + /** + * Line number to set breakpoint at. + */ + lineNumber: number; + /** + * URL of the resources to set breakpoint on. + */ + url?: string | undefined; + /** + * Regex pattern for the URLs of the resources to set breakpoints on. Either url or urlRegex must be specified. + */ + urlRegex?: string | undefined; + /** + * Script hash of the resources to set breakpoint on. + */ + scriptHash?: string | undefined; + /** + * Offset in the line to set breakpoint at. + */ + columnNumber?: number | undefined; + /** + * Expression to use as a breakpoint condition. When specified, debugger will only stop on the breakpoint if this expression evaluates to true. + */ + condition?: string | undefined; + } + interface SetBreakpointParameterType { + /** + * Location to set breakpoint in. + */ + location: Location; + /** + * Expression to use as a breakpoint condition. When specified, debugger will only stop on the breakpoint if this expression evaluates to true. + */ + condition?: string | undefined; + } + interface RemoveBreakpointParameterType { + breakpointId: BreakpointId; + } + interface GetPossibleBreakpointsParameterType { + /** + * Start of range to search possible breakpoint locations in. + */ + start: Location; + /** + * End of range to search possible breakpoint locations in (excluding). When not specified, end of scripts is used as end of range. + */ + end?: Location | undefined; + /** + * Only consider locations which are in the same (non-nested) function as start. + */ + restrictToFunction?: boolean | undefined; + } + interface ContinueToLocationParameterType { + /** + * Location to continue to. + */ + location: Location; + targetCallFrames?: string | undefined; + } + interface PauseOnAsyncCallParameterType { + /** + * Debugger will pause when async call with given stack trace is started. + */ + parentStackTraceId: Runtime.StackTraceId; + } + interface StepIntoParameterType { + /** + * Debugger will issue additional Debugger.paused notification if any async task is scheduled before next pause. + * @experimental + */ + breakOnAsyncCall?: boolean | undefined; + } + interface GetStackTraceParameterType { + stackTraceId: Runtime.StackTraceId; + } + interface SearchInContentParameterType { + /** + * Id of the script to search in. + */ + scriptId: Runtime.ScriptId; + /** + * String to search for. + */ + query: string; + /** + * If true, search is case sensitive. + */ + caseSensitive?: boolean | undefined; + /** + * If true, treats string parameter as regex. + */ + isRegex?: boolean | undefined; + } + interface SetScriptSourceParameterType { + /** + * Id of the script to edit. + */ + scriptId: Runtime.ScriptId; + /** + * New content of the script. + */ + scriptSource: string; + /** + * If true the change will not actually be applied. Dry run may be used to get result description without actually modifying the code. + */ + dryRun?: boolean | undefined; + } + interface RestartFrameParameterType { + /** + * Call frame identifier to evaluate on. + */ + callFrameId: CallFrameId; + } + interface GetScriptSourceParameterType { + /** + * Id of the script to get source for. + */ + scriptId: Runtime.ScriptId; + } + interface SetPauseOnExceptionsParameterType { + /** + * Pause on exceptions mode. + */ + state: string; + } + interface EvaluateOnCallFrameParameterType { + /** + * Call frame identifier to evaluate on. + */ + callFrameId: CallFrameId; + /** + * Expression to evaluate. + */ + expression: string; + /** + * String object group name to put result into (allows rapid releasing resulting object handles using releaseObjectGroup). + */ + objectGroup?: string | undefined; + /** + * Specifies whether command line API should be available to the evaluated expression, defaults to false. + */ + includeCommandLineAPI?: boolean | undefined; + /** + * In silent mode exceptions thrown during evaluation are not reported and do not pause execution. Overrides setPauseOnException state. + */ + silent?: boolean | undefined; + /** + * Whether the result is expected to be a JSON object that should be sent by value. + */ + returnByValue?: boolean | undefined; + /** + * Whether preview should be generated for the result. + * @experimental + */ + generatePreview?: boolean | undefined; + /** + * Whether to throw an exception if side effect cannot be ruled out during evaluation. + */ + throwOnSideEffect?: boolean | undefined; + } + interface SetVariableValueParameterType { + /** + * 0-based number of scope as was listed in scope chain. Only 'local', 'closure' and 'catch' scope types are allowed. Other scopes could be manipulated manually. + */ + scopeNumber: number; + /** + * Variable name. + */ + variableName: string; + /** + * New variable value. + */ + newValue: Runtime.CallArgument; + /** + * Id of callframe that holds variable. + */ + callFrameId: CallFrameId; + } + interface SetReturnValueParameterType { + /** + * New return value. + */ + newValue: Runtime.CallArgument; + } + interface SetAsyncCallStackDepthParameterType { + /** + * Maximum depth of async call stacks. Setting to 0 will effectively disable collecting async call stacks (default). + */ + maxDepth: number; + } + interface SetBlackboxPatternsParameterType { + /** + * Array of regexps that will be used to check script url for blackbox state. + */ + patterns: string[]; + } + interface SetBlackboxedRangesParameterType { + /** + * Id of the script. + */ + scriptId: Runtime.ScriptId; + positions: ScriptPosition[]; + } + interface EnableReturnType { + /** + * Unique identifier of the debugger. + * @experimental + */ + debuggerId: Runtime.UniqueDebuggerId; + } + interface SetBreakpointByUrlReturnType { + /** + * Id of the created breakpoint for further reference. + */ + breakpointId: BreakpointId; + /** + * List of the locations this breakpoint resolved into upon addition. + */ + locations: Location[]; + } + interface SetBreakpointReturnType { + /** + * Id of the created breakpoint for further reference. + */ + breakpointId: BreakpointId; + /** + * Location this breakpoint resolved into. + */ + actualLocation: Location; + } + interface GetPossibleBreakpointsReturnType { + /** + * List of the possible breakpoint locations. + */ + locations: BreakLocation[]; + } + interface GetStackTraceReturnType { + stackTrace: Runtime.StackTrace; + } + interface SearchInContentReturnType { + /** + * List of search matches. + */ + result: SearchMatch[]; + } + interface SetScriptSourceReturnType { + /** + * New stack trace in case editing has happened while VM was stopped. + */ + callFrames?: CallFrame[] | undefined; + /** + * Whether current call stack was modified after applying the changes. + */ + stackChanged?: boolean | undefined; + /** + * Async stack trace, if any. + */ + asyncStackTrace?: Runtime.StackTrace | undefined; + /** + * Async stack trace, if any. + * @experimental + */ + asyncStackTraceId?: Runtime.StackTraceId | undefined; + /** + * Exception details if any. + */ + exceptionDetails?: Runtime.ExceptionDetails | undefined; + } + interface RestartFrameReturnType { + /** + * New stack trace. + */ + callFrames: CallFrame[]; + /** + * Async stack trace, if any. + */ + asyncStackTrace?: Runtime.StackTrace | undefined; + /** + * Async stack trace, if any. + * @experimental + */ + asyncStackTraceId?: Runtime.StackTraceId | undefined; + } + interface GetScriptSourceReturnType { + /** + * Script source. + */ + scriptSource: string; + } + interface EvaluateOnCallFrameReturnType { + /** + * Object wrapper for the evaluation result. + */ + result: Runtime.RemoteObject; + /** + * Exception details. + */ + exceptionDetails?: Runtime.ExceptionDetails | undefined; + } + interface ScriptParsedEventDataType { + /** + * Identifier of the script parsed. + */ + scriptId: Runtime.ScriptId; + /** + * URL or name of the script parsed (if any). + */ + url: string; + /** + * Line offset of the script within the resource with given URL (for script tags). + */ + startLine: number; + /** + * Column offset of the script within the resource with given URL. + */ + startColumn: number; + /** + * Last line of the script. + */ + endLine: number; + /** + * Length of the last line of the script. + */ + endColumn: number; + /** + * Specifies script creation context. + */ + executionContextId: Runtime.ExecutionContextId; + /** + * Content hash of the script. + */ + hash: string; + /** + * Embedder-specific auxiliary data. + */ + executionContextAuxData?: {} | undefined; + /** + * True, if this script is generated as a result of the live edit operation. + * @experimental + */ + isLiveEdit?: boolean | undefined; + /** + * URL of source map associated with script (if any). + */ + sourceMapURL?: string | undefined; + /** + * True, if this script has sourceURL. + */ + hasSourceURL?: boolean | undefined; + /** + * True, if this script is ES6 module. + */ + isModule?: boolean | undefined; + /** + * This script length. + */ + length?: number | undefined; + /** + * JavaScript top stack frame of where the script parsed event was triggered if available. + * @experimental + */ + stackTrace?: Runtime.StackTrace | undefined; + } + interface ScriptFailedToParseEventDataType { + /** + * Identifier of the script parsed. + */ + scriptId: Runtime.ScriptId; + /** + * URL or name of the script parsed (if any). + */ + url: string; + /** + * Line offset of the script within the resource with given URL (for script tags). + */ + startLine: number; + /** + * Column offset of the script within the resource with given URL. + */ + startColumn: number; + /** + * Last line of the script. + */ + endLine: number; + /** + * Length of the last line of the script. + */ + endColumn: number; + /** + * Specifies script creation context. + */ + executionContextId: Runtime.ExecutionContextId; + /** + * Content hash of the script. + */ + hash: string; + /** + * Embedder-specific auxiliary data. + */ + executionContextAuxData?: {} | undefined; + /** + * URL of source map associated with script (if any). + */ + sourceMapURL?: string | undefined; + /** + * True, if this script has sourceURL. + */ + hasSourceURL?: boolean | undefined; + /** + * True, if this script is ES6 module. + */ + isModule?: boolean | undefined; + /** + * This script length. + */ + length?: number | undefined; + /** + * JavaScript top stack frame of where the script parsed event was triggered if available. + * @experimental + */ + stackTrace?: Runtime.StackTrace | undefined; + } + interface BreakpointResolvedEventDataType { + /** + * Breakpoint unique identifier. + */ + breakpointId: BreakpointId; + /** + * Actual breakpoint location. + */ + location: Location; + } + interface PausedEventDataType { + /** + * Call stack the virtual machine stopped on. + */ + callFrames: CallFrame[]; + /** + * Pause reason. + */ + reason: string; + /** + * Object containing break-specific auxiliary properties. + */ + data?: {} | undefined; + /** + * Hit breakpoints IDs + */ + hitBreakpoints?: string[] | undefined; + /** + * Async stack trace, if any. + */ + asyncStackTrace?: Runtime.StackTrace | undefined; + /** + * Async stack trace, if any. + * @experimental + */ + asyncStackTraceId?: Runtime.StackTraceId | undefined; + /** + * Just scheduled async call will have this stack trace as parent stack during async execution. This field is available only after Debugger.stepInto call with breakOnAsynCall flag. + * @experimental + */ + asyncCallStackTraceId?: Runtime.StackTraceId | undefined; + } + } + namespace Console { + /** + * Console message. + */ + interface ConsoleMessage { + /** + * Message source. + */ + source: string; + /** + * Message severity. + */ + level: string; + /** + * Message text. + */ + text: string; + /** + * URL of the message origin. + */ + url?: string | undefined; + /** + * Line number in the resource that generated this message (1-based). + */ + line?: number | undefined; + /** + * Column number in the resource that generated this message (1-based). + */ + column?: number | undefined; + } + interface MessageAddedEventDataType { + /** + * Console message that has been added. + */ + message: ConsoleMessage; + } + } + namespace Profiler { + /** + * Profile node. Holds callsite information, execution statistics and child nodes. + */ + interface ProfileNode { + /** + * Unique id of the node. + */ + id: number; + /** + * Function location. + */ + callFrame: Runtime.CallFrame; + /** + * Number of samples where this node was on top of the call stack. + */ + hitCount?: number | undefined; + /** + * Child node ids. + */ + children?: number[] | undefined; + /** + * The reason of being not optimized. The function may be deoptimized or marked as don't optimize. + */ + deoptReason?: string | undefined; + /** + * An array of source position ticks. + */ + positionTicks?: PositionTickInfo[] | undefined; + } + /** + * Profile. + */ + interface Profile { + /** + * The list of profile nodes. First item is the root node. + */ + nodes: ProfileNode[]; + /** + * Profiling start timestamp in microseconds. + */ + startTime: number; + /** + * Profiling end timestamp in microseconds. + */ + endTime: number; + /** + * Ids of samples top nodes. + */ + samples?: number[] | undefined; + /** + * Time intervals between adjacent samples in microseconds. The first delta is relative to the profile startTime. + */ + timeDeltas?: number[] | undefined; + } + /** + * Specifies a number of samples attributed to a certain source position. + */ + interface PositionTickInfo { + /** + * Source line number (1-based). + */ + line: number; + /** + * Number of samples attributed to the source line. + */ + ticks: number; + } + /** + * Coverage data for a source range. + */ + interface CoverageRange { + /** + * JavaScript script source offset for the range start. + */ + startOffset: number; + /** + * JavaScript script source offset for the range end. + */ + endOffset: number; + /** + * Collected execution count of the source range. + */ + count: number; + } + /** + * Coverage data for a JavaScript function. + */ + interface FunctionCoverage { + /** + * JavaScript function name. + */ + functionName: string; + /** + * Source ranges inside the function with coverage data. + */ + ranges: CoverageRange[]; + /** + * Whether coverage data for this function has block granularity. + */ + isBlockCoverage: boolean; + } + /** + * Coverage data for a JavaScript script. + */ + interface ScriptCoverage { + /** + * JavaScript script id. + */ + scriptId: Runtime.ScriptId; + /** + * JavaScript script name or url. + */ + url: string; + /** + * Functions contained in the script that has coverage data. + */ + functions: FunctionCoverage[]; + } + /** + * Describes a type collected during runtime. + * @experimental + */ + interface TypeObject { + /** + * Name of a type collected with type profiling. + */ + name: string; + } + /** + * Source offset and types for a parameter or return value. + * @experimental + */ + interface TypeProfileEntry { + /** + * Source offset of the parameter or end of function for return values. + */ + offset: number; + /** + * The types for this parameter or return value. + */ + types: TypeObject[]; + } + /** + * Type profile data collected during runtime for a JavaScript script. + * @experimental + */ + interface ScriptTypeProfile { + /** + * JavaScript script id. + */ + scriptId: Runtime.ScriptId; + /** + * JavaScript script name or url. + */ + url: string; + /** + * Type profile entries for parameters and return values of the functions in the script. + */ + entries: TypeProfileEntry[]; + } + interface SetSamplingIntervalParameterType { + /** + * New sampling interval in microseconds. + */ + interval: number; + } + interface StartPreciseCoverageParameterType { + /** + * Collect accurate call counts beyond simple 'covered' or 'not covered'. + */ + callCount?: boolean | undefined; + /** + * Collect block-based coverage. + */ + detailed?: boolean | undefined; + } + interface StopReturnType { + /** + * Recorded profile. + */ + profile: Profile; + } + interface TakePreciseCoverageReturnType { + /** + * Coverage data for the current isolate. + */ + result: ScriptCoverage[]; + } + interface GetBestEffortCoverageReturnType { + /** + * Coverage data for the current isolate. + */ + result: ScriptCoverage[]; + } + interface TakeTypeProfileReturnType { + /** + * Type profile for all scripts since startTypeProfile() was turned on. + */ + result: ScriptTypeProfile[]; + } + interface ConsoleProfileStartedEventDataType { + id: string; + /** + * Location of console.profile(). + */ + location: Debugger.Location; + /** + * Profile title passed as an argument to console.profile(). + */ + title?: string | undefined; + } + interface ConsoleProfileFinishedEventDataType { + id: string; + /** + * Location of console.profileEnd(). + */ + location: Debugger.Location; + profile: Profile; + /** + * Profile title passed as an argument to console.profile(). + */ + title?: string | undefined; + } + } + namespace HeapProfiler { + /** + * Heap snapshot object id. + */ + type HeapSnapshotObjectId = string; + /** + * Sampling Heap Profile node. Holds callsite information, allocation statistics and child nodes. + */ + interface SamplingHeapProfileNode { + /** + * Function location. + */ + callFrame: Runtime.CallFrame; + /** + * Allocations size in bytes for the node excluding children. + */ + selfSize: number; + /** + * Child nodes. + */ + children: SamplingHeapProfileNode[]; + } + /** + * Profile. + */ + interface SamplingHeapProfile { + head: SamplingHeapProfileNode; + } + interface StartTrackingHeapObjectsParameterType { + trackAllocations?: boolean | undefined; + } + interface StopTrackingHeapObjectsParameterType { + /** + * If true 'reportHeapSnapshotProgress' events will be generated while snapshot is being taken when the tracking is stopped. + */ + reportProgress?: boolean | undefined; + } + interface TakeHeapSnapshotParameterType { + /** + * If true 'reportHeapSnapshotProgress' events will be generated while snapshot is being taken. + */ + reportProgress?: boolean | undefined; + } + interface GetObjectByHeapObjectIdParameterType { + objectId: HeapSnapshotObjectId; + /** + * Symbolic group name that can be used to release multiple objects. + */ + objectGroup?: string | undefined; + } + interface AddInspectedHeapObjectParameterType { + /** + * Heap snapshot object id to be accessible by means of $x command line API. + */ + heapObjectId: HeapSnapshotObjectId; + } + interface GetHeapObjectIdParameterType { + /** + * Identifier of the object to get heap object id for. + */ + objectId: Runtime.RemoteObjectId; + } + interface StartSamplingParameterType { + /** + * Average sample interval in bytes. Poisson distribution is used for the intervals. The default value is 32768 bytes. + */ + samplingInterval?: number | undefined; + } + interface GetObjectByHeapObjectIdReturnType { + /** + * Evaluation result. + */ + result: Runtime.RemoteObject; + } + interface GetHeapObjectIdReturnType { + /** + * Id of the heap snapshot object corresponding to the passed remote object id. + */ + heapSnapshotObjectId: HeapSnapshotObjectId; + } + interface StopSamplingReturnType { + /** + * Recorded sampling heap profile. + */ + profile: SamplingHeapProfile; + } + interface GetSamplingProfileReturnType { + /** + * Return the sampling profile being collected. + */ + profile: SamplingHeapProfile; + } + interface AddHeapSnapshotChunkEventDataType { + chunk: string; + } + interface ReportHeapSnapshotProgressEventDataType { + done: number; + total: number; + finished?: boolean | undefined; + } + interface LastSeenObjectIdEventDataType { + lastSeenObjectId: number; + timestamp: number; + } + interface HeapStatsUpdateEventDataType { + /** + * An array of triplets. Each triplet describes a fragment. The first integer is the fragment index, the second integer is a total count of objects for the fragment, the third integer is a total size of the objects for the fragment. + */ + statsUpdate: number[]; + } + } + namespace NodeTracing { + interface TraceConfig { + /** + * Controls how the trace buffer stores data. + */ + recordMode?: string | undefined; + /** + * Included category filters. + */ + includedCategories: string[]; + } + interface StartParameterType { + traceConfig: TraceConfig; + } + interface GetCategoriesReturnType { + /** + * A list of supported tracing categories. + */ + categories: string[]; + } + interface DataCollectedEventDataType { + value: Array<{}>; + } + } + namespace NodeWorker { + type WorkerID = string; + /** + * Unique identifier of attached debugging session. + */ + type SessionID = string; + interface WorkerInfo { + workerId: WorkerID; + type: string; + title: string; + url: string; + } + interface SendMessageToWorkerParameterType { + message: string; + /** + * Identifier of the session. + */ + sessionId: SessionID; + } + interface EnableParameterType { + /** + * Whether to new workers should be paused until the frontend sends `Runtime.runIfWaitingForDebugger` + * message to run them. + */ + waitForDebuggerOnStart: boolean; + } + interface DetachParameterType { + sessionId: SessionID; + } + interface AttachedToWorkerEventDataType { + /** + * Identifier assigned to the session used to send/receive messages. + */ + sessionId: SessionID; + workerInfo: WorkerInfo; + waitingForDebugger: boolean; + } + interface DetachedFromWorkerEventDataType { + /** + * Detached session identifier. + */ + sessionId: SessionID; + } + interface ReceivedMessageFromWorkerEventDataType { + /** + * Identifier of a session which sends a message. + */ + sessionId: SessionID; + message: string; + } + } + namespace NodeRuntime { + interface NotifyWhenWaitingForDisconnectParameterType { + enabled: boolean; + } + } + /** + * The `inspector.Session` is used for dispatching messages to the V8 inspector + * back-end and receiving message responses and notifications. + */ + class Session extends EventEmitter { + /** + * Create a new instance of the inspector.Session class. + * The inspector session needs to be connected through session.connect() before the messages can be dispatched to the inspector backend. + */ + constructor(); + /** + * Connects a session to the inspector back-end. + * @since v8.0.0 + */ + connect(): void; + /** + * Immediately close the session. All pending message callbacks will be called + * with an error. `session.connect()` will need to be called to be able to send + * messages again. Reconnected session will lose all inspector state, such as + * enabled agents or configured breakpoints. + * @since v8.0.0 + */ + disconnect(): void; + /** + * Posts a message to the inspector back-end. `callback` will be notified when + * a response is received. `callback` is a function that accepts two optional + * arguments: error and message-specific result. + * + * ```js + * session.post('Runtime.evaluate', { expression: '2 + 2' }, + * (error, { result }) => console.log(result)); + * // Output: { type: 'number', value: 4, description: '4' } + * ``` + * + * The latest version of the V8 inspector protocol is published on the [Chrome DevTools Protocol Viewer](https://chromedevtools.github.io/devtools-protocol/v8/). + * + * Node.js inspector supports all the Chrome DevTools Protocol domains declared + * by V8\. Chrome DevTools Protocol domain provides an interface for interacting + * with one of the runtime agents used to inspect the application state and listen + * to the run-time events. + * + * ## Example usage + * + * Apart from the debugger, various V8 Profilers are available through the DevTools + * protocol. + * @since v8.0.0 + */ + post(method: string, params?: {}, callback?: (err: Error | null, params?: {}) => void): void; + post(method: string, callback?: (err: Error | null, params?: {}) => void): void; + /** + * Returns supported domains. + */ + post( + method: 'Schema.getDomains', + callback?: (err: Error | null, params: Schema.GetDomainsReturnType) => void, + ): void; + /** + * Evaluates expression on global object. + */ + post( + method: 'Runtime.evaluate', + params?: Runtime.EvaluateParameterType, + callback?: (err: Error | null, params: Runtime.EvaluateReturnType) => void, + ): void; + post(method: 'Runtime.evaluate', callback?: (err: Error | null, params: Runtime.EvaluateReturnType) => void): void; + /** + * Add handler to promise with given promise object id. + */ + post( + method: 'Runtime.awaitPromise', + params?: Runtime.AwaitPromiseParameterType, + callback?: (err: Error | null, params: Runtime.AwaitPromiseReturnType) => void, + ): void; + post( + method: 'Runtime.awaitPromise', + callback?: (err: Error | null, params: Runtime.AwaitPromiseReturnType) => void, + ): void; + /** + * Calls function with given declaration on the given object. Object group of the result is inherited from the target object. + */ + post( + method: 'Runtime.callFunctionOn', + params?: Runtime.CallFunctionOnParameterType, + callback?: (err: Error | null, params: Runtime.CallFunctionOnReturnType) => void, + ): void; + post( + method: 'Runtime.callFunctionOn', + callback?: (err: Error | null, params: Runtime.CallFunctionOnReturnType) => void, + ): void; + /** + * Returns properties of a given object. Object group of the result is inherited from the target object. + */ + post( + method: 'Runtime.getProperties', + params?: Runtime.GetPropertiesParameterType, + callback?: (err: Error | null, params: Runtime.GetPropertiesReturnType) => void, + ): void; + post( + method: 'Runtime.getProperties', + callback?: (err: Error | null, params: Runtime.GetPropertiesReturnType) => void, + ): void; + /** + * Releases remote object with given id. + */ + post( + method: 'Runtime.releaseObject', + params?: Runtime.ReleaseObjectParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Runtime.releaseObject', callback?: (err: Error | null) => void): void; + /** + * Releases all remote objects that belong to a given group. + */ + post( + method: 'Runtime.releaseObjectGroup', + params?: Runtime.ReleaseObjectGroupParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Runtime.releaseObjectGroup', callback?: (err: Error | null) => void): void; + /** + * Tells inspected instance to run if it was waiting for debugger to attach. + */ + post(method: 'Runtime.runIfWaitingForDebugger', callback?: (err: Error | null) => void): void; + /** + * Enables reporting of execution contexts creation by means of executionContextCreated event. When the reporting gets enabled the event will be sent immediately for each existing execution context. + */ + post(method: 'Runtime.enable', callback?: (err: Error | null) => void): void; + /** + * Disables reporting of execution contexts creation. + */ + post(method: 'Runtime.disable', callback?: (err: Error | null) => void): void; + /** + * Discards collected exceptions and console API calls. + */ + post(method: 'Runtime.discardConsoleEntries', callback?: (err: Error | null) => void): void; + /** + * @experimental + */ + post( + method: 'Runtime.setCustomObjectFormatterEnabled', + params?: Runtime.SetCustomObjectFormatterEnabledParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Runtime.setCustomObjectFormatterEnabled', callback?: (err: Error | null) => void): void; + /** + * Compiles expression. + */ + post( + method: 'Runtime.compileScript', + params?: Runtime.CompileScriptParameterType, + callback?: (err: Error | null, params: Runtime.CompileScriptReturnType) => void, + ): void; + post( + method: 'Runtime.compileScript', + callback?: (err: Error | null, params: Runtime.CompileScriptReturnType) => void, + ): void; + /** + * Runs script with given id in a given context. + */ + post( + method: 'Runtime.runScript', + params?: Runtime.RunScriptParameterType, + callback?: (err: Error | null, params: Runtime.RunScriptReturnType) => void, + ): void; + post( + method: 'Runtime.runScript', + callback?: (err: Error | null, params: Runtime.RunScriptReturnType) => void, + ): void; + post( + method: 'Runtime.queryObjects', + params?: Runtime.QueryObjectsParameterType, + callback?: (err: Error | null, params: Runtime.QueryObjectsReturnType) => void, + ): void; + post( + method: 'Runtime.queryObjects', + callback?: (err: Error | null, params: Runtime.QueryObjectsReturnType) => void, + ): void; + /** + * Returns all let, const and class variables from global scope. + */ + post( + method: 'Runtime.globalLexicalScopeNames', + params?: Runtime.GlobalLexicalScopeNamesParameterType, + callback?: (err: Error | null, params: Runtime.GlobalLexicalScopeNamesReturnType) => void, + ): void; + post( + method: 'Runtime.globalLexicalScopeNames', + callback?: (err: Error | null, params: Runtime.GlobalLexicalScopeNamesReturnType) => void, + ): void; + /** + * Enables debugger for the given page. Clients should not assume that the debugging has been enabled until the result for this command is received. + */ + post(method: 'Debugger.enable', callback?: (err: Error | null, params: Debugger.EnableReturnType) => void): void; + /** + * Disables debugger for given page. + */ + post(method: 'Debugger.disable', callback?: (err: Error | null) => void): void; + /** + * Activates / deactivates all breakpoints on the page. + */ + post( + method: 'Debugger.setBreakpointsActive', + params?: Debugger.SetBreakpointsActiveParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Debugger.setBreakpointsActive', callback?: (err: Error | null) => void): void; + /** + * Makes page not interrupt on any pauses (breakpoint, exception, dom exception etc). + */ + post( + method: 'Debugger.setSkipAllPauses', + params?: Debugger.SetSkipAllPausesParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Debugger.setSkipAllPauses', callback?: (err: Error | null) => void): void; + /** + * Sets JavaScript breakpoint at given location specified either by URL or URL regex. Once this command is issued, all existing parsed scripts will have breakpoints resolved and returned in locations property. Further matching script parsing will result in subsequent breakpointResolved events issued. This logical breakpoint will survive page reloads. + */ + post( + method: 'Debugger.setBreakpointByUrl', + params?: Debugger.SetBreakpointByUrlParameterType, + callback?: (err: Error | null, params: Debugger.SetBreakpointByUrlReturnType) => void, + ): void; + post( + method: 'Debugger.setBreakpointByUrl', + callback?: (err: Error | null, params: Debugger.SetBreakpointByUrlReturnType) => void, + ): void; + /** + * Sets JavaScript breakpoint at a given location. + */ + post( + method: 'Debugger.setBreakpoint', + params?: Debugger.SetBreakpointParameterType, + callback?: (err: Error | null, params: Debugger.SetBreakpointReturnType) => void, + ): void; + post( + method: 'Debugger.setBreakpoint', + callback?: (err: Error | null, params: Debugger.SetBreakpointReturnType) => void, + ): void; + /** + * Removes JavaScript breakpoint. + */ + post( + method: 'Debugger.removeBreakpoint', + params?: Debugger.RemoveBreakpointParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Debugger.removeBreakpoint', callback?: (err: Error | null) => void): void; + /** + * Returns possible locations for breakpoint. scriptId in start and end range locations should be the same. + */ + post( + method: 'Debugger.getPossibleBreakpoints', + params?: Debugger.GetPossibleBreakpointsParameterType, + callback?: (err: Error | null, params: Debugger.GetPossibleBreakpointsReturnType) => void, + ): void; + post( + method: 'Debugger.getPossibleBreakpoints', + callback?: (err: Error | null, params: Debugger.GetPossibleBreakpointsReturnType) => void, + ): void; + /** + * Continues execution until specific location is reached. + */ + post( + method: 'Debugger.continueToLocation', + params?: Debugger.ContinueToLocationParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Debugger.continueToLocation', callback?: (err: Error | null) => void): void; + /** + * @experimental + */ + post( + method: 'Debugger.pauseOnAsyncCall', + params?: Debugger.PauseOnAsyncCallParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Debugger.pauseOnAsyncCall', callback?: (err: Error | null) => void): void; + /** + * Steps over the statement. + */ + post(method: 'Debugger.stepOver', callback?: (err: Error | null) => void): void; + /** + * Steps into the function call. + */ + post( + method: 'Debugger.stepInto', + params?: Debugger.StepIntoParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Debugger.stepInto', callback?: (err: Error | null) => void): void; + /** + * Steps out of the function call. + */ + post(method: 'Debugger.stepOut', callback?: (err: Error | null) => void): void; + /** + * Stops on the next JavaScript statement. + */ + post(method: 'Debugger.pause', callback?: (err: Error | null) => void): void; + /** + * This method is deprecated - use Debugger.stepInto with breakOnAsyncCall and Debugger.pauseOnAsyncTask instead. Steps into next scheduled async task if any is scheduled before next pause. Returns success when async task is actually scheduled, returns error if no task were scheduled or another scheduleStepIntoAsync was called. + * @experimental + */ + post(method: 'Debugger.scheduleStepIntoAsync', callback?: (err: Error | null) => void): void; + /** + * Resumes JavaScript execution. + */ + post(method: 'Debugger.resume', callback?: (err: Error | null) => void): void; + /** + * Returns stack trace with given stackTraceId. + * @experimental + */ + post( + method: 'Debugger.getStackTrace', + params?: Debugger.GetStackTraceParameterType, + callback?: (err: Error | null, params: Debugger.GetStackTraceReturnType) => void, + ): void; + post( + method: 'Debugger.getStackTrace', + callback?: (err: Error | null, params: Debugger.GetStackTraceReturnType) => void, + ): void; + /** + * Searches for given string in script content. + */ + post( + method: 'Debugger.searchInContent', + params?: Debugger.SearchInContentParameterType, + callback?: (err: Error | null, params: Debugger.SearchInContentReturnType) => void, + ): void; + post( + method: 'Debugger.searchInContent', + callback?: (err: Error | null, params: Debugger.SearchInContentReturnType) => void, + ): void; + /** + * Edits JavaScript source live. + */ + post( + method: 'Debugger.setScriptSource', + params?: Debugger.SetScriptSourceParameterType, + callback?: (err: Error | null, params: Debugger.SetScriptSourceReturnType) => void, + ): void; + post( + method: 'Debugger.setScriptSource', + callback?: (err: Error | null, params: Debugger.SetScriptSourceReturnType) => void, + ): void; + /** + * Restarts particular call frame from the beginning. + */ + post( + method: 'Debugger.restartFrame', + params?: Debugger.RestartFrameParameterType, + callback?: (err: Error | null, params: Debugger.RestartFrameReturnType) => void, + ): void; + post( + method: 'Debugger.restartFrame', + callback?: (err: Error | null, params: Debugger.RestartFrameReturnType) => void, + ): void; + /** + * Returns source for the script with given id. + */ + post( + method: 'Debugger.getScriptSource', + params?: Debugger.GetScriptSourceParameterType, + callback?: (err: Error | null, params: Debugger.GetScriptSourceReturnType) => void, + ): void; + post( + method: 'Debugger.getScriptSource', + callback?: (err: Error | null, params: Debugger.GetScriptSourceReturnType) => void, + ): void; + /** + * Defines pause on exceptions state. Can be set to stop on all exceptions, uncaught exceptions or no exceptions. Initial pause on exceptions state is none. + */ + post( + method: 'Debugger.setPauseOnExceptions', + params?: Debugger.SetPauseOnExceptionsParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Debugger.setPauseOnExceptions', callback?: (err: Error | null) => void): void; + /** + * Evaluates expression on a given call frame. + */ + post( + method: 'Debugger.evaluateOnCallFrame', + params?: Debugger.EvaluateOnCallFrameParameterType, + callback?: (err: Error | null, params: Debugger.EvaluateOnCallFrameReturnType) => void, + ): void; + post( + method: 'Debugger.evaluateOnCallFrame', + callback?: (err: Error | null, params: Debugger.EvaluateOnCallFrameReturnType) => void, + ): void; + /** + * Changes value of variable in a callframe. Object-based scopes are not supported and must be mutated manually. + */ + post( + method: 'Debugger.setVariableValue', + params?: Debugger.SetVariableValueParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Debugger.setVariableValue', callback?: (err: Error | null) => void): void; + /** + * Changes return value in top frame. Available only at return break position. + * @experimental + */ + post( + method: 'Debugger.setReturnValue', + params?: Debugger.SetReturnValueParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Debugger.setReturnValue', callback?: (err: Error | null) => void): void; + /** + * Enables or disables async call stacks tracking. + */ + post( + method: 'Debugger.setAsyncCallStackDepth', + params?: Debugger.SetAsyncCallStackDepthParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Debugger.setAsyncCallStackDepth', callback?: (err: Error | null) => void): void; + /** + * Replace previous blackbox patterns with passed ones. Forces backend to skip stepping/pausing in scripts with url matching one of the patterns. VM will try to leave blackboxed script by performing 'step in' several times, finally resorting to 'step out' if unsuccessful. + * @experimental + */ + post( + method: 'Debugger.setBlackboxPatterns', + params?: Debugger.SetBlackboxPatternsParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Debugger.setBlackboxPatterns', callback?: (err: Error | null) => void): void; + /** + * Makes backend skip steps in the script in blackboxed ranges. VM will try leave blacklisted scripts by performing 'step in' several times, finally resorting to 'step out' if unsuccessful. Positions array contains positions where blackbox state is changed. First interval isn't blackboxed. Array should be sorted. + * @experimental + */ + post( + method: 'Debugger.setBlackboxedRanges', + params?: Debugger.SetBlackboxedRangesParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Debugger.setBlackboxedRanges', callback?: (err: Error | null) => void): void; + /** + * Enables console domain, sends the messages collected so far to the client by means of the messageAdded notification. + */ + post(method: 'Console.enable', callback?: (err: Error | null) => void): void; + /** + * Disables console domain, prevents further console messages from being reported to the client. + */ + post(method: 'Console.disable', callback?: (err: Error | null) => void): void; + /** + * Does nothing. + */ + post(method: 'Console.clearMessages', callback?: (err: Error | null) => void): void; + post(method: 'Profiler.enable', callback?: (err: Error | null) => void): void; + post(method: 'Profiler.disable', callback?: (err: Error | null) => void): void; + /** + * Changes CPU profiler sampling interval. Must be called before CPU profiles recording started. + */ + post( + method: 'Profiler.setSamplingInterval', + params?: Profiler.SetSamplingIntervalParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Profiler.setSamplingInterval', callback?: (err: Error | null) => void): void; + post(method: 'Profiler.start', callback?: (err: Error | null) => void): void; + post(method: 'Profiler.stop', callback?: (err: Error | null, params: Profiler.StopReturnType) => void): void; + /** + * Enable precise code coverage. Coverage data for JavaScript executed before enabling precise code coverage may be incomplete. Enabling prevents running optimized code and resets execution counters. + */ + post( + method: 'Profiler.startPreciseCoverage', + params?: Profiler.StartPreciseCoverageParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Profiler.startPreciseCoverage', callback?: (err: Error | null) => void): void; + /** + * Disable precise code coverage. Disabling releases unnecessary execution count records and allows executing optimized code. + */ + post(method: 'Profiler.stopPreciseCoverage', callback?: (err: Error | null) => void): void; + /** + * Collect coverage data for the current isolate, and resets execution counters. Precise code coverage needs to have started. + */ + post( + method: 'Profiler.takePreciseCoverage', + callback?: (err: Error | null, params: Profiler.TakePreciseCoverageReturnType) => void, + ): void; + /** + * Collect coverage data for the current isolate. The coverage data may be incomplete due to garbage collection. + */ + post( + method: 'Profiler.getBestEffortCoverage', + callback?: (err: Error | null, params: Profiler.GetBestEffortCoverageReturnType) => void, + ): void; + /** + * Enable type profile. + * @experimental + */ + post(method: 'Profiler.startTypeProfile', callback?: (err: Error | null) => void): void; + /** + * Disable type profile. Disabling releases type profile data collected so far. + * @experimental + */ + post(method: 'Profiler.stopTypeProfile', callback?: (err: Error | null) => void): void; + /** + * Collect type profile. + * @experimental + */ + post( + method: 'Profiler.takeTypeProfile', + callback?: (err: Error | null, params: Profiler.TakeTypeProfileReturnType) => void, + ): void; + post(method: 'HeapProfiler.enable', callback?: (err: Error | null) => void): void; + post(method: 'HeapProfiler.disable', callback?: (err: Error | null) => void): void; + post( + method: 'HeapProfiler.startTrackingHeapObjects', + params?: HeapProfiler.StartTrackingHeapObjectsParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'HeapProfiler.startTrackingHeapObjects', callback?: (err: Error | null) => void): void; + post( + method: 'HeapProfiler.stopTrackingHeapObjects', + params?: HeapProfiler.StopTrackingHeapObjectsParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'HeapProfiler.stopTrackingHeapObjects', callback?: (err: Error | null) => void): void; + post( + method: 'HeapProfiler.takeHeapSnapshot', + params?: HeapProfiler.TakeHeapSnapshotParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'HeapProfiler.takeHeapSnapshot', callback?: (err: Error | null) => void): void; + post(method: 'HeapProfiler.collectGarbage', callback?: (err: Error | null) => void): void; + post( + method: 'HeapProfiler.getObjectByHeapObjectId', + params?: HeapProfiler.GetObjectByHeapObjectIdParameterType, + callback?: (err: Error | null, params: HeapProfiler.GetObjectByHeapObjectIdReturnType) => void, + ): void; + post( + method: 'HeapProfiler.getObjectByHeapObjectId', + callback?: (err: Error | null, params: HeapProfiler.GetObjectByHeapObjectIdReturnType) => void, + ): void; + /** + * Enables console to refer to the node with given id via $x (see Command Line API for more details $x functions). + */ + post( + method: 'HeapProfiler.addInspectedHeapObject', + params?: HeapProfiler.AddInspectedHeapObjectParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'HeapProfiler.addInspectedHeapObject', callback?: (err: Error | null) => void): void; + post( + method: 'HeapProfiler.getHeapObjectId', + params?: HeapProfiler.GetHeapObjectIdParameterType, + callback?: (err: Error | null, params: HeapProfiler.GetHeapObjectIdReturnType) => void, + ): void; + post( + method: 'HeapProfiler.getHeapObjectId', + callback?: (err: Error | null, params: HeapProfiler.GetHeapObjectIdReturnType) => void, + ): void; + post( + method: 'HeapProfiler.startSampling', + params?: HeapProfiler.StartSamplingParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'HeapProfiler.startSampling', callback?: (err: Error | null) => void): void; + post( + method: 'HeapProfiler.stopSampling', + callback?: (err: Error | null, params: HeapProfiler.StopSamplingReturnType) => void, + ): void; + post( + method: 'HeapProfiler.getSamplingProfile', + callback?: (err: Error | null, params: HeapProfiler.GetSamplingProfileReturnType) => void, + ): void; + /** + * Gets supported tracing categories. + */ + post( + method: 'NodeTracing.getCategories', + callback?: (err: Error | null, params: NodeTracing.GetCategoriesReturnType) => void, + ): void; + /** + * Start trace events collection. + */ + post( + method: 'NodeTracing.start', + params?: NodeTracing.StartParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'NodeTracing.start', callback?: (err: Error | null) => void): void; + /** + * Stop trace events collection. Remaining collected events will be sent as a sequence of + * dataCollected events followed by tracingComplete event. + */ + post(method: 'NodeTracing.stop', callback?: (err: Error | null) => void): void; + /** + * Sends protocol message over session with given id. + */ + post( + method: 'NodeWorker.sendMessageToWorker', + params?: NodeWorker.SendMessageToWorkerParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'NodeWorker.sendMessageToWorker', callback?: (err: Error | null) => void): void; + /** + * Instructs the inspector to attach to running workers. Will also attach to new workers + * as they start + */ + post( + method: 'NodeWorker.enable', + params?: NodeWorker.EnableParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'NodeWorker.enable', callback?: (err: Error | null) => void): void; + /** + * Detaches from all running workers and disables attaching to new workers as they are started. + */ + post(method: 'NodeWorker.disable', callback?: (err: Error | null) => void): void; + /** + * Detached from the worker with given sessionId. + */ + post( + method: 'NodeWorker.detach', + params?: NodeWorker.DetachParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'NodeWorker.detach', callback?: (err: Error | null) => void): void; + /** + * Enable the `NodeRuntime.waitingForDisconnect`. + */ + post( + method: 'NodeRuntime.notifyWhenWaitingForDisconnect', + params?: NodeRuntime.NotifyWhenWaitingForDisconnectParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'NodeRuntime.notifyWhenWaitingForDisconnect', callback?: (err: Error | null) => void): void; + // Events + addListener(event: string, listener: (...args: any[]) => void): this; + /** + * Emitted when any notification from the V8 Inspector is received. + */ + addListener(event: 'inspectorNotification', listener: (message: InspectorNotification<{}>) => void): this; + /** + * Issued when new execution context is created. + */ + addListener( + event: 'Runtime.executionContextCreated', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when execution context is destroyed. + */ + addListener( + event: 'Runtime.executionContextDestroyed', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when all executionContexts were cleared in browser + */ + addListener(event: 'Runtime.executionContextsCleared', listener: () => void): this; + /** + * Issued when exception was thrown and unhandled. + */ + addListener( + event: 'Runtime.exceptionThrown', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when unhandled exception was revoked. + */ + addListener( + event: 'Runtime.exceptionRevoked', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when console API was called. + */ + addListener( + event: 'Runtime.consoleAPICalled', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when object should be inspected (for example, as a result of inspect() command line API call). + */ + addListener( + event: 'Runtime.inspectRequested', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when virtual machine parses script. This event is also fired for all known and uncollected scripts upon enabling debugger. + */ + addListener( + event: 'Debugger.scriptParsed', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when virtual machine fails to parse the script. + */ + addListener( + event: 'Debugger.scriptFailedToParse', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when breakpoint is resolved to an actual script and location. + */ + addListener( + event: 'Debugger.breakpointResolved', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria. + */ + addListener( + event: 'Debugger.paused', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when the virtual machine resumed execution. + */ + addListener(event: 'Debugger.resumed', listener: () => void): this; + /** + * Issued when new console message is added. + */ + addListener( + event: 'Console.messageAdded', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Sent when new profile recording is started using console.profile() call. + */ + addListener( + event: 'Profiler.consoleProfileStarted', + listener: (message: InspectorNotification) => void, + ): this; + addListener( + event: 'Profiler.consoleProfileFinished', + listener: (message: InspectorNotification) => void, + ): this; + addListener( + event: 'HeapProfiler.addHeapSnapshotChunk', + listener: (message: InspectorNotification) => void, + ): this; + addListener(event: 'HeapProfiler.resetProfiles', listener: () => void): this; + addListener( + event: 'HeapProfiler.reportHeapSnapshotProgress', + listener: (message: InspectorNotification) => void, + ): this; + /** + * If heap objects tracking has been started then backend regularly sends a current value for last seen object id and corresponding timestamp. If the were changes in the heap since last event then one or more heapStatsUpdate events will be sent before a new lastSeenObjectId event. + */ + addListener( + event: 'HeapProfiler.lastSeenObjectId', + listener: (message: InspectorNotification) => void, + ): this; + /** + * If heap objects tracking has been started then backend may send update for one or more fragments + */ + addListener( + event: 'HeapProfiler.heapStatsUpdate', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Contains an bucket of collected trace events. + */ + addListener( + event: 'NodeTracing.dataCollected', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Signals that tracing is stopped and there is no trace buffers pending flush, all data were + * delivered via dataCollected events. + */ + addListener(event: 'NodeTracing.tracingComplete', listener: () => void): this; + /** + * Issued when attached to a worker. + */ + addListener( + event: 'NodeWorker.attachedToWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when detached from the worker. + */ + addListener( + event: 'NodeWorker.detachedFromWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Notifies about a new protocol message received from the session + * (session ID is provided in attachedToWorker notification). + */ + addListener( + event: 'NodeWorker.receivedMessageFromWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * This event is fired instead of `Runtime.executionContextDestroyed` when + * enabled. + * It is fired when the Node process finished all code execution and is + * waiting for all frontends to disconnect. + */ + addListener(event: 'NodeRuntime.waitingForDisconnect', listener: () => void): this; + emit(event: string | symbol, ...args: any[]): boolean; + emit(event: 'inspectorNotification', message: InspectorNotification<{}>): boolean; + emit( + event: 'Runtime.executionContextCreated', + message: InspectorNotification, + ): boolean; + emit( + event: 'Runtime.executionContextDestroyed', + message: InspectorNotification, + ): boolean; + emit(event: 'Runtime.executionContextsCleared'): boolean; + emit( + event: 'Runtime.exceptionThrown', + message: InspectorNotification, + ): boolean; + emit( + event: 'Runtime.exceptionRevoked', + message: InspectorNotification, + ): boolean; + emit( + event: 'Runtime.consoleAPICalled', + message: InspectorNotification, + ): boolean; + emit( + event: 'Runtime.inspectRequested', + message: InspectorNotification, + ): boolean; + emit(event: 'Debugger.scriptParsed', message: InspectorNotification): boolean; + emit( + event: 'Debugger.scriptFailedToParse', + message: InspectorNotification, + ): boolean; + emit( + event: 'Debugger.breakpointResolved', + message: InspectorNotification, + ): boolean; + emit(event: 'Debugger.paused', message: InspectorNotification): boolean; + emit(event: 'Debugger.resumed'): boolean; + emit(event: 'Console.messageAdded', message: InspectorNotification): boolean; + emit( + event: 'Profiler.consoleProfileStarted', + message: InspectorNotification, + ): boolean; + emit( + event: 'Profiler.consoleProfileFinished', + message: InspectorNotification, + ): boolean; + emit( + event: 'HeapProfiler.addHeapSnapshotChunk', + message: InspectorNotification, + ): boolean; + emit(event: 'HeapProfiler.resetProfiles'): boolean; + emit( + event: 'HeapProfiler.reportHeapSnapshotProgress', + message: InspectorNotification, + ): boolean; + emit( + event: 'HeapProfiler.lastSeenObjectId', + message: InspectorNotification, + ): boolean; + emit( + event: 'HeapProfiler.heapStatsUpdate', + message: InspectorNotification, + ): boolean; + emit( + event: 'NodeTracing.dataCollected', + message: InspectorNotification, + ): boolean; + emit(event: 'NodeTracing.tracingComplete'): boolean; + emit( + event: 'NodeWorker.attachedToWorker', + message: InspectorNotification, + ): boolean; + emit( + event: 'NodeWorker.detachedFromWorker', + message: InspectorNotification, + ): boolean; + emit( + event: 'NodeWorker.receivedMessageFromWorker', + message: InspectorNotification, + ): boolean; + emit(event: 'NodeRuntime.waitingForDisconnect'): boolean; + on(event: string, listener: (...args: any[]) => void): this; + /** + * Emitted when any notification from the V8 Inspector is received. + */ + on(event: 'inspectorNotification', listener: (message: InspectorNotification<{}>) => void): this; + /** + * Issued when new execution context is created. + */ + on( + event: 'Runtime.executionContextCreated', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when execution context is destroyed. + */ + on( + event: 'Runtime.executionContextDestroyed', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when all executionContexts were cleared in browser + */ + on(event: 'Runtime.executionContextsCleared', listener: () => void): this; + /** + * Issued when exception was thrown and unhandled. + */ + on( + event: 'Runtime.exceptionThrown', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when unhandled exception was revoked. + */ + on( + event: 'Runtime.exceptionRevoked', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when console API was called. + */ + on( + event: 'Runtime.consoleAPICalled', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when object should be inspected (for example, as a result of inspect() command line API call). + */ + on( + event: 'Runtime.inspectRequested', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when virtual machine parses script. This event is also fired for all known and uncollected scripts upon enabling debugger. + */ + on( + event: 'Debugger.scriptParsed', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when virtual machine fails to parse the script. + */ + on( + event: 'Debugger.scriptFailedToParse', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when breakpoint is resolved to an actual script and location. + */ + on( + event: 'Debugger.breakpointResolved', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria. + */ + on( + event: 'Debugger.paused', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when the virtual machine resumed execution. + */ + on(event: 'Debugger.resumed', listener: () => void): this; + /** + * Issued when new console message is added. + */ + on( + event: 'Console.messageAdded', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Sent when new profile recording is started using console.profile() call. + */ + on( + event: 'Profiler.consoleProfileStarted', + listener: (message: InspectorNotification) => void, + ): this; + on( + event: 'Profiler.consoleProfileFinished', + listener: (message: InspectorNotification) => void, + ): this; + on( + event: 'HeapProfiler.addHeapSnapshotChunk', + listener: (message: InspectorNotification) => void, + ): this; + on(event: 'HeapProfiler.resetProfiles', listener: () => void): this; + on( + event: 'HeapProfiler.reportHeapSnapshotProgress', + listener: (message: InspectorNotification) => void, + ): this; + /** + * If heap objects tracking has been started then backend regularly sends a current value for last seen object id and corresponding timestamp. If the were changes in the heap since last event then one or more heapStatsUpdate events will be sent before a new lastSeenObjectId event. + */ + on( + event: 'HeapProfiler.lastSeenObjectId', + listener: (message: InspectorNotification) => void, + ): this; + /** + * If heap objects tracking has been started then backend may send update for one or more fragments + */ + on( + event: 'HeapProfiler.heapStatsUpdate', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Contains an bucket of collected trace events. + */ + on( + event: 'NodeTracing.dataCollected', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Signals that tracing is stopped and there is no trace buffers pending flush, all data were + * delivered via dataCollected events. + */ + on(event: 'NodeTracing.tracingComplete', listener: () => void): this; + /** + * Issued when attached to a worker. + */ + on( + event: 'NodeWorker.attachedToWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when detached from the worker. + */ + on( + event: 'NodeWorker.detachedFromWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Notifies about a new protocol message received from the session + * (session ID is provided in attachedToWorker notification). + */ + on( + event: 'NodeWorker.receivedMessageFromWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * This event is fired instead of `Runtime.executionContextDestroyed` when + * enabled. + * It is fired when the Node process finished all code execution and is + * waiting for all frontends to disconnect. + */ + on(event: 'NodeRuntime.waitingForDisconnect', listener: () => void): this; + once(event: string, listener: (...args: any[]) => void): this; + /** + * Emitted when any notification from the V8 Inspector is received. + */ + once(event: 'inspectorNotification', listener: (message: InspectorNotification<{}>) => void): this; + /** + * Issued when new execution context is created. + */ + once( + event: 'Runtime.executionContextCreated', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when execution context is destroyed. + */ + once( + event: 'Runtime.executionContextDestroyed', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when all executionContexts were cleared in browser + */ + once(event: 'Runtime.executionContextsCleared', listener: () => void): this; + /** + * Issued when exception was thrown and unhandled. + */ + once( + event: 'Runtime.exceptionThrown', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when unhandled exception was revoked. + */ + once( + event: 'Runtime.exceptionRevoked', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when console API was called. + */ + once( + event: 'Runtime.consoleAPICalled', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when object should be inspected (for example, as a result of inspect() command line API call). + */ + once( + event: 'Runtime.inspectRequested', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when virtual machine parses script. This event is also fired for all known and uncollected scripts upon enabling debugger. + */ + once( + event: 'Debugger.scriptParsed', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when virtual machine fails to parse the script. + */ + once( + event: 'Debugger.scriptFailedToParse', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when breakpoint is resolved to an actual script and location. + */ + once( + event: 'Debugger.breakpointResolved', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria. + */ + once( + event: 'Debugger.paused', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when the virtual machine resumed execution. + */ + once(event: 'Debugger.resumed', listener: () => void): this; + /** + * Issued when new console message is added. + */ + once( + event: 'Console.messageAdded', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Sent when new profile recording is started using console.profile() call. + */ + once( + event: 'Profiler.consoleProfileStarted', + listener: (message: InspectorNotification) => void, + ): this; + once( + event: 'Profiler.consoleProfileFinished', + listener: (message: InspectorNotification) => void, + ): this; + once( + event: 'HeapProfiler.addHeapSnapshotChunk', + listener: (message: InspectorNotification) => void, + ): this; + once(event: 'HeapProfiler.resetProfiles', listener: () => void): this; + once( + event: 'HeapProfiler.reportHeapSnapshotProgress', + listener: (message: InspectorNotification) => void, + ): this; + /** + * If heap objects tracking has been started then backend regularly sends a current value for last seen object id and corresponding timestamp. If the were changes in the heap since last event then one or more heapStatsUpdate events will be sent before a new lastSeenObjectId event. + */ + once( + event: 'HeapProfiler.lastSeenObjectId', + listener: (message: InspectorNotification) => void, + ): this; + /** + * If heap objects tracking has been started then backend may send update for one or more fragments + */ + once( + event: 'HeapProfiler.heapStatsUpdate', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Contains an bucket of collected trace events. + */ + once( + event: 'NodeTracing.dataCollected', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Signals that tracing is stopped and there is no trace buffers pending flush, all data were + * delivered via dataCollected events. + */ + once(event: 'NodeTracing.tracingComplete', listener: () => void): this; + /** + * Issued when attached to a worker. + */ + once( + event: 'NodeWorker.attachedToWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when detached from the worker. + */ + once( + event: 'NodeWorker.detachedFromWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Notifies about a new protocol message received from the session + * (session ID is provided in attachedToWorker notification). + */ + once( + event: 'NodeWorker.receivedMessageFromWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * This event is fired instead of `Runtime.executionContextDestroyed` when + * enabled. + * It is fired when the Node process finished all code execution and is + * waiting for all frontends to disconnect. + */ + once(event: 'NodeRuntime.waitingForDisconnect', listener: () => void): this; + prependListener(event: string, listener: (...args: any[]) => void): this; + /** + * Emitted when any notification from the V8 Inspector is received. + */ + prependListener(event: 'inspectorNotification', listener: (message: InspectorNotification<{}>) => void): this; + /** + * Issued when new execution context is created. + */ + prependListener( + event: 'Runtime.executionContextCreated', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when execution context is destroyed. + */ + prependListener( + event: 'Runtime.executionContextDestroyed', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when all executionContexts were cleared in browser + */ + prependListener(event: 'Runtime.executionContextsCleared', listener: () => void): this; + /** + * Issued when exception was thrown and unhandled. + */ + prependListener( + event: 'Runtime.exceptionThrown', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when unhandled exception was revoked. + */ + prependListener( + event: 'Runtime.exceptionRevoked', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when console API was called. + */ + prependListener( + event: 'Runtime.consoleAPICalled', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when object should be inspected (for example, as a result of inspect() command line API call). + */ + prependListener( + event: 'Runtime.inspectRequested', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when virtual machine parses script. This event is also fired for all known and uncollected scripts upon enabling debugger. + */ + prependListener( + event: 'Debugger.scriptParsed', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when virtual machine fails to parse the script. + */ + prependListener( + event: 'Debugger.scriptFailedToParse', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when breakpoint is resolved to an actual script and location. + */ + prependListener( + event: 'Debugger.breakpointResolved', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria. + */ + prependListener( + event: 'Debugger.paused', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when the virtual machine resumed execution. + */ + prependListener(event: 'Debugger.resumed', listener: () => void): this; + /** + * Issued when new console message is added. + */ + prependListener( + event: 'Console.messageAdded', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Sent when new profile recording is started using console.profile() call. + */ + prependListener( + event: 'Profiler.consoleProfileStarted', + listener: (message: InspectorNotification) => void, + ): this; + prependListener( + event: 'Profiler.consoleProfileFinished', + listener: (message: InspectorNotification) => void, + ): this; + prependListener( + event: 'HeapProfiler.addHeapSnapshotChunk', + listener: (message: InspectorNotification) => void, + ): this; + prependListener(event: 'HeapProfiler.resetProfiles', listener: () => void): this; + prependListener( + event: 'HeapProfiler.reportHeapSnapshotProgress', + listener: (message: InspectorNotification) => void, + ): this; + /** + * If heap objects tracking has been started then backend regularly sends a current value for last seen object id and corresponding timestamp. If the were changes in the heap since last event then one or more heapStatsUpdate events will be sent before a new lastSeenObjectId event. + */ + prependListener( + event: 'HeapProfiler.lastSeenObjectId', + listener: (message: InspectorNotification) => void, + ): this; + /** + * If heap objects tracking has been started then backend may send update for one or more fragments + */ + prependListener( + event: 'HeapProfiler.heapStatsUpdate', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Contains an bucket of collected trace events. + */ + prependListener( + event: 'NodeTracing.dataCollected', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Signals that tracing is stopped and there is no trace buffers pending flush, all data were + * delivered via dataCollected events. + */ + prependListener(event: 'NodeTracing.tracingComplete', listener: () => void): this; + /** + * Issued when attached to a worker. + */ + prependListener( + event: 'NodeWorker.attachedToWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when detached from the worker. + */ + prependListener( + event: 'NodeWorker.detachedFromWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Notifies about a new protocol message received from the session + * (session ID is provided in attachedToWorker notification). + */ + prependListener( + event: 'NodeWorker.receivedMessageFromWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * This event is fired instead of `Runtime.executionContextDestroyed` when + * enabled. + * It is fired when the Node process finished all code execution and is + * waiting for all frontends to disconnect. + */ + prependListener(event: 'NodeRuntime.waitingForDisconnect', listener: () => void): this; + prependOnceListener(event: string, listener: (...args: any[]) => void): this; + /** + * Emitted when any notification from the V8 Inspector is received. + */ + prependOnceListener(event: 'inspectorNotification', listener: (message: InspectorNotification<{}>) => void): this; + /** + * Issued when new execution context is created. + */ + prependOnceListener( + event: 'Runtime.executionContextCreated', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when execution context is destroyed. + */ + prependOnceListener( + event: 'Runtime.executionContextDestroyed', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when all executionContexts were cleared in browser + */ + prependOnceListener(event: 'Runtime.executionContextsCleared', listener: () => void): this; + /** + * Issued when exception was thrown and unhandled. + */ + prependOnceListener( + event: 'Runtime.exceptionThrown', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when unhandled exception was revoked. + */ + prependOnceListener( + event: 'Runtime.exceptionRevoked', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when console API was called. + */ + prependOnceListener( + event: 'Runtime.consoleAPICalled', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when object should be inspected (for example, as a result of inspect() command line API call). + */ + prependOnceListener( + event: 'Runtime.inspectRequested', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when virtual machine parses script. This event is also fired for all known and uncollected scripts upon enabling debugger. + */ + prependOnceListener( + event: 'Debugger.scriptParsed', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when virtual machine fails to parse the script. + */ + prependOnceListener( + event: 'Debugger.scriptFailedToParse', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when breakpoint is resolved to an actual script and location. + */ + prependOnceListener( + event: 'Debugger.breakpointResolved', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria. + */ + prependOnceListener( + event: 'Debugger.paused', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when the virtual machine resumed execution. + */ + prependOnceListener(event: 'Debugger.resumed', listener: () => void): this; + /** + * Issued when new console message is added. + */ + prependOnceListener( + event: 'Console.messageAdded', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Sent when new profile recording is started using console.profile() call. + */ + prependOnceListener( + event: 'Profiler.consoleProfileStarted', + listener: (message: InspectorNotification) => void, + ): this; + prependOnceListener( + event: 'Profiler.consoleProfileFinished', + listener: (message: InspectorNotification) => void, + ): this; + prependOnceListener( + event: 'HeapProfiler.addHeapSnapshotChunk', + listener: (message: InspectorNotification) => void, + ): this; + prependOnceListener(event: 'HeapProfiler.resetProfiles', listener: () => void): this; + prependOnceListener( + event: 'HeapProfiler.reportHeapSnapshotProgress', + listener: (message: InspectorNotification) => void, + ): this; + /** + * If heap objects tracking has been started then backend regularly sends a current value for last seen object id and corresponding timestamp. If the were changes in the heap since last event then one or more heapStatsUpdate events will be sent before a new lastSeenObjectId event. + */ + prependOnceListener( + event: 'HeapProfiler.lastSeenObjectId', + listener: (message: InspectorNotification) => void, + ): this; + /** + * If heap objects tracking has been started then backend may send update for one or more fragments + */ + prependOnceListener( + event: 'HeapProfiler.heapStatsUpdate', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Contains an bucket of collected trace events. + */ + prependOnceListener( + event: 'NodeTracing.dataCollected', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Signals that tracing is stopped and there is no trace buffers pending flush, all data were + * delivered via dataCollected events. + */ + prependOnceListener(event: 'NodeTracing.tracingComplete', listener: () => void): this; + /** + * Issued when attached to a worker. + */ + prependOnceListener( + event: 'NodeWorker.attachedToWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when detached from the worker. + */ + prependOnceListener( + event: 'NodeWorker.detachedFromWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Notifies about a new protocol message received from the session + * (session ID is provided in attachedToWorker notification). + */ + prependOnceListener( + event: 'NodeWorker.receivedMessageFromWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * This event is fired instead of `Runtime.executionContextDestroyed` when + * enabled. + * It is fired when the Node process finished all code execution and is + * waiting for all frontends to disconnect. + */ + prependOnceListener(event: 'NodeRuntime.waitingForDisconnect', listener: () => void): this; + } + /** + * Activate inspector on host and port. Equivalent to`node --inspect=[[host:]port]`, but can be done programmatically after node has + * started. + * + * If wait is `true`, will block until a client has connected to the inspect port + * and flow control has been passed to the debugger client. + * + * See the `security warning` regarding the `host`parameter usage. + * @param [port='what was specified on the CLI'] Port to listen on for inspector connections. Optional. + * @param [host='what was specified on the CLI'] Host to listen on for inspector connections. Optional. + * @param [wait=false] Block until a client has connected. Optional. + */ + function open(port?: number, host?: string, wait?: boolean): void; + /** + * Deactivate the inspector. Blocks until there are no active connections. + */ + function close(): void; + /** + * Return the URL of the active inspector, or `undefined` if there is none. + * + * ```console + * $ node --inspect -p 'inspector.url()' + * Debugger listening on ws://127.0.0.1:9229/166e272e-7a30-4d09-97ce-f1c012b43c34 + * For help, see: https://nodejs.org/en/docs/inspector + * ws://127.0.0.1:9229/166e272e-7a30-4d09-97ce-f1c012b43c34 + * + * $ node --inspect=localhost:3000 -p 'inspector.url()' + * Debugger listening on ws://localhost:3000/51cf8d0e-3c36-4c59-8efd-54519839e56a + * For help, see: https://nodejs.org/en/docs/inspector + * ws://localhost:3000/51cf8d0e-3c36-4c59-8efd-54519839e56a + * + * $ node -p 'inspector.url()' + * undefined + * ``` + */ + function url(): string | undefined; + /** + * Blocks until a client (existing or connected later) has sent`Runtime.runIfWaitingForDebugger` command. + * + * An exception will be thrown if there is no active inspector. + * @since v12.7.0 + */ + function waitForDebugger(): void; +} +/** + * The inspector module provides an API for interacting with the V8 inspector. + */ +declare module 'node:inspector' { + import inspector = require('inspector'); + export = inspector; +} diff --git a/packages/node/src/integrations/linkederrors.ts b/packages/node/src/integrations/linkederrors.ts index 1b0a6bf12565..e2894a128be5 100644 --- a/packages/node/src/integrations/linkederrors.ts +++ b/packages/node/src/integrations/linkederrors.ts @@ -1,8 +1,8 @@ import { addGlobalEventProcessor, getCurrentHub } from '@sentry/core'; -import { Event, EventHint, Exception, ExtendedError, Integration, StackParser } from '@sentry/types'; +import type { Event, EventHint, Exception, ExtendedError, Integration, StackParser } from '@sentry/types'; import { isInstanceOf, resolvedSyncPromise, SyncPromise } from '@sentry/utils'; -import { NodeClient } from '../client'; +import type { NodeClient } from '../client'; import { exceptionFromError } from '../eventbuilder'; import { ContextLines } from './contextlines'; diff --git a/packages/node/src/integrations/localvariables.ts b/packages/node/src/integrations/localvariables.ts new file mode 100644 index 000000000000..785c0fc74713 --- /dev/null +++ b/packages/node/src/integrations/localvariables.ts @@ -0,0 +1,279 @@ +import type { + ClientOptions, + Event, + EventProcessor, + Exception, + Hub, + Integration, + StackFrame, + StackParser, +} from '@sentry/types'; +import type { Debugger, InspectorNotification, Runtime } from 'inspector'; +import { Session } from 'inspector'; +import { LRUMap } from 'lru_map'; + +export interface DebugSession { + /** Configures and connects to the debug session */ + configureAndConnect(onPause: (message: InspectorNotification) => void): void; + /** Gets local variables for an objectId */ + getLocalVariables(objectId: string): Promise>; +} + +/** + * Promise API is available as `Experimental` and in Node 19 only. + * + * Callback-based API is `Stable` since v14 and `Experimental` since v8. + * Because of that, we are creating our own `AsyncSession` class. + * + * https://nodejs.org/docs/latest-v19.x/api/inspector.html#promises-api + * https://nodejs.org/docs/latest-v14.x/api/inspector.html + */ +class AsyncSession extends Session implements DebugSession { + /** @inheritdoc */ + public configureAndConnect(onPause: (message: InspectorNotification) => void): void { + this.connect(); + this.on('Debugger.paused', onPause); + this.post('Debugger.enable'); + // We only want to pause on uncaught exceptions + this.post('Debugger.setPauseOnExceptions', { state: 'uncaught' }); + } + + /** @inheritdoc */ + public async getLocalVariables(objectId: string): Promise> { + const props = await this._getProperties(objectId); + const unrolled: Record = {}; + + for (const prop of props) { + if (prop?.value?.objectId && prop?.value.className === 'Array') { + unrolled[prop.name] = await this._unrollArray(prop.value.objectId); + } else if (prop?.value?.objectId && prop?.value?.className === 'Object') { + unrolled[prop.name] = await this._unrollObject(prop.value.objectId); + } else if (prop?.value?.value || prop?.value?.description) { + unrolled[prop.name] = prop.value.value || `<${prop.value.description}>`; + } + } + + return unrolled; + } + + /** + * Gets all the PropertyDescriptors of an object + */ + private _getProperties(objectId: string): Promise { + return new Promise((resolve, reject) => { + this.post( + 'Runtime.getProperties', + { + objectId, + ownProperties: true, + }, + (err, params) => { + if (err) { + reject(err); + } else { + resolve(params.result); + } + }, + ); + }); + } + + /** + * Unrolls an array property + */ + private async _unrollArray(objectId: string): Promise { + const props = await this._getProperties(objectId); + return props + .filter(v => v.name !== 'length' && !isNaN(parseInt(v.name, 10))) + .sort((a, b) => parseInt(a.name, 10) - parseInt(b.name, 10)) + .map(v => v?.value?.value); + } + + /** + * Unrolls an object property + */ + private async _unrollObject(objectId: string): Promise> { + const props = await this._getProperties(objectId); + return props + .map<[string, unknown]>(v => [v.name, v?.value?.value]) + .reduce((obj, [key, val]) => { + obj[key] = val; + return obj; + }, {} as Record); + } +} + +// Add types for the exception event data +type PausedExceptionEvent = Debugger.PausedEventDataType & { + data: { + // This contains error.stack + description: string; + }; +}; + +/** Could this be an anonymous function? */ +function isAnonymous(name: string | undefined): boolean { + return name !== undefined && ['', '?', ''].includes(name); +} + +/** Do the function names appear to match? */ +function functionNamesMatch(a: string | undefined, b: string | undefined): boolean { + return a === b || (isAnonymous(a) && isAnonymous(b)); +} + +/** Creates a unique hash from stack frames */ +function hashFrames(frames: StackFrame[] | undefined): string | undefined { + if (frames === undefined) { + return; + } + + // Only hash the 10 most recent frames (ie. the last 10) + return frames.slice(-10).reduce((acc, frame) => `${acc},${frame.function},${frame.lineno},${frame.colno}`, ''); +} + +/** + * We use the stack parser to create a unique hash from the exception stack trace + * This is used to lookup vars when the exception passes through the event processor + */ +function hashFromStack(stackParser: StackParser, stack: string | undefined): string | undefined { + if (stack === undefined) { + return undefined; + } + + return hashFrames(stackParser(stack, 1)); +} + +export interface FrameVariables { + function: string; + vars?: Record; +} + +/** There are no options yet. This allows them to be added later without breaking changes */ +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface Options {} + +/** + * Adds local variables to exception frames + */ +export class LocalVariables implements Integration { + public static id: string = 'LocalVariables'; + + public readonly name: string = LocalVariables.id; + + private readonly _cachedFrames: LRUMap> = new LRUMap(20); + + public constructor(_options: Options = {}, private readonly _session: DebugSession = new AsyncSession()) {} + + /** + * @inheritDoc + */ + public setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void { + this._setup(addGlobalEventProcessor, getCurrentHub().getClient()?.getOptions()); + } + + /** Setup in a way that's easier to call from tests */ + private _setup( + addGlobalEventProcessor: (callback: EventProcessor) => void, + clientOptions: ClientOptions | undefined, + ): void { + if (clientOptions?._experiments?.includeStackLocals) { + this._session.configureAndConnect(ev => + this._handlePaused(clientOptions.stackParser, ev as InspectorNotification), + ); + + addGlobalEventProcessor(async event => this._addLocalVariables(event)); + } + } + + /** + * Handle the pause event + */ + private async _handlePaused( + stackParser: StackParser, + { params: { reason, data, callFrames } }: InspectorNotification, + ): Promise { + if (reason !== 'exception' && reason !== 'promiseRejection') { + return; + } + + // data.description contains the original error.stack + const exceptionHash = hashFromStack(stackParser, data?.description); + + if (exceptionHash == undefined) { + return; + } + + const framePromises = callFrames.map(async ({ scopeChain, functionName, this: obj }) => { + const localScope = scopeChain.find(scope => scope.type === 'local'); + + const fn = obj.className === 'global' ? functionName : `${obj.className}.${functionName}`; + + if (localScope?.object.objectId === undefined) { + return { function: fn }; + } + + const vars = await this._session.getLocalVariables(localScope.object.objectId); + + return { function: fn, vars }; + }); + + // We add the un-awaited promise to the cache rather than await here otherwise the event processor + // can be called before we're finished getting all the vars + this._cachedFrames.set(exceptionHash, Promise.all(framePromises)); + } + + /** + * Adds local variables event stack frames. + */ + private async _addLocalVariables(event: Event): Promise { + for (const exception of event?.exception?.values || []) { + await this._addLocalVariablesToException(exception); + } + + return event; + } + + /** + * Adds local variables to the exception stack frames. + */ + private async _addLocalVariablesToException(exception: Exception): Promise { + const hash = hashFrames(exception?.stacktrace?.frames); + + if (hash === undefined) { + return; + } + + // Check if we have local variables for an exception that matches the hash + // delete is identical to get but also removes the entry from the cache + const cachedFrames = await this._cachedFrames.delete(hash); + + if (cachedFrames === undefined) { + return; + } + + const frameCount = exception.stacktrace?.frames?.length || 0; + + for (let i = 0; i < frameCount; i++) { + // Sentry frames are in reverse order + const frameIndex = frameCount - i - 1; + + // Drop out if we run out of frames to match up + if (!exception?.stacktrace?.frames?.[frameIndex] || !cachedFrames[i]) { + break; + } + + if ( + // We need to have vars to add + cachedFrames[i].vars === undefined || + // We're not interested in frames that are not in_app because the vars are not relevant + exception.stacktrace.frames[frameIndex].in_app === false || + // The function names need to match + !functionNamesMatch(exception.stacktrace.frames[frameIndex].function, cachedFrames[i].function) + ) { + continue; + } + + exception.stacktrace.frames[frameIndex].vars = cachedFrames[i].vars; + } + } +} diff --git a/packages/node/src/integrations/modules.ts b/packages/node/src/integrations/modules.ts index 5140c5ee253a..376c0b0144f6 100644 --- a/packages/node/src/integrations/modules.ts +++ b/packages/node/src/integrations/modules.ts @@ -1,4 +1,4 @@ -import { EventProcessor, Hub, Integration } from '@sentry/types'; +import type { EventProcessor, Hub, Integration } from '@sentry/types'; import { existsSync, readFileSync } from 'fs'; import { dirname, join } from 'path'; diff --git a/packages/node/src/integrations/onuncaughtexception.ts b/packages/node/src/integrations/onuncaughtexception.ts index 2108e2829f63..2d10ae61d696 100644 --- a/packages/node/src/integrations/onuncaughtexception.ts +++ b/packages/node/src/integrations/onuncaughtexception.ts @@ -1,8 +1,9 @@ -import { getCurrentHub, Scope } from '@sentry/core'; -import { Integration } from '@sentry/types'; +import type { Scope } from '@sentry/core'; +import { getCurrentHub } from '@sentry/core'; +import type { Integration } from '@sentry/types'; import { logger } from '@sentry/utils'; -import { NodeClient } from '../client'; +import type { NodeClient } from '../client'; import { logAndExitProcess } from './utils/errorhandling'; type OnFatalErrorHandler = (firstError: Error, secondError?: Error) => void; @@ -29,7 +30,7 @@ interface OnUncaughtExceptionOptions { * `onFatalError` itself threw, or because an independent error happened somewhere else while `onFatalError` * was running. */ - onFatalError?(firstError: Error, secondError?: Error): void; + onFatalError?(this: void, firstError: Error, secondError?: Error): void; } /** Global Exception handler */ @@ -84,10 +85,8 @@ export class OnUncaughtException implements Integration { const client = getCurrentHub().getClient(); if (this._options.onFatalError) { - // eslint-disable-next-line @typescript-eslint/unbound-method onFatalError = this._options.onFatalError; } else if (client && client.getOptions().onFatalError) { - // eslint-disable-next-line @typescript-eslint/unbound-method onFatalError = client.getOptions().onFatalError as OnFatalErrorHandler; } diff --git a/packages/node/src/integrations/onunhandledrejection.ts b/packages/node/src/integrations/onunhandledrejection.ts index 2ba9a3d8f205..aa3916931ba7 100644 --- a/packages/node/src/integrations/onunhandledrejection.ts +++ b/packages/node/src/integrations/onunhandledrejection.ts @@ -1,5 +1,6 @@ -import { getCurrentHub, Scope } from '@sentry/core'; -import { Integration } from '@sentry/types'; +import type { Scope } from '@sentry/core'; +import { getCurrentHub } from '@sentry/core'; +import type { Integration } from '@sentry/types'; import { consoleSandbox } from '@sentry/utils'; import { logAndExitProcess } from './utils/errorhandling'; diff --git a/packages/node/src/integrations/requestdata.ts b/packages/node/src/integrations/requestdata.ts index ec091d6907f7..2994b797b221 100644 --- a/packages/node/src/integrations/requestdata.ts +++ b/packages/node/src/integrations/requestdata.ts @@ -1,10 +1,11 @@ // TODO (v8 or v9): Whenever this becomes a default integration for `@sentry/browser`, move this to `@sentry/core`. For // now, we leave it in `@sentry/integrations` so that it doesn't contribute bytes to our CDN bundles. -import { Event, EventProcessor, Hub, Integration, PolymorphicRequest, Transaction } from '@sentry/types'; +import type { Event, EventProcessor, Hub, Integration, PolymorphicRequest, Transaction } from '@sentry/types'; import { extractPathForTransaction } from '@sentry/utils'; -import { addRequestDataToEvent, AddRequestDataToEventOptions, TransactionNamingScheme } from '../requestdata'; +import type { AddRequestDataToEventOptions, TransactionNamingScheme } from '../requestdata'; +import { addRequestDataToEvent } from '../requestdata'; export type RequestDataIntegrationOptions = { /** diff --git a/packages/node/src/integrations/utils/errorhandling.ts b/packages/node/src/integrations/utils/errorhandling.ts index 912b01c55ba9..e4c7a14924a1 100644 --- a/packages/node/src/integrations/utils/errorhandling.ts +++ b/packages/node/src/integrations/utils/errorhandling.ts @@ -1,7 +1,7 @@ import { getCurrentHub } from '@sentry/core'; import { logger } from '@sentry/utils'; -import { NodeClient } from '../../client'; +import type { NodeClient } from '../../client'; const DEFAULT_SHUTDOWN_TIMEOUT = 2000; diff --git a/packages/node/src/integrations/utils/http.ts b/packages/node/src/integrations/utils/http.ts index 067b2db2da9e..1281f9d6329d 100644 --- a/packages/node/src/integrations/utils/http.ts +++ b/packages/node/src/integrations/utils/http.ts @@ -1,7 +1,7 @@ import { getCurrentHub } from '@sentry/core'; import { parseSemver } from '@sentry/utils'; -import * as http from 'http'; -import * as https from 'https'; +import type * as http from 'http'; +import type * as https from 'https'; import { URL } from 'url'; const NODE_VERSION = parseSemver(process.versions.node); diff --git a/packages/node/src/module.ts b/packages/node/src/module.ts index 1e759df77506..0bb3335b635c 100644 --- a/packages/node/src/module.ts +++ b/packages/node/src/module.ts @@ -27,14 +27,14 @@ export function getModule(filename: string | undefined): string | undefined { let n = path.lastIndexOf('/node_modules/'); if (n > -1) { // /node_modules/ is 14 chars - return `${path.substr(n + 14).replace(/\//g, '.')}:${file}`; + return `${path.slice(n + 14).replace(/\//g, '.')}:${file}`; } // Let's see if it's a part of the main module // To be a part of main module, it has to share the same base n = `${path}/`.lastIndexOf(base, 0); if (n === 0) { - let moduleName = path.substr(base.length).replace(/\//g, '.'); + let moduleName = path.slice(base.length).replace(/\//g, '.'); if (moduleName) { moduleName += ':'; } diff --git a/packages/node/src/requestDataDeprecated.ts b/packages/node/src/requestDataDeprecated.ts index 7e7ce5c5d241..2a45e71f8e47 100644 --- a/packages/node/src/requestDataDeprecated.ts +++ b/packages/node/src/requestDataDeprecated.ts @@ -6,13 +6,10 @@ /* eslint-disable deprecation/deprecation */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Event, ExtractedNodeRequestData, PolymorphicRequest } from '@sentry/types'; +import type { Event, ExtractedNodeRequestData, PolymorphicRequest } from '@sentry/types'; -import { - addRequestDataToEvent, - AddRequestDataToEventOptions, - extractRequestData as _extractRequestData, -} from './requestdata'; +import type { AddRequestDataToEventOptions } from './requestdata'; +import { addRequestDataToEvent, extractRequestData as _extractRequestData } from './requestdata'; /** * @deprecated `Handlers.ExpressRequest` is deprecated and will be removed in v8. Use `PolymorphicRequest` instead. diff --git a/packages/node/src/requestdata.ts b/packages/node/src/requestdata.ts index d72880df9680..d13aa78700f2 100644 --- a/packages/node/src/requestdata.ts +++ b/packages/node/src/requestdata.ts @@ -1,4 +1,10 @@ -import { Event, ExtractedNodeRequestData, PolymorphicRequest, Transaction, TransactionSource } from '@sentry/types'; +import type { + Event, + ExtractedNodeRequestData, + PolymorphicRequest, + Transaction, + TransactionSource, +} from '@sentry/types'; import { isPlainObject, isString, normalize, stripUrlQueryAndFragment } from '@sentry/utils'; import * as cookie from 'cookie'; import * as url from 'url'; diff --git a/packages/node/src/sdk.ts b/packages/node/src/sdk.ts index 42751525fbaa..71ae258826c8 100644 --- a/packages/node/src/sdk.ts +++ b/packages/node/src/sdk.ts @@ -7,7 +7,7 @@ import { Integrations as CoreIntegrations, setHubOnCarrier, } from '@sentry/core'; -import { SessionStatus, StackParser } from '@sentry/types'; +import type { SessionStatus, StackParser } from '@sentry/types'; import { createStackParser, GLOBAL_OBJ, @@ -24,6 +24,7 @@ import { ContextLines, Http, LinkedErrors, + LocalVariables, Modules, OnUncaughtException, OnUnhandledRejection, @@ -31,7 +32,7 @@ import { } from './integrations'; import { getModule } from './module'; import { makeNodeTransport } from './transports'; -import { NodeClientOptions, NodeOptions } from './types'; +import type { NodeClientOptions, NodeOptions } from './types'; export const defaultIntegrations = [ // Common @@ -45,6 +46,7 @@ export const defaultIntegrations = [ new OnUnhandledRejection(), // Event Info new ContextLines(), + new LocalVariables(), new Context(), new Modules(), new RequestData(), diff --git a/packages/node/src/transports/http-module.ts b/packages/node/src/transports/http-module.ts index d3856bcb4b8e..64b255cc869c 100644 --- a/packages/node/src/transports/http-module.ts +++ b/packages/node/src/transports/http-module.ts @@ -1,7 +1,7 @@ -import { IncomingHttpHeaders, RequestOptions as HTTPRequestOptions } from 'http'; -import { RequestOptions as HTTPSRequestOptions } from 'https'; -import { Writable } from 'stream'; -import { URL } from 'url'; +import type { IncomingHttpHeaders, RequestOptions as HTTPRequestOptions } from 'http'; +import type { RequestOptions as HTTPSRequestOptions } from 'https'; +import type { Writable } from 'stream'; +import type { URL } from 'url'; export type HTTPModuleRequestOptions = HTTPRequestOptions | HTTPSRequestOptions | string | URL; diff --git a/packages/node/src/transports/http.ts b/packages/node/src/transports/http.ts index 687325b776f4..a16f78e79a6f 100644 --- a/packages/node/src/transports/http.ts +++ b/packages/node/src/transports/http.ts @@ -1,5 +1,5 @@ import { createTransport } from '@sentry/core'; -import { +import type { BaseTransportOptions, Transport, TransportMakeRequestResponse, @@ -12,7 +12,7 @@ import { Readable } from 'stream'; import { URL } from 'url'; import { createGzip } from 'zlib'; -import { HTTPModule } from './http-module'; +import type { HTTPModule } from './http-module'; export interface NodeTransportOptions extends BaseTransportOptions { /** Define custom headers */ @@ -47,7 +47,18 @@ function streamFromBody(body: Uint8Array | string): Readable { * Creates a Transport that uses native the native 'http' and 'https' modules to send events to Sentry. */ export function makeNodeTransport(options: NodeTransportOptions): Transport { - const urlSegments = new URL(options.url); + let urlSegments: URL; + + try { + urlSegments = new URL(options.url); + } catch (e) { + // eslint-disable-next-line no-console + console.warn( + '[@sentry/node]: Invalid dsn or tunnel option, will not send any events. The tunnel option must be a full URL when used.', + ); + return createTransport(options, () => Promise.resolve({})); + } + const isHttps = urlSegments.protocol === 'https:'; // Proxy prioritization: http => `options.proxy` | `process.env.http_proxy` @@ -63,7 +74,8 @@ export function makeNodeTransport(options: NodeTransportOptions): Transport { // TODO(v7): Evaluate if we can set keepAlive to true. This would involve testing for memory leaks in older node // versions(>= 8) as they had memory leaks when using it: #2555 const agent = proxy - ? (new (require('https-proxy-agent'))(proxy) as http.Agent) + ? // eslint-disable-next-line @typescript-eslint/no-var-requires + (new (require('https-proxy-agent'))(proxy) as http.Agent) : new nativeHttpModule.Agent({ keepAlive, maxSockets: 30, timeout: 2000 }); const requestExecutor = createRequestExecutor(options, options.httpModule ?? nativeHttpModule, agent); diff --git a/packages/node/src/types.ts b/packages/node/src/types.ts index d4ca5dd4900d..6acdbb4b2566 100644 --- a/packages/node/src/types.ts +++ b/packages/node/src/types.ts @@ -1,6 +1,6 @@ -import { ClientOptions, Options, TracePropagationTargets } from '@sentry/types'; +import type { ClientOptions, Options, TracePropagationTargets } from '@sentry/types'; -import { NodeTransportOptions } from './transports'; +import type { NodeTransportOptions } from './transports'; export interface BaseNodeOptions { /** @@ -45,10 +45,10 @@ export interface BaseNodeOptions { * }); * ``` */ - shouldCreateSpanForRequest?(url: string): boolean; + shouldCreateSpanForRequest?(this: void, url: string): boolean; /** Callback that is executed when a fatal global error occurs. */ - onFatalError?(error: Error): void; + onFatalError?(this: void, error: Error): void; } /** diff --git a/packages/node/test/client.test.ts b/packages/node/test/client.test.ts index a219a307f9d9..a996b5408288 100644 --- a/packages/node/test/client.test.ts +++ b/packages/node/test/client.test.ts @@ -1,5 +1,5 @@ import { Scope, SessionFlusher } from '@sentry/core'; -import { Event, EventHint } from '@sentry/types'; +import type { Event, EventHint } from '@sentry/types'; import * as os from 'os'; import { NodeClient } from '../src'; diff --git a/packages/node/test/context-lines.test.ts b/packages/node/test/context-lines.test.ts index 2b0fef8206b8..de6f6f382cd8 100644 --- a/packages/node/test/context-lines.test.ts +++ b/packages/node/test/context-lines.test.ts @@ -1,4 +1,4 @@ -import { StackFrame } from '@sentry/types'; +import type { StackFrame } from '@sentry/types'; import * as fs from 'fs'; import { parseStackFrames } from '../src/eventbuilder'; diff --git a/packages/node/test/eventbuilders.test.ts b/packages/node/test/eventbuilders.test.ts index 49f8e8156c5c..46dfc02a3c33 100644 --- a/packages/node/test/eventbuilders.test.ts +++ b/packages/node/test/eventbuilders.test.ts @@ -1,4 +1,4 @@ -import { Client } from '@sentry/types'; +import type { Client } from '@sentry/types'; import { defaultStackParser, Scope } from '../src'; import { eventFromUnknownInput } from '../src/eventbuilder'; diff --git a/packages/node/test/handlers.test.ts b/packages/node/test/handlers.test.ts index 1bf422f59621..9145305d6c1f 100644 --- a/packages/node/test/handlers.test.ts +++ b/packages/node/test/handlers.test.ts @@ -1,7 +1,7 @@ import * as sentryCore from '@sentry/core'; import { Hub, makeMain, Scope } from '@sentry/core'; import { Transaction } from '@sentry/tracing'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { SentryError } from '@sentry/utils'; import * as http from 'http'; diff --git a/packages/node/test/helper/node-client-options.ts b/packages/node/test/helper/node-client-options.ts index d6973e4a437d..9103174ad813 100644 --- a/packages/node/test/helper/node-client-options.ts +++ b/packages/node/test/helper/node-client-options.ts @@ -1,7 +1,7 @@ import { createTransport } from '@sentry/core'; import { resolvedSyncPromise } from '@sentry/utils'; -import { NodeClientOptions } from '../../src/types'; +import type { NodeClientOptions } from '../../src/types'; export function getDefaultNodeClientOptions(options: Partial = {}): NodeClientOptions { return { diff --git a/packages/node/test/index.test.ts b/packages/node/test/index.test.ts index d345998d3628..6ca834874d2e 100644 --- a/packages/node/test/index.test.ts +++ b/packages/node/test/index.test.ts @@ -1,22 +1,21 @@ import { getMainCarrier, initAndBind, SDK_VERSION } from '@sentry/core'; -import { EventHint, Integration } from '@sentry/types'; +import type { EventHint, Integration } from '@sentry/types'; import * as domain from 'domain'; +import type { Event, Scope } from '../src'; import { addBreadcrumb, captureEvent, captureException, captureMessage, configureScope, - Event, getCurrentHub, init, NodeClient, - Scope, } from '../src'; import { ContextLines, LinkedErrors } from '../src/integrations'; import { defaultStackParser } from '../src/sdk'; -import { NodeClientOptions } from '../src/types'; +import type { NodeClientOptions } from '../src/types'; import { getDefaultNodeClientOptions } from './helper/node-client-options'; jest.mock('@sentry/core', () => { diff --git a/packages/node/test/integrations/http.test.ts b/packages/node/test/integrations/http.test.ts index c717550710cf..ab2370ec19c6 100644 --- a/packages/node/test/integrations/http.test.ts +++ b/packages/node/test/integrations/http.test.ts @@ -1,17 +1,18 @@ import * as sentryCore from '@sentry/core'; import { Hub } from '@sentry/core'; -import { addExtensionMethods, Span, TRACEPARENT_REGEXP, Transaction } from '@sentry/tracing'; -import { TransactionContext } from '@sentry/types'; +import type { Span, Transaction } from '@sentry/tracing'; +import { addExtensionMethods, TRACEPARENT_REGEXP } from '@sentry/tracing'; +import type { TransactionContext } from '@sentry/types'; import { logger, parseSemver } from '@sentry/utils'; import * as http from 'http'; import * as https from 'https'; import * as HttpsProxyAgent from 'https-proxy-agent'; import * as nock from 'nock'; -import { Breadcrumb } from '../../src'; +import type { Breadcrumb } from '../../src'; import { NodeClient } from '../../src/client'; import { Http as HttpIntegration } from '../../src/integrations/http'; -import { NodeClientOptions } from '../../src/types'; +import type { NodeClientOptions } from '../../src/types'; import { getDefaultNodeClientOptions } from '../helper/node-client-options'; const NODE_VERSION = parseSemver(process.versions.node); diff --git a/packages/node/test/integrations/linkederrors.test.ts b/packages/node/test/integrations/linkederrors.test.ts index 390a847eea34..6582c0f6e9e5 100644 --- a/packages/node/test/integrations/linkederrors.test.ts +++ b/packages/node/test/integrations/linkederrors.test.ts @@ -1,6 +1,7 @@ -import { ExtendedError } from '@sentry/types'; +import type { ExtendedError } from '@sentry/types'; -import { Event, NodeClient } from '../../src'; +import type { Event } from '../../src'; +import { NodeClient } from '../../src'; import { LinkedErrors } from '../../src/integrations/linkederrors'; import { defaultStackParser as stackParser } from '../../src/sdk'; import { getDefaultNodeClientOptions } from '../helper/node-client-options'; diff --git a/packages/node/test/integrations/localvariables.test.ts b/packages/node/test/integrations/localvariables.test.ts new file mode 100644 index 000000000000..5324c2908b0f --- /dev/null +++ b/packages/node/test/integrations/localvariables.test.ts @@ -0,0 +1,270 @@ +import type { ClientOptions, EventProcessor } from '@sentry/types'; +import type { Debugger, InspectorNotification } from 'inspector'; +import type { LRUMap } from 'lru_map'; + +import { defaultStackParser } from '../../src'; +import type { DebugSession, FrameVariables } from '../../src/integrations/localvariables'; +import { LocalVariables } from '../../src/integrations/localvariables'; +import { getDefaultNodeClientOptions } from '../../test/helper/node-client-options'; + +interface ThrowOn { + configureAndConnect?: boolean; + getLocalVariables?: boolean; +} + +class MockDebugSession implements DebugSession { + private _onPause?: (message: InspectorNotification) => void; + + constructor(private readonly _vars: Record>, private readonly _throwOn?: ThrowOn) {} + + public configureAndConnect(onPause: (message: InspectorNotification) => void): void { + if (this._throwOn?.configureAndConnect) { + throw new Error('configureAndConnect should not be called'); + } + + this._onPause = onPause; + } + + public async getLocalVariables(objectId: string): Promise> { + if (this._throwOn?.getLocalVariables) { + throw new Error('getLocalVariables should not be called'); + } + + return this._vars[objectId]; + } + + public runPause(message: InspectorNotification) { + this._onPause?.(message); + } +} + +interface LocalVariablesPrivate { + _cachedFrames: LRUMap>; + _setup(addGlobalEventProcessor: (callback: EventProcessor) => void, clientOptions: ClientOptions): void; +} + +const exceptionEvent = { + method: 'Debugger.paused', + params: { + reason: 'exception', + data: { + description: + 'Error: Some error\n' + + ' at two (/dist/javascript/src/main.js:23:9)\n' + + ' at one (/dist/javascript/src/main.js:19:3)\n' + + ' at Timeout._onTimeout (/dist/javascript/src/main.js:40:5)\n' + + ' at listOnTimeout (node:internal/timers:559:17)\n' + + ' at process.processTimers (node:internal/timers:502:7)', + }, + callFrames: [ + { + callFrameId: '-6224981551105448869.1.0', + functionName: 'two', + location: { scriptId: '134', lineNumber: 22 }, + url: '', + scopeChain: [ + { + type: 'local', + object: { + type: 'object', + className: 'Object', + objectId: '-6224981551105448869.1.2', + }, + name: 'two', + }, + ], + this: { + type: 'object', + className: 'global', + }, + }, + { + callFrameId: '-6224981551105448869.1.1', + functionName: 'one', + location: { scriptId: '134', lineNumber: 18 }, + url: '', + scopeChain: [ + { + type: 'local', + object: { + type: 'object', + className: 'Object', + objectId: '-6224981551105448869.1.6', + }, + name: 'one', + }, + ], + this: { + type: 'object', + className: 'global', + }, + }, + ], + }, +}; + +describe('LocalVariables', () => { + it('Adds local variables to stack frames', async () => { + expect.assertions(7); + + const session = new MockDebugSession({ + '-6224981551105448869.1.2': { name: 'tim' }, + '-6224981551105448869.1.6': { arr: [1, 2, 3] }, + }); + const localVariables = new LocalVariables({}, session); + const options = getDefaultNodeClientOptions({ + stackParser: defaultStackParser, + _experiments: { includeStackLocals: true }, + }); + + let eventProcessor: EventProcessor | undefined; + + (localVariables as unknown as LocalVariablesPrivate)._setup(callback => { + eventProcessor = callback; + }, options); + + expect(eventProcessor).toBeDefined(); + + session.runPause(exceptionEvent); + + expect((localVariables as unknown as LocalVariablesPrivate)._cachedFrames.size).toBe(1); + + let frames: Promise | undefined; + + (localVariables as unknown as LocalVariablesPrivate)._cachedFrames.forEach(promise => { + frames = promise; + }); + + expect(frames).toBeDefined(); + + const vars = await (frames as Promise); + + expect(vars).toEqual([ + { function: 'two', vars: { name: 'tim' } }, + { function: 'one', vars: { arr: [1, 2, 3] } }, + ]); + + const event = await eventProcessor?.( + { + event_id: '9cbf882ade9a415986632ac4e16918eb', + platform: 'node', + timestamp: 1671113680.306, + level: 'fatal', + exception: { + values: [ + { + type: 'Error', + value: 'Some error', + stacktrace: { + frames: [ + { + function: 'process.processTimers', + lineno: 502, + colno: 7, + in_app: false, + }, + { + function: 'listOnTimeout', + lineno: 559, + colno: 17, + in_app: false, + }, + { + function: 'Timeout._onTimeout', + lineno: 40, + colno: 5, + in_app: true, + }, + { + function: 'one', + lineno: 19, + colno: 3, + in_app: true, + }, + { + function: 'two', + lineno: 23, + colno: 9, + in_app: true, + }, + ], + }, + mechanism: { type: 'generic', handled: true }, + }, + ], + }, + }, + {}, + ); + + expect(event?.exception?.values?.[0].stacktrace?.frames?.[3]?.vars).toEqual({ arr: [1, 2, 3] }); + expect(event?.exception?.values?.[0].stacktrace?.frames?.[4]?.vars).toEqual({ name: 'tim' }); + + expect((localVariables as unknown as LocalVariablesPrivate)._cachedFrames.size).toBe(0); + }); + + it('Should not lookup variables for non-exception reasons', async () => { + expect.assertions(1); + + const session = new MockDebugSession({}, { getLocalVariables: true }); + const localVariables = new LocalVariables({}, session); + const options = getDefaultNodeClientOptions({ + stackParser: defaultStackParser, + _experiments: { includeStackLocals: true }, + }); + + (localVariables as unknown as LocalVariablesPrivate)._setup(_ => {}, options); + + const nonExceptionEvent = { + method: exceptionEvent.method, + params: { ...exceptionEvent.params, reason: 'non-exception-reason' }, + }; + + session.runPause(nonExceptionEvent); + + expect((localVariables as unknown as LocalVariablesPrivate)._cachedFrames.size).toBe(0); + }); + + it('Should not initialize when disabled', async () => { + expect.assertions(1); + + const session = new MockDebugSession({}, { configureAndConnect: true }); + const localVariables = new LocalVariables({}, session); + const options = getDefaultNodeClientOptions({ + stackParser: defaultStackParser, + _experiments: { includeStackLocals: false }, + }); + + let eventProcessor: EventProcessor | undefined; + + (localVariables as unknown as LocalVariablesPrivate)._setup(callback => { + eventProcessor = callback; + }, options); + + expect(eventProcessor).toBeUndefined(); + }); + + it.only('Should cache identical uncaught exception events', async () => { + expect.assertions(1); + + const session = new MockDebugSession({ + '-6224981551105448869.1.2': { name: 'tim' }, + '-6224981551105448869.1.6': { arr: [1, 2, 3] }, + }); + const localVariables = new LocalVariables({}, session); + const options = getDefaultNodeClientOptions({ + stackParser: defaultStackParser, + _experiments: { includeStackLocals: true }, + }); + + (localVariables as unknown as LocalVariablesPrivate)._setup(_ => {}, options); + + session.runPause(exceptionEvent); + session.runPause(exceptionEvent); + session.runPause(exceptionEvent); + session.runPause(exceptionEvent); + session.runPause(exceptionEvent); + + expect((localVariables as unknown as LocalVariablesPrivate)._cachedFrames.size).toBe(1); + }); +}); diff --git a/packages/node/test/integrations/requestdata.test.ts b/packages/node/test/integrations/requestdata.test.ts index 270c04cdfa86..91d9870f8292 100644 --- a/packages/node/test/integrations/requestdata.test.ts +++ b/packages/node/test/integrations/requestdata.test.ts @@ -1,10 +1,11 @@ import { getCurrentHub, Hub, makeMain } from '@sentry/core'; -import { Event, EventProcessor, PolymorphicRequest } from '@sentry/types'; +import type { Event, EventProcessor, PolymorphicRequest } from '@sentry/types'; import * as http from 'http'; import { NodeClient } from '../../src/client'; import { requestHandler } from '../../src/handlers'; -import { RequestData, RequestDataIntegrationOptions } from '../../src/integrations/requestdata'; +import type { RequestDataIntegrationOptions } from '../../src/integrations/requestdata'; +import { RequestData } from '../../src/integrations/requestdata'; import * as requestDataModule from '../../src/requestdata'; import { getDefaultNodeClientOptions } from '../helper/node-client-options'; diff --git a/packages/node/test/manual/webpack-domain/yarn.lock b/packages/node/test/manual/webpack-domain/yarn.lock index 211372cbec81..4c93e5750bcf 100644 --- a/packages/node/test/manual/webpack-domain/yarn.lock +++ b/packages/node/test/manual/webpack-domain/yarn.lock @@ -1391,9 +1391,9 @@ json-schema-traverse@^0.4.1: integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== json5@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== dependencies: minimist "^1.2.0" @@ -1577,9 +1577,9 @@ minimatch@^3.0.4: brace-expansion "^1.1.7" minimist@^1.2.0, minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + version "1.2.7" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" + integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== mississippi@^3.0.0: version "3.0.0" diff --git a/packages/node/test/requestdata.test.ts b/packages/node/test/requestdata.test.ts index 9c79f2cd59bd..744cc9077c37 100644 --- a/packages/node/test/requestdata.test.ts +++ b/packages/node/test/requestdata.test.ts @@ -2,20 +2,17 @@ // TODO (v8 / #5257): Remove everything related to the deprecated functions -import { Event, PolymorphicRequest, TransactionSource, User } from '@sentry/types'; -import * as net from 'net'; +import type { Event, PolymorphicRequest, TransactionSource, User } from '@sentry/types'; +import type * as net from 'net'; +import type { AddRequestDataToEventOptions } from '../src/requestdata'; import { addRequestDataToEvent, - AddRequestDataToEventOptions, extractPathForTransaction, extractRequestData as newExtractRequestData, } from '../src/requestdata'; -import { - ExpressRequest, - extractRequestData as oldExtractRequestData, - parseRequest, -} from '../src/requestDataDeprecated'; +import type { ExpressRequest } from '../src/requestDataDeprecated'; +import { extractRequestData as oldExtractRequestData, parseRequest } from '../src/requestDataDeprecated'; // TODO (v8 / #5257): Remove `describe.each` wrapper, remove `formatArgs` wrapper, reformat args in tests, and use only // `addRequestDataToEvent` diff --git a/packages/node/test/sdk.test.ts b/packages/node/test/sdk.test.ts index 48e5accee439..abd0265b62c4 100644 --- a/packages/node/test/sdk.test.ts +++ b/packages/node/test/sdk.test.ts @@ -1,4 +1,4 @@ -import { Integration } from '@sentry/types'; +import type { Integration } from '@sentry/types'; import { init } from '../src/sdk'; import * as sdk from '../src/sdk'; diff --git a/packages/node/test/transports/http.test.ts b/packages/node/test/transports/http.test.ts index 9594940c2014..d03e917c4f42 100644 --- a/packages/node/test/transports/http.test.ts +++ b/packages/node/test/transports/http.test.ts @@ -1,5 +1,5 @@ import { createTransport } from '@sentry/core'; -import { EventEnvelope, EventItem } from '@sentry/types'; +import type { EventEnvelope, EventItem } from '@sentry/types'; import { addItemToEnvelope, createAttachmentEnvelopeItem, createEnvelope, serializeEnvelope } from '@sentry/utils'; import * as http from 'http'; import { TextEncoder } from 'util'; @@ -7,6 +7,8 @@ import { createGunzip } from 'zlib'; import { makeNodeTransport } from '../../src/transports'; +const textEncoder = new TextEncoder(); + jest.mock('@sentry/core', () => { const actualCore = jest.requireActual('@sentry/core'); return { @@ -70,22 +72,19 @@ const EVENT_ENVELOPE = createEnvelope({ event_id: 'aa3ff046696b4b [{ type: 'event' }, { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }] as EventItem, ]); -const SERIALIZED_EVENT_ENVELOPE = serializeEnvelope(EVENT_ENVELOPE, new TextEncoder()); +const SERIALIZED_EVENT_ENVELOPE = serializeEnvelope(EVENT_ENVELOPE, textEncoder); const ATTACHMENT_ITEM = createAttachmentEnvelopeItem( { filename: 'empty-file.bin', data: new Uint8Array(50_000) }, - new TextEncoder(), + textEncoder, ); const EVENT_ATTACHMENT_ENVELOPE = addItemToEnvelope(EVENT_ENVELOPE, ATTACHMENT_ITEM); -const SERIALIZED_EVENT_ATTACHMENT_ENVELOPE = serializeEnvelope( - EVENT_ATTACHMENT_ENVELOPE, - new TextEncoder(), -) as Uint8Array; +const SERIALIZED_EVENT_ATTACHMENT_ENVELOPE = serializeEnvelope(EVENT_ATTACHMENT_ENVELOPE, textEncoder) as Uint8Array; const defaultOptions = { url: TEST_SERVER_URL, recordDroppedEvent: () => undefined, - textEncoder: new TextEncoder(), + textEncoder, }; describe('makeNewHttpTransport()', () => { @@ -151,7 +150,9 @@ describe('makeNewHttpTransport()', () => { const transport = makeNodeTransport(defaultOptions); - await expect(transport.send(EVENT_ENVELOPE)).resolves.toBeUndefined(); + await expect(transport.send(EVENT_ENVELOPE)).resolves.toEqual( + expect.objectContaining({ statusCode: serverStatusCode }), + ); }, ); @@ -165,20 +166,13 @@ describe('makeNewHttpTransport()', () => { }); const transport = makeNodeTransport(defaultOptions); - await expect(transport.send(EVENT_ENVELOPE)).resolves.toBeUndefined(); - }); - - it('should resolve when server responds with rate limit header and status code 200', async () => { - await setupTestServer({ + await expect(transport.send(EVENT_ENVELOPE)).resolves.toEqual({ statusCode: SUCCESS, - responseHeaders: { - 'Retry-After': '2700', - 'X-Sentry-Rate-Limits': '60::organization, 2700::organization', + headers: { + 'retry-after': '2700', + 'x-sentry-rate-limits': '60::organization, 2700::organization', }, }); - - const transport = makeNodeTransport(defaultOptions); - await transport.send(EVENT_ENVELOPE); }); }); @@ -308,7 +302,7 @@ describe('makeNewHttpTransport()', () => { const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ - body: serializeEnvelope(EVENT_ENVELOPE, new TextEncoder()), + body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), category: 'error', }); @@ -328,7 +322,7 @@ describe('makeNewHttpTransport()', () => { const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ - body: serializeEnvelope(EVENT_ENVELOPE, new TextEncoder()), + body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), category: 'error', }); @@ -356,7 +350,7 @@ describe('makeNewHttpTransport()', () => { const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ - body: serializeEnvelope(EVENT_ENVELOPE, new TextEncoder()), + body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), category: 'error', }); @@ -384,7 +378,7 @@ describe('makeNewHttpTransport()', () => { const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ - body: serializeEnvelope(EVENT_ENVELOPE, new TextEncoder()), + body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), category: 'error', }); @@ -399,4 +393,20 @@ describe('makeNewHttpTransport()', () => { ); }); }); + + it('should create a noop transport if an invalid url is passed', async () => { + const requestSpy = jest.spyOn(http, 'request'); + const transport = makeNodeTransport({ ...defaultOptions, url: 'foo' }); + await transport.send(EVENT_ENVELOPE); + expect(requestSpy).not.toHaveBeenCalled(); + }); + + it('should warn if an invalid url is passed', async () => { + const consoleWarnSpy = jest.spyOn(console, 'warn'); + const transport = makeNodeTransport({ ...defaultOptions, url: 'invalid url' }); + await transport.send(EVENT_ENVELOPE); + expect(consoleWarnSpy).toHaveBeenCalledWith( + '[@sentry/node]: Invalid dsn or tunnel option, will not send any events. The tunnel option must be a full URL when used.', + ); + }); }); diff --git a/packages/node/test/transports/https.test.ts b/packages/node/test/transports/https.test.ts index cf7051b54fe4..d43b8306bed0 100644 --- a/packages/node/test/transports/https.test.ts +++ b/packages/node/test/transports/https.test.ts @@ -1,14 +1,16 @@ import { createTransport } from '@sentry/core'; -import { EventEnvelope, EventItem } from '@sentry/types'; +import type { EventEnvelope, EventItem } from '@sentry/types'; import { createEnvelope, serializeEnvelope } from '@sentry/utils'; import * as http from 'http'; import * as https from 'https'; import { TextEncoder } from 'util'; import { makeNodeTransport } from '../../src/transports'; -import { HTTPModule, HTTPModuleRequestIncomingMessage } from '../../src/transports/http-module'; +import type { HTTPModule, HTTPModuleRequestIncomingMessage } from '../../src/transports/http-module'; import testServerCerts from './test-server-certs'; +const textEncoder = new TextEncoder(); + jest.mock('@sentry/core', () => { const actualCore = jest.requireActual('@sentry/core'); return { @@ -70,7 +72,7 @@ const EVENT_ENVELOPE = createEnvelope({ event_id: 'aa3ff046696b4b [{ type: 'event' }, { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }] as EventItem, ]); -const SERIALIZED_EVENT_ENVELOPE = serializeEnvelope(EVENT_ENVELOPE, new TextEncoder()); +const SERIALIZED_EVENT_ENVELOPE = serializeEnvelope(EVENT_ENVELOPE, textEncoder); const unsafeHttpsModule: HTTPModule = { request: jest @@ -84,7 +86,7 @@ const defaultOptions = { httpModule: unsafeHttpsModule, url: TEST_SERVER_URL, recordDroppedEvent: () => undefined, // noop - textEncoder: new TextEncoder(), + textEncoder, }; describe('makeNewHttpsTransport()', () => { @@ -151,20 +153,13 @@ describe('makeNewHttpsTransport()', () => { }); const transport = makeNodeTransport(defaultOptions); - await expect(transport.send(EVENT_ENVELOPE)).resolves.toBeUndefined(); - }); - - it('should resolve when server responds with rate limit header and status code 200', async () => { - await setupTestServer({ + await expect(transport.send(EVENT_ENVELOPE)).resolves.toEqual({ statusCode: SUCCESS, - responseHeaders: { - 'Retry-After': '2700', - 'X-Sentry-Rate-Limits': '60::organization, 2700::organization', + headers: { + 'retry-after': '2700', + 'x-sentry-rate-limits': '60::organization, 2700::organization', }, }); - - const transport = makeNodeTransport(defaultOptions); - await expect(transport.send(EVENT_ENVELOPE)).resolves.toBeUndefined(); }); it('should use `caCerts` option', async () => { @@ -299,7 +294,7 @@ describe('makeNewHttpsTransport()', () => { const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ - body: serializeEnvelope(EVENT_ENVELOPE, new TextEncoder()), + body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), category: 'error', }); @@ -319,7 +314,7 @@ describe('makeNewHttpsTransport()', () => { const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ - body: serializeEnvelope(EVENT_ENVELOPE, new TextEncoder()), + body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), category: 'error', }); @@ -347,7 +342,7 @@ describe('makeNewHttpsTransport()', () => { const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ - body: serializeEnvelope(EVENT_ENVELOPE, new TextEncoder()), + body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), category: 'error', }); @@ -375,7 +370,7 @@ describe('makeNewHttpsTransport()', () => { const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ - body: serializeEnvelope(EVENT_ENVELOPE, new TextEncoder()), + body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), category: 'error', }); diff --git a/packages/opentelemetry-node/package.json b/packages/opentelemetry-node/package.json index b7c2d1359448..35982c32fd78 100644 --- a/packages/opentelemetry-node/package.json +++ b/packages/opentelemetry-node/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/opentelemetry-node", - "version": "7.29.0", + "version": "7.30.0", "description": "Official Sentry SDK for OpenTelemetry Node.js", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/opentelemetry-node", @@ -16,10 +16,10 @@ "access": "public" }, "dependencies": { - "@sentry/core": "7.29.0", - "@sentry/tracing": "7.29.0", - "@sentry/types": "7.29.0", - "@sentry/utils": "7.29.0" + "@sentry/core": "7.30.0", + "@sentry/tracing": "7.30.0", + "@sentry/types": "7.30.0", + "@sentry/utils": "7.30.0" }, "peerDependencies": { "@opentelemetry/api": "1.x", @@ -33,18 +33,18 @@ "@opentelemetry/sdk-trace-base": "^1.7.0", "@opentelemetry/sdk-trace-node": "^1.7.0", "@opentelemetry/semantic-conventions": "^1.7.0", - "@sentry/node": "7.29.0" + "@sentry/node": "7.30.0" }, "scripts": { - "build": "run-p build:rollup build:types", + "build": "run-p build:transpile build:types", "build:dev": "run-s build", - "build:rollup": "rollup -c rollup.npm.config.js", + "build:transpile": "rollup -c rollup.npm.config.js", "build:types": "tsc -p tsconfig.types.json", - "build:watch": "run-p build:rollup:watch build:types:watch", + "build:watch": "run-p build:transpile:watch build:types:watch", "build:dev:watch": "run-s build:watch", - "build:rollup:watch": "rollup -c rollup.npm.config.js --watch", + "build:transpile:watch": "rollup -c rollup.npm.config.js --watch", "build:types:watch": "tsc -p tsconfig.types.json --watch", - "build:npm": "ts-node ../../scripts/prepack.ts && npm pack ./build", + "build:tarball": "ts-node ../../scripts/prepack.ts && npm pack ./build", "circularDepCheck": "madge --circular src/index.ts", "clean": "rimraf build coverage sentry-node-*.tgz", "fix": "run-s fix:eslint fix:prettier", diff --git a/packages/opentelemetry-node/src/propagator.ts b/packages/opentelemetry-node/src/propagator.ts index 4e0df90f912a..913e1ac218df 100644 --- a/packages/opentelemetry-node/src/propagator.ts +++ b/packages/opentelemetry-node/src/propagator.ts @@ -1,17 +1,10 @@ -import { - Context, - isSpanContextValid, - TextMapGetter, - TextMapPropagator, - TextMapSetter, - trace, - TraceFlags, -} from '@opentelemetry/api'; -import { isTracingSuppressed } from '@opentelemetry/core'; +import type { Baggage, Context, TextMapGetter, TextMapSetter } from '@opentelemetry/api'; +import { isSpanContextValid, propagation, trace, TraceFlags } from '@opentelemetry/api'; +import { isTracingSuppressed, W3CBaggagePropagator } from '@opentelemetry/core'; import { baggageHeaderToDynamicSamplingContext, - dynamicSamplingContextToSentryBaggageHeader, extractTraceparentData, + SENTRY_BAGGAGE_KEY_PREFIX, } from '@sentry/utils'; import { @@ -25,7 +18,7 @@ import { SENTRY_SPAN_PROCESSOR_MAP } from './spanprocessor'; /** * Injects and extracts `sentry-trace` and `baggage` headers from carriers. */ -export class SentryPropagator implements TextMapPropagator { +export class SentryPropagator extends W3CBaggagePropagator { /** * @inheritDoc */ @@ -41,10 +34,18 @@ export class SentryPropagator implements TextMapPropagator { if (span.transaction) { const dynamicSamplingContext = span.transaction.getDynamicSamplingContext(); - const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext); - if (sentryBaggageHeader) { - setter.set(carrier, SENTRY_BAGGAGE_HEADER, sentryBaggageHeader); - } + + const baggage = propagation.getBaggage(context) || propagation.createBaggage({}); + const baggageWithSentryInfo = Object.entries(dynamicSamplingContext).reduce( + (b, [dscKey, dscValue]) => { + if (dscValue) { + return b.setEntry(`${SENTRY_BAGGAGE_KEY_PREFIX}${dscKey}`, { value: dscValue }); + } + return b; + }, + baggage, + ); + super.inject(propagation.setBaggage(context, baggageWithSentryInfo), carrier, setter); } } } diff --git a/packages/opentelemetry-node/src/spanprocessor.ts b/packages/opentelemetry-node/src/spanprocessor.ts index dfb8c6f0d2b2..8aac3e1c53be 100644 --- a/packages/opentelemetry-node/src/spanprocessor.ts +++ b/packages/opentelemetry-node/src/spanprocessor.ts @@ -1,8 +1,9 @@ -import { Context, trace } from '@opentelemetry/api'; -import { Span as OtelSpan, SpanProcessor as OtelSpanProcessor } from '@opentelemetry/sdk-trace-base'; +import type { Context } from '@opentelemetry/api'; +import { trace } from '@opentelemetry/api'; +import type { Span as OtelSpan, SpanProcessor as OtelSpanProcessor } from '@opentelemetry/sdk-trace-base'; import { addGlobalEventProcessor, getCurrentHub } from '@sentry/core'; import { Transaction } from '@sentry/tracing'; -import { DynamicSamplingContext, Span as SentrySpan, TraceparentData, TransactionContext } from '@sentry/types'; +import type { DynamicSamplingContext, Span as SentrySpan, TraceparentData, TransactionContext } from '@sentry/types'; import { logger } from '@sentry/utils'; import { SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, SENTRY_TRACE_PARENT_CONTEXT_KEY } from './constants'; @@ -22,25 +23,22 @@ export const SENTRY_SPAN_PROCESSOR_MAP: Map = export class SentrySpanProcessor implements OtelSpanProcessor { public constructor() { addGlobalEventProcessor(event => { - const otelSpan = trace.getActiveSpan(); + const otelSpan = trace.getActiveSpan() as OtelSpan; if (!otelSpan) { return event; } - const otelSpanId = otelSpan.spanContext().spanId; - const sentrySpan = SENTRY_SPAN_PROCESSOR_MAP.get(otelSpanId); - - if (!sentrySpan) { - return event; - } + const otelSpanContext = otelSpan.spanContext(); // If event has already set `trace` context, use that one. - // This happens in the case of transaction events. - event.contexts = { trace: sentrySpan.getTraceContext(), ...event.contexts }; - const transactionName = sentrySpan.transaction && sentrySpan.transaction.name; - if (transactionName) { - event.tags = { transaction: transactionName, ...event.tags }; - } + event.contexts = { + trace: { + trace_id: otelSpanContext.traceId, + span_id: otelSpanContext.spanId, + parent_span_id: otelSpan.parentSpanId, + }, + ...event.contexts, + }; return event; }); diff --git a/packages/opentelemetry-node/src/utils/is-sentry-request.ts b/packages/opentelemetry-node/src/utils/is-sentry-request.ts index 1a504f3c3820..b02e3b4cb588 100644 --- a/packages/opentelemetry-node/src/utils/is-sentry-request.ts +++ b/packages/opentelemetry-node/src/utils/is-sentry-request.ts @@ -1,4 +1,4 @@ -import { Span as OtelSpan } from '@opentelemetry/sdk-trace-base'; +import type { Span as OtelSpan } from '@opentelemetry/sdk-trace-base'; import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; import { getCurrentHub } from '@sentry/core'; diff --git a/packages/opentelemetry-node/src/utils/map-otel-status.ts b/packages/opentelemetry-node/src/utils/map-otel-status.ts index 869d80862d13..789cd7706622 100644 --- a/packages/opentelemetry-node/src/utils/map-otel-status.ts +++ b/packages/opentelemetry-node/src/utils/map-otel-status.ts @@ -1,6 +1,6 @@ -import { Span as OtelSpan } from '@opentelemetry/sdk-trace-base'; +import type { Span as OtelSpan } from '@opentelemetry/sdk-trace-base'; import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; -import { SpanStatusType as SentryStatus } from '@sentry/tracing'; +import type { SpanStatusType as SentryStatus } from '@sentry/tracing'; // canonicalCodesHTTPMap maps some HTTP codes to Sentry's span statuses. See possible mapping in https://develop.sentry.dev/sdk/event-payloads/span/ const canonicalCodesHTTPMap: Record = { diff --git a/packages/opentelemetry-node/src/utils/parse-otel-span-description.ts b/packages/opentelemetry-node/src/utils/parse-otel-span-description.ts index d95fc7028223..396e37a29ced 100644 --- a/packages/opentelemetry-node/src/utils/parse-otel-span-description.ts +++ b/packages/opentelemetry-node/src/utils/parse-otel-span-description.ts @@ -1,7 +1,8 @@ -import { AttributeValue, SpanKind } from '@opentelemetry/api'; -import { Span as OtelSpan } from '@opentelemetry/sdk-trace-base'; +import type { AttributeValue } from '@opentelemetry/api'; +import { SpanKind } from '@opentelemetry/api'; +import type { Span as OtelSpan } from '@opentelemetry/sdk-trace-base'; import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; -import { TransactionSource } from '@sentry/types'; +import type { TransactionSource } from '@sentry/types'; interface SpanDescription { op: string | undefined; diff --git a/packages/opentelemetry-node/test/propagator.test.ts b/packages/opentelemetry-node/test/propagator.test.ts index 49e2243fe8ee..27c633e61c06 100644 --- a/packages/opentelemetry-node/test/propagator.test.ts +++ b/packages/opentelemetry-node/test/propagator.test.ts @@ -1,8 +1,15 @@ -import { defaultTextMapGetter, defaultTextMapSetter, ROOT_CONTEXT, trace, TraceFlags } from '@opentelemetry/api'; +import { + defaultTextMapGetter, + defaultTextMapSetter, + propagation, + ROOT_CONTEXT, + trace, + TraceFlags, +} from '@opentelemetry/api'; import { suppressTracing } from '@opentelemetry/core'; import { Hub, makeMain } from '@sentry/core'; import { addExtensionMethods, Transaction } from '@sentry/tracing'; -import { TransactionContext } from '@sentry/types'; +import type { TransactionContext } from '@sentry/types'; import { SENTRY_BAGGAGE_HEADER, @@ -138,6 +145,27 @@ describe('SentryPropagator', () => { expect(carrier[SENTRY_TRACE_HEADER]).toBe(sentryTrace); }); + it('should include existing baggage', () => { + const transactionContext = { + name: 'sampled-transaction', + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + sampled: true, + }; + const spanContext = { + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + traceFlags: TraceFlags.SAMPLED, + }; + createTransactionAndMaybeSpan(type, transactionContext); + const context = trace.setSpanContext(ROOT_CONTEXT, spanContext); + const baggage = propagation.createBaggage({ foo: { value: 'bar' } }); + propagator.inject(propagation.setBaggage(context, baggage), carrier, defaultTextMapSetter); + expect(carrier[SENTRY_BAGGAGE_HEADER]).toBe( + 'foo=bar,sentry-environment=production,sentry-release=1.0.0,sentry-transaction=sampled-transaction,sentry-public_key=abc,sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b', + ); + }); + it('should NOT set baggage and sentry-trace header if instrumentation is supressed', () => { const spanContext = { traceId: 'd4cda95b652f4a1592b449d5929fda1b', diff --git a/packages/opentelemetry-node/test/spanprocessor.test.ts b/packages/opentelemetry-node/test/spanprocessor.test.ts index 746f6e447c13..d07c0b950f64 100644 --- a/packages/opentelemetry-node/test/spanprocessor.test.ts +++ b/packages/opentelemetry-node/test/spanprocessor.test.ts @@ -1,12 +1,13 @@ -import * as OpenTelemetry from '@opentelemetry/api'; +import type * as OpenTelemetry from '@opentelemetry/api'; import { SpanKind } from '@opentelemetry/api'; import { Resource } from '@opentelemetry/resources'; -import { Span as OtelSpan } from '@opentelemetry/sdk-trace-base'; +import type { Span as OtelSpan } from '@opentelemetry/sdk-trace-base'; import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'; import { SemanticAttributes, SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; import { createTransport, Hub, makeMain } from '@sentry/core'; import { NodeClient } from '@sentry/node'; -import { addExtensionMethods, Span as SentrySpan, SpanStatusType, Transaction } from '@sentry/tracing'; +import type { SpanStatusType } from '@sentry/tracing'; +import { addExtensionMethods, Span as SentrySpan, Transaction } from '@sentry/tracing'; import { resolvedSyncPromise } from '@sentry/utils'; import { SENTRY_SPAN_PROCESSOR_MAP, SentrySpanProcessor } from '../src/spanprocessor'; @@ -744,7 +745,6 @@ describe('SentrySpanProcessor', () => { expect(sentryEvent).toBeDefined(); expect(sentryEvent.exception).toBeDefined(); expect(sentryEvent.contexts.trace).toEqual({ - description: otelSpan.name, parent_span_id: otelSpan.parentSpanId, span_id: otelSpan.spanContext().spanId, trace_id: otelSpan.spanContext().traceId, diff --git a/packages/react/package.json b/packages/react/package.json index 48d8008547e0..c97a29aa314c 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/react", - "version": "7.29.0", + "version": "7.30.0", "description": "Official Sentry SDK for React.js", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/react", @@ -16,9 +16,9 @@ "access": "public" }, "dependencies": { - "@sentry/browser": "7.29.0", - "@sentry/types": "7.29.0", - "@sentry/utils": "7.29.0", + "@sentry/browser": "7.30.0", + "@sentry/types": "7.30.0", + "@sentry/utils": "7.30.0", "hoist-non-react-statics": "^3.3.2", "tslib": "^1.9.3" }, @@ -51,15 +51,15 @@ "redux": "^4.0.5" }, "scripts": { - "build": "run-p build:rollup build:types", + "build": "run-p build:transpile build:types", "build:dev": "run-s build", - "build:rollup": "rollup -c rollup.npm.config.js", + "build:transpile": "rollup -c rollup.npm.config.js", "build:types": "tsc -p tsconfig.types.json", - "build:watch": "run-p build:rollup:watch build:types:watch", + "build:watch": "run-p build:transpile:watch build:types:watch", "build:dev:watch": "run-s build:watch", - "build:rollup:watch": "rollup -c rollup.npm.config.js --watch", + "build:transpile:watch": "rollup -c rollup.npm.config.js --watch", "build:types:watch": "tsc -p tsconfig.types.json --watch", - "build:npm": "ts-node ../../scripts/prepack.ts && npm pack ./build", + "build:tarball": "ts-node ../../scripts/prepack.ts && npm pack ./build", "circularDepCheck": "madge --circular src/index.ts", "clean": "rimraf build coverage sentry-react-*.tgz", "fix": "run-s fix:eslint fix:prettier", diff --git a/packages/react/src/errorboundary.tsx b/packages/react/src/errorboundary.tsx index 7aae1fbf6d08..907f7e029314 100644 --- a/packages/react/src/errorboundary.tsx +++ b/packages/react/src/errorboundary.tsx @@ -1,4 +1,5 @@ -import { captureException, ReportDialogOptions, Scope, showReportDialog, withScope } from '@sentry/browser'; +import type { ReportDialogOptions, Scope} from '@sentry/browser'; +import { captureException, showReportDialog, withScope } from '@sentry/browser'; import { isError, logger } from '@sentry/utils'; import hoistNonReactStatics from 'hoist-non-react-statics'; import * as React from 'react'; diff --git a/packages/react/src/profiler.tsx b/packages/react/src/profiler.tsx index 3f8f397f5ad9..d4c0f0a277e4 100644 --- a/packages/react/src/profiler.tsx +++ b/packages/react/src/profiler.tsx @@ -1,7 +1,8 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { getCurrentHub, Hub } from '@sentry/browser'; -import { Span, Transaction } from '@sentry/types'; +import type { Hub } from '@sentry/browser'; +import { getCurrentHub } from '@sentry/browser'; +import type { Span, Transaction } from '@sentry/types'; import { timestampWithMs } from '@sentry/utils'; import hoistNonReactStatics from 'hoist-non-react-statics'; import * as React from 'react'; diff --git a/packages/react/src/reactrouter.tsx b/packages/react/src/reactrouter.tsx index bad84fabf5a8..bfe89afd0890 100644 --- a/packages/react/src/reactrouter.tsx +++ b/packages/react/src/reactrouter.tsx @@ -1,9 +1,9 @@ import { WINDOW } from '@sentry/browser'; -import { Transaction, TransactionSource } from '@sentry/types'; +import type { Transaction, TransactionSource } from '@sentry/types'; import hoistNonReactStatics from 'hoist-non-react-statics'; import * as React from 'react'; -import { Action, Location, ReactRouterInstrumentation } from './types'; +import type { Action, Location, ReactRouterInstrumentation } from './types'; // We need to disable eslint no-explict-any because any is required for the // react-router typings. diff --git a/packages/react/src/reactrouterv3.ts b/packages/react/src/reactrouterv3.ts index e916784ede4e..4d12b3581e53 100644 --- a/packages/react/src/reactrouterv3.ts +++ b/packages/react/src/reactrouterv3.ts @@ -1,7 +1,7 @@ import { WINDOW } from '@sentry/browser'; -import { Primitive, Transaction, TransactionContext, TransactionSource } from '@sentry/types'; +import type { Primitive, Transaction, TransactionContext, TransactionSource } from '@sentry/types'; -import { Location, ReactRouterInstrumentation } from './types'; +import type { Location, ReactRouterInstrumentation } from './types'; // Many of the types below had to be mocked out to prevent typescript issues // these types are required for correct functionality. diff --git a/packages/react/src/reactrouterv6.tsx b/packages/react/src/reactrouterv6.tsx index 47db243cd8ab..877cd770dc7a 100644 --- a/packages/react/src/reactrouterv6.tsx +++ b/packages/react/src/reactrouterv6.tsx @@ -2,12 +2,12 @@ // https://gist.github.com/wontondon/e8c4bdf2888875e4c755712e99279536 import { WINDOW } from '@sentry/browser'; -import { Transaction, TransactionContext, TransactionSource } from '@sentry/types'; +import type { Transaction, TransactionContext, TransactionSource } from '@sentry/types'; import { getNumberOfUrlSegments, logger } from '@sentry/utils'; import hoistNonReactStatics from 'hoist-non-react-statics'; import React from 'react'; -import { +import type { Action, AgnosticDataRouteMatch, CreateRouterFunction, diff --git a/packages/react/src/redux.ts b/packages/react/src/redux.ts index d0751b4f43c3..5ef64a3d2522 100644 --- a/packages/react/src/redux.ts +++ b/packages/react/src/redux.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { configureScope } from '@sentry/browser'; -import { Scope } from '@sentry/types'; +import type { Scope } from '@sentry/types'; interface Action { type: T; @@ -111,7 +111,6 @@ function createReduxEnhancer(enhancerOptions?: Partial): } /* Allow user to configure scope with latest state */ - // eslint-disable-next-line @typescript-eslint/unbound-method const { configureScopeWithState } = options; if (typeof configureScopeWithState === 'function') { configureScopeWithState(scope, newState); diff --git a/packages/react/src/sdk.ts b/packages/react/src/sdk.ts index 038a509c03d7..e224d72945bb 100644 --- a/packages/react/src/sdk.ts +++ b/packages/react/src/sdk.ts @@ -1,4 +1,5 @@ -import { BrowserOptions, init as browserInit, SDK_VERSION } from '@sentry/browser'; +import type { BrowserOptions } from '@sentry/browser'; +import { init as browserInit, SDK_VERSION } from '@sentry/browser'; /** * Inits the React SDK diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts index 57b2814463ec..0b908a800ff7 100644 --- a/packages/react/src/types.ts +++ b/packages/react/src/types.ts @@ -1,6 +1,6 @@ // Disabling `no-explicit-any` for the whole file as `any` has became common requirement. /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Transaction, TransactionContext } from '@sentry/types'; +import type { Transaction, TransactionContext } from '@sentry/types'; export type Action = 'PUSH' | 'REPLACE' | 'POP'; diff --git a/packages/react/test/errorboundary.test.tsx b/packages/react/test/errorboundary.test.tsx index 7b0c25dc311a..d37a1de49116 100644 --- a/packages/react/test/errorboundary.test.tsx +++ b/packages/react/test/errorboundary.test.tsx @@ -3,9 +3,10 @@ import { fireEvent, render, screen } from '@testing-library/react'; import * as React from 'react'; import { useState } from 'react'; +import type { + ErrorBoundaryProps} from '../src/errorboundary'; import { ErrorBoundary, - ErrorBoundaryProps, isAtLeastReact17, UNKNOWN_COMPONENT, withErrorBoundary, diff --git a/packages/react/test/profiler.test.tsx b/packages/react/test/profiler.test.tsx index 96988f24b44b..20dc1f2962c5 100644 --- a/packages/react/test/profiler.test.tsx +++ b/packages/react/test/profiler.test.tsx @@ -1,4 +1,4 @@ -import { SpanContext } from '@sentry/types'; +import type { SpanContext } from '@sentry/types'; import { render } from '@testing-library/react'; import { renderHook } from '@testing-library/react-hooks'; import * as React from 'react'; diff --git a/packages/react/test/reactrouterv3.test.tsx b/packages/react/test/reactrouterv3.test.tsx index 336f52b74268..a4cbe463adbc 100644 --- a/packages/react/test/reactrouterv3.test.tsx +++ b/packages/react/test/reactrouterv3.test.tsx @@ -2,7 +2,8 @@ import { act, render } from '@testing-library/react'; import * as React from 'react'; import { createMemoryHistory, createRoutes, IndexRoute, match, Route, Router } from 'react-router-3'; -import { Match, reactRouterV3Instrumentation, Route as RouteType } from '../src/reactrouterv3'; +import type { Match, Route as RouteType } from '../src/reactrouterv3'; +import { reactRouterV3Instrumentation } from '../src/reactrouterv3'; // Have to manually set types because we are using package-alias declare module 'react-router-3' { diff --git a/packages/react/test/reactrouterv4.test.tsx b/packages/react/test/reactrouterv4.test.tsx index 7e2dfd14a763..343f78c175e5 100644 --- a/packages/react/test/reactrouterv4.test.tsx +++ b/packages/react/test/reactrouterv4.test.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { matchPath, Route, Router, Switch } from 'react-router-4'; import { reactRouterV4Instrumentation, withSentryRouting } from '../src'; -import { RouteConfig } from '../src/reactrouter'; +import type { RouteConfig } from '../src/reactrouter'; describe('React Router v4', () => { function createInstrumentation(_opts?: { diff --git a/packages/react/test/reactrouterv5.test.tsx b/packages/react/test/reactrouterv5.test.tsx index f37dc10edea7..568e630ccc25 100644 --- a/packages/react/test/reactrouterv5.test.tsx +++ b/packages/react/test/reactrouterv5.test.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { matchPath, Route, Router, Switch } from 'react-router-5'; import { reactRouterV5Instrumentation, withSentryRouting } from '../src'; -import { RouteConfig } from '../src/reactrouter'; +import type { RouteConfig } from '../src/reactrouter'; describe('React Router v5', () => { function createInstrumentation(_opts?: { diff --git a/packages/react/test/reactrouterv6.4.test.tsx b/packages/react/test/reactrouterv6.4.test.tsx index aeb7953ea1ab..0863535b38d5 100644 --- a/packages/react/test/reactrouterv6.4.test.tsx +++ b/packages/react/test/reactrouterv6.4.test.tsx @@ -13,7 +13,7 @@ import { } from 'react-router-6.4'; import { reactRouterV6Instrumentation,wrapCreateBrowserRouter } from '../src'; -import { CreateRouterFunction } from '../src/types'; +import type { CreateRouterFunction } from '../src/types'; beforeAll(() => { // @ts-ignore need to override global Request because it's not in the jest environment (even with an diff --git a/packages/react/test/redux.test.ts b/packages/react/test/redux.test.ts index 9be231adaddb..bf9fc31853c4 100644 --- a/packages/react/test/redux.test.ts +++ b/packages/react/test/redux.test.ts @@ -1,5 +1,5 @@ import * as Sentry from '@sentry/browser'; -import { Scope } from '@sentry/types'; +import type { Scope } from '@sentry/types'; import * as Redux from 'redux'; import { createReduxEnhancer } from '../src/redux'; diff --git a/packages/remix/package.json b/packages/remix/package.json index b48366671d16..4086b5ddab4b 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/remix", - "version": "7.29.0", + "version": "7.30.0", "description": "Official Sentry SDK for Remix", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/remix", @@ -15,19 +15,19 @@ "main": "build/cjs/index.server.js", "module": "build/esm/index.server.js", "browser": "build/esm/index.client.js", - "types": "build/types/index.server.d.ts", + "types": "build/types/index.types.d.ts", "publishConfig": { "access": "public" }, "dependencies": { "@sentry/cli": "2.2.0", - "@sentry/core": "7.29.0", - "@sentry/integrations": "7.29.0", - "@sentry/node": "7.29.0", - "@sentry/react": "7.29.0", - "@sentry/tracing": "7.29.0", - "@sentry/types": "7.29.0", - "@sentry/utils": "7.29.0", + "@sentry/core": "7.30.0", + "@sentry/integrations": "7.30.0", + "@sentry/node": "7.30.0", + "@sentry/react": "7.30.0", + "@sentry/tracing": "7.30.0", + "@sentry/types": "7.30.0", + "@sentry/utils": "7.30.0", "@sentry/webpack-plugin": "1.19.0", "tslib": "^1.9.3", "yargs": "^17.6.0" @@ -44,15 +44,15 @@ "react": "16.x || 17.x || 18.x" }, "scripts": { - "build": "run-p build:rollup build:types", + "build": "run-p build:transpile build:types", "build:dev": "run-s build", - "build:rollup": "rollup -c rollup.npm.config.js", + "build:transpile": "rollup -c rollup.npm.config.js", "build:types": "tsc -p tsconfig.types.json", - "build:watch": "run-p build:rollup:watch build:types:watch", + "build:watch": "run-p build:transpile:watch build:types:watch", "build:dev:watch": "run-s build:watch", - "build:rollup:watch": "rollup -c rollup.npm.config.js --watch", + "build:transpile:watch": "rollup -c rollup.npm.config.js --watch", "build:types:watch": "tsc -p tsconfig.types.json --watch", - "build:npm": "ts-node ../../scripts/prepack.ts && npm pack ./build", + "build:tarball": "ts-node ../../scripts/prepack.ts && npm pack ./build", "circularDepCheck": "madge --circular src/index.server.ts", "clean": "rimraf build coverage sentry-remix-*.tgz", "fix": "run-s fix:eslint fix:prettier", diff --git a/packages/remix/src/index.client.tsx b/packages/remix/src/index.client.tsx index 68e6c2027141..22d9ce73fe82 100644 --- a/packages/remix/src/index.client.tsx +++ b/packages/remix/src/index.client.tsx @@ -2,7 +2,7 @@ import { configureScope, init as reactInit, Integrations } from '@sentry/react'; import { buildMetadata } from './utils/metadata'; -import { RemixOptions } from './utils/remixOptions'; +import type { RemixOptions } from './utils/remixOptions'; export { remixRouterInstrumentation, withSentry } from './performance/client'; export { BrowserTracing } from '@sentry/tracing'; export * from '@sentry/react'; diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts index 2576c9a293f1..b2ad73866fde 100644 --- a/packages/remix/src/index.server.ts +++ b/packages/remix/src/index.server.ts @@ -4,7 +4,7 @@ import { logger } from '@sentry/utils'; import { instrumentServer } from './utils/instrumentServer'; import { buildMetadata } from './utils/metadata'; -import { RemixOptions } from './utils/remixOptions'; +import type { RemixOptions } from './utils/remixOptions'; export { ErrorBoundary, withErrorBoundary } from '@sentry/react'; export { remixRouterInstrumentation, withSentry } from './performance/client'; diff --git a/packages/remix/src/index.types.ts b/packages/remix/src/index.types.ts new file mode 100644 index 000000000000..079f524e3b61 --- /dev/null +++ b/packages/remix/src/index.types.ts @@ -0,0 +1,30 @@ +/* eslint-disable import/export */ + +// We export everything from both the client part of the SDK and from the server part. Some of the exports collide, +// which is not allowed, unless we redifine the colliding exports in this file - which we do below. +export * from './index.client'; +export * from './index.server'; + +import type { Integration, StackParser } from '@sentry/types'; + +import * as clientSdk from './index.client'; +import * as serverSdk from './index.server'; +import type { RemixOptions } from './utils/remixOptions'; + +/** Initializes Sentry Remix SDK */ +export declare function init(options: RemixOptions): void; + +// We export a merged Integrations object so that users can (at least typing-wise) use all integrations everywhere. +export const Integrations = { ...clientSdk.Integrations, ...serverSdk.Integrations }; + +export declare const defaultIntegrations: Integration[]; +export declare const defaultStackParser: StackParser; + +// This variable is not a runtime variable but just a type to tell typescript that the methods below can either come +// from the client SDK or from the server SDK. TypeScript is smart enough to understand that these resolve to the same +// methods from `@sentry/core`. +declare const runtime: 'client' | 'server'; + +export const close = runtime === 'client' ? clientSdk.close : serverSdk.close; +export const flush = runtime === 'client' ? clientSdk.flush : serverSdk.flush; +export const lastEventId = runtime === 'client' ? clientSdk.lastEventId : serverSdk.lastEventId; diff --git a/packages/remix/src/performance/client.tsx b/packages/remix/src/performance/client.tsx index 2308c64d0727..0f88a67c2592 100644 --- a/packages/remix/src/performance/client.tsx +++ b/packages/remix/src/performance/client.tsx @@ -1,5 +1,6 @@ -import { ErrorBoundaryProps, WINDOW, withErrorBoundary } from '@sentry/react'; -import { Transaction, TransactionContext } from '@sentry/types'; +import type { ErrorBoundaryProps} from '@sentry/react'; +import { WINDOW, withErrorBoundary } from '@sentry/react'; +import type { Transaction, TransactionContext } from '@sentry/types'; import { logger } from '@sentry/utils'; import * as React from 'react'; diff --git a/packages/remix/src/utils/instrumentServer.ts b/packages/remix/src/utils/instrumentServer.ts index 4aee67a174b4..d80158a7b4bd 100644 --- a/packages/remix/src/utils/instrumentServer.ts +++ b/packages/remix/src/utils/instrumentServer.ts @@ -1,7 +1,8 @@ /* eslint-disable max-lines */ -import { captureException, getCurrentHub, Hub } from '@sentry/node'; +import type { Hub } from '@sentry/node'; +import { captureException, getCurrentHub } from '@sentry/node'; import { getActiveTransaction, hasTracingEnabled } from '@sentry/tracing'; -import { Transaction, TransactionSource, WrappedFunction } from '@sentry/types'; +import type { Transaction, TransactionSource, WrappedFunction } from '@sentry/types'; import { addExceptionMechanism, baggageHeaderToDynamicSamplingContext, @@ -14,7 +15,7 @@ import { } from '@sentry/utils'; import * as domain from 'domain'; -import { +import type { AppData, CreateRequestHandlerFunction, DataFunction, diff --git a/packages/remix/src/utils/metadata.ts b/packages/remix/src/utils/metadata.ts index 243cdcc3826f..382eff9c3e26 100644 --- a/packages/remix/src/utils/metadata.ts +++ b/packages/remix/src/utils/metadata.ts @@ -1,5 +1,5 @@ import { SDK_VERSION } from '@sentry/core'; -import { Options, SdkInfo } from '@sentry/types'; +import type { Options, SdkInfo } from '@sentry/types'; const PACKAGE_NAME_PREFIX = 'npm:@sentry/'; diff --git a/packages/remix/src/utils/remixOptions.ts b/packages/remix/src/utils/remixOptions.ts index 9534ed57de3b..4a1fe13e18e1 100644 --- a/packages/remix/src/utils/remixOptions.ts +++ b/packages/remix/src/utils/remixOptions.ts @@ -1,5 +1,5 @@ -import { NodeOptions } from '@sentry/node'; -import { BrowserOptions } from '@sentry/react'; -import { Options } from '@sentry/types'; +import type { NodeOptions } from '@sentry/node'; +import type { BrowserOptions } from '@sentry/react'; +import type { Options } from '@sentry/types'; export type RemixOptions = Options | BrowserOptions | NodeOptions; diff --git a/packages/remix/src/utils/serverAdapters/express.ts b/packages/remix/src/utils/serverAdapters/express.ts index 136246e60dde..e2484e4a6d6d 100644 --- a/packages/remix/src/utils/serverAdapters/express.ts +++ b/packages/remix/src/utils/serverAdapters/express.ts @@ -1,7 +1,7 @@ import { getCurrentHub } from '@sentry/core'; import { flush } from '@sentry/node'; import { hasTracingEnabled } from '@sentry/tracing'; -import { Transaction } from '@sentry/types'; +import type { Transaction } from '@sentry/types'; import { extractRequestData, isString, logger } from '@sentry/utils'; import { cwd } from 'process'; @@ -12,7 +12,7 @@ import { isRequestHandlerWrapped, startRequestHandlerTransaction, } from '../instrumentServer'; -import { +import type { ExpressCreateRequestHandler, ExpressCreateRequestHandlerOptions, ExpressNextFunction, diff --git a/packages/remix/src/utils/types.ts b/packages/remix/src/utils/types.ts index f6fe6e8d30e9..225a3ea1ad56 100644 --- a/packages/remix/src/utils/types.ts +++ b/packages/remix/src/utils/types.ts @@ -3,7 +3,7 @@ // Types vendored from @remix-run/server-runtime@1.6.0: // https://github.com/remix-run/remix/blob/f3691d51027b93caa3fd2cdfe146d7b62a6eb8f2/packages/remix-server-runtime/server.ts import type * as Express from 'express'; -import { Agent } from 'https'; +import type { Agent } from 'https'; import type { ComponentType } from 'react'; export type RemixRequestState = { diff --git a/packages/remix/src/utils/web-fetch.ts b/packages/remix/src/utils/web-fetch.ts index 72ce4177827b..853eac775c84 100644 --- a/packages/remix/src/utils/web-fetch.ts +++ b/packages/remix/src/utils/web-fetch.ts @@ -4,7 +4,7 @@ import { logger } from '@sentry/utils'; import { getClientIPAddress } from './getIpAddress'; -import { RemixRequest } from './types'; +import type { RemixRequest } from './types'; /* * Symbol extractor utility to be able to access internal fields of Remix requests. diff --git a/packages/remix/test/integration/package.json b/packages/remix/test/integration/package.json index 724ee95c1d61..102a7844b1f9 100644 --- a/packages/remix/test/integration/package.json +++ b/packages/remix/test/integration/package.json @@ -7,16 +7,16 @@ "start": "remix-serve build" }, "dependencies": { - "@remix-run/express": "^1.6.5", - "@remix-run/node": "^1.6.5", - "@remix-run/react": "^1.6.5", - "@remix-run/serve": "^1.6.5", + "@remix-run/express": "1.9.0", + "@remix-run/node": "1.9.0", + "@remix-run/react": "1.9.0", + "@remix-run/serve": "1.9.0", "@sentry/remix": "file:../..", "react": "^17.0.2", "react-dom": "^17.0.2" }, "devDependencies": { - "@remix-run/dev": "^1.6.5", + "@remix-run/dev": "1.9.0", "@types/react": "^17.0.47", "@types/react-dom": "^17.0.17", "nock": "^13.1.0", diff --git a/packages/replay/.eslintignore b/packages/replay/.eslintignore index 0a749745f94c..c76c6c2d64d1 100644 --- a/packages/replay/.eslintignore +++ b/packages/replay/.eslintignore @@ -3,3 +3,4 @@ build/ demo/build/ # TODO: Check if we can re-introduce linting in demo demo +metrics diff --git a/packages/replay/.eslintrc.js b/packages/replay/.eslintrc.js index 9df1f4dffa5f..85d8dbf13a1e 100644 --- a/packages/replay/.eslintrc.js +++ b/packages/replay/.eslintrc.js @@ -23,12 +23,8 @@ module.exports = { { files: ['*.ts', '*.tsx', '*.d.ts'], rules: { - // TODO (high-prio): Re-enable this after migration - '@typescript-eslint/explicit-member-accessibility': 'off', - // TODO (high-prio): Re-enable this after migration + // Since we target only es6 here, we can leave this off '@sentry-internal/sdk/no-async-await': 'off', - // TODO (medium-prio): Re-enable this after migration - 'jsdoc/require-jsdoc': 'off', }, }, { diff --git a/packages/replay/demo/yarn.lock b/packages/replay/demo/yarn.lock index 4bc70e61d74f..3e9509ff43c9 100644 --- a/packages/replay/demo/yarn.lock +++ b/packages/replay/demo/yarn.lock @@ -1493,13 +1493,13 @@ "@sentry/utils" "7.1.1" tslib "^1.9.3" -"@sentry/core@7.24.0": - version "7.24.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.24.0.tgz#e856a071c702279854d6e5221957d61cd48036cc" - integrity sha512-QVRtmnaWEI0/MHIfBozgsMfh+7WU6OfpvUd72x1Dpk3Zk6Zs7Hqq0YfxfeBd7ApjNjGogPl1beaHcHAHlr3IyA== +"@sentry/core@7.28.1": + version "7.28.1" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.28.1.tgz#c712ce17469b18b01606108817be24a99ed2116e" + integrity sha512-7wvnuvn/mrAfcugWoCG/3pqDIrUgH5t+HisMJMGw0h9Tc33KqrmqMDCQVvjlrr2pWrw/vuUCFdm8CbUHJ832oQ== dependencies: - "@sentry/types" "7.24.0" - "@sentry/utils" "7.24.0" + "@sentry/types" "7.28.1" + "@sentry/utils" "7.28.1" tslib "^1.9.3" "@sentry/hub@7.1.1": @@ -1512,12 +1512,11 @@ tslib "^1.9.3" "@sentry/replay@file:..": - version "7.24.0" + version "7.28.1" dependencies: - "@sentry/core" "7.24.0" - "@sentry/types" "7.24.0" - "@sentry/utils" "7.24.0" - lodash.debounce "^4.0.8" + "@sentry/core" "7.28.1" + "@sentry/types" "7.28.1" + "@sentry/utils" "7.28.1" "@sentry/tracing@^7.1.1": version "7.1.1" @@ -1534,10 +1533,10 @@ resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.1.1.tgz#63aa68e7be36d63cc305d01af9119a4cdb186ae3" integrity sha512-5N1UMd2SqvUXprcIUMyDEju3H9lJY2oWfWQBGo0lG6Amn/lGAPAYlchg+4vQCLutDQMyd8K9zPwcbKn4u6gHdw== -"@sentry/types@7.24.0": - version "7.24.0" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.24.0.tgz#1acc841efe7bd56fc3eb647a613dba92631e8413" - integrity sha512-Xs4r9esBPieJUA6cGmMqfSQiinILdlhScjM+NqDSzxOo8+LRCJzckTLhUttBGVlaAoa4hjCEsfkHA1tVV1DycA== +"@sentry/types@7.28.1": + version "7.28.1" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.28.1.tgz#9018b4c152b475de9bedd267237393d3c9b1253d" + integrity sha512-DvSplMVrVEmOzR2M161V5+B8Up3vR71xMqJOpWTzE9TqtFJRGPtqT/5OBsNJJw1+/j2ssMcnKwbEo9Q2EGeS6g== "@sentry/utils@7.1.1": version "7.1.1" @@ -1547,12 +1546,12 @@ "@sentry/types" "7.1.1" tslib "^1.9.3" -"@sentry/utils@7.24.0": - version "7.24.0" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.24.0.tgz#73f100dc8942e73353473eaf3168e5052e2f45be" - integrity sha512-baaRDhHWHTyhmR6V8YKSo0NvN+D17pIKRDmb2vpWHVpTjobKCivNBLRoy3VhnIMS/24XyZnL028QLwkUNLg1Ug== +"@sentry/utils@7.28.1": + version "7.28.1" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.28.1.tgz#0a7b6aa4b09e91e4d1aded2a8c8dbaf818cee96e" + integrity sha512-75/jzLUO9HH09iC9TslNimGbxOP3jgn89P+q7uR+rp2fJfRExHVeKJZQdK0Ij4/SmE7TJ3Uh2r154N0INZEx1g== dependencies: - "@sentry/types" "7.24.0" + "@sentry/types" "7.28.1" tslib "^1.9.3" "@sinonjs/commons@^1.7.0": @@ -5548,9 +5547,9 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" json5@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== dependencies: minimist "^1.2.0" @@ -5879,12 +5878,7 @@ minimatch@^5.0.1: dependencies: brace-expansion "^2.0.1" -minimist@^1.1.1, minimist@^1.2.5: - version "1.2.6" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" - integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== - -minimist@^1.2.0: +minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: version "1.2.7" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== diff --git a/packages/replay/jest.setup.ts b/packages/replay/jest.setup.ts index 9d165d8c428e..26f9ea346a0d 100644 --- a/packages/replay/jest.setup.ts +++ b/packages/replay/jest.setup.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ import { getCurrentHub } from '@sentry/core'; -import { ReplayRecordingData,Transport } from '@sentry/types'; +import type { ReplayRecordingData, Transport } from '@sentry/types'; import type { ReplayContainer, Session } from './src/types'; @@ -34,7 +34,7 @@ type SentReplayExpected = { replayEventPayload?: ReplayEventPayload; recordingHeader?: RecordingHeader; recordingPayloadHeader?: RecordingPayloadHeader; - events?: ReplayRecordingData; + recordingData?: ReplayRecordingData; }; // eslint-disable-next-line @typescript-eslint/explicit-function-return-type @@ -79,7 +79,7 @@ function checkCallForSentReplay( const [[replayEventHeader, replayEventPayload], [recordingHeader, recordingPayload] = []] = envelopeItems; // @ts-ignore recordingPayload is always a string in our tests - const [recordingPayloadHeader, events] = recordingPayload?.split('\n') || []; + const [recordingPayloadHeader, recordingData] = recordingPayload?.split('\n') || []; const actualObj: Required = { // @ts-ignore Custom envelope @@ -91,7 +91,7 @@ function checkCallForSentReplay( // @ts-ignore Custom envelope recordingHeader: recordingHeader, recordingPayloadHeader: recordingPayloadHeader && JSON.parse(recordingPayloadHeader), - events, + recordingData, }; const isObjectContaining = expected && 'sample' in expected && 'inverse' in expected; diff --git a/packages/replay/metrics/.eslintrc.cjs b/packages/replay/metrics/.eslintrc.cjs new file mode 100644 index 000000000000..9f90433a8fa8 --- /dev/null +++ b/packages/replay/metrics/.eslintrc.cjs @@ -0,0 +1,14 @@ +module.exports = { + extends: ['../.eslintrc.js'], + ignorePatterns: ['test-apps'], + overrides: [ + { + files: ['*.ts'], + rules: { + 'no-console': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + 'import/no-unresolved': 'off', + }, + }, + ], +}; diff --git a/packages/replay/metrics/.gitignore b/packages/replay/metrics/.gitignore new file mode 100644 index 000000000000..505d701f0e12 --- /dev/null +++ b/packages/replay/metrics/.gitignore @@ -0,0 +1 @@ +out diff --git a/packages/replay/metrics/README.md b/packages/replay/metrics/README.md new file mode 100644 index 000000000000..6877d491c1b2 --- /dev/null +++ b/packages/replay/metrics/README.md @@ -0,0 +1,11 @@ +# Replay performance metrics + +Evaluates Replay impact on website performance by running a web app in Chromium via Playwright and collecting various metrics. + +The general idea is to run a web app without Sentry Replay and then run the same app again with Sentry and another one with Sentry+Replay included. +For the three scenarios, we collect some metrics (CPU, memory, vitals) and later compare them and post as a comment in a PR. +Changes in the metrics, compared to previous runs from the main branch, should be evaluated on case-by-case basis when preparing and reviewing the PR. + +## Resources + +* https://github.com/addyosmani/puppeteer-webperf diff --git a/packages/replay/metrics/configs/README.md b/packages/replay/metrics/configs/README.md new file mode 100644 index 000000000000..cb9724ba4619 --- /dev/null +++ b/packages/replay/metrics/configs/README.md @@ -0,0 +1,4 @@ +# Replay metrics configuration & entrypoints (scripts) + +* [dev](dev) contains scripts launched during local development +* [ci](ci) contains scripts launched in CI diff --git a/packages/replay/metrics/configs/ci/collect.ts b/packages/replay/metrics/configs/ci/collect.ts new file mode 100644 index 000000000000..67add4feb6f2 --- /dev/null +++ b/packages/replay/metrics/configs/ci/collect.ts @@ -0,0 +1,54 @@ +import { Metrics, MetricsCollector } from '../../src/collector.js'; +import { MetricsStats, NumberProvider } from '../../src/results/metrics-stats.js'; +import { JankTestScenario } from '../../src/scenarios.js'; +import { printStats } from '../../src/util/console.js'; +import { latestResultFile } from './env.js'; + +function checkStdDev(results: Metrics[], name: string, provider: NumberProvider, max: number): boolean { + const value = MetricsStats.stddev(results, provider); + if (value == undefined) { + console.warn(`✗ | Discarding results because StandardDeviation(${name}) is undefined`); + return false; + } else if (value > max) { + console.warn(`✗ | Discarding results because StandardDeviation(${name}) is larger than ${max}. Actual value: ${value}`); + return false; + } else { + console.log(`✓ | StandardDeviation(${name}) is ${value} (<= ${max})`) + } + return true; +} + +const collector = new MetricsCollector({ headless: true, cpuThrottling: 2 }); +const result = await collector.execute({ + name: 'jank', + scenarios: [ + new JankTestScenario('index.html'), + new JankTestScenario('with-sentry.html'), + new JankTestScenario('with-replay.html'), + ], + runs: 10, + tries: 10, + async shouldAccept(results: Metrics[]): Promise { + await printStats(results); + + if (!checkStdDev(results, 'lcp', MetricsStats.lcp, 50) + || !checkStdDev(results, 'cls', MetricsStats.cls, 0.1) + || !checkStdDev(results, 'cpu', MetricsStats.cpu, 1) + || !checkStdDev(results, 'memory-mean', MetricsStats.memoryMean, 1000 * 1024) + || !checkStdDev(results, 'memory-max', MetricsStats.memoryMax, 1000 * 1024)) { + return false; + } + + const cpuUsage = MetricsStats.mean(results, MetricsStats.cpu)!; + if (cpuUsage > 0.85) { + // Note: complexity on the "JankTest" is defined by the `minimum = ...,` setting in app.js - specifying the number of animated elements. + console.warn(`✗ | Discarding results because CPU usage is too high and may be inaccurate: ${(cpuUsage * 100).toFixed(2)} %.`, + 'Consider simplifying the scenario or changing the CPU throttling factor.'); + return false; + } + + return true; + }, +}); + +result.writeToFile(latestResultFile); diff --git a/packages/replay/metrics/configs/ci/env.ts b/packages/replay/metrics/configs/ci/env.ts new file mode 100644 index 000000000000..c41e4bcdf6c3 --- /dev/null +++ b/packages/replay/metrics/configs/ci/env.ts @@ -0,0 +1,4 @@ +export const previousResultsDir = 'out/previous-results'; +export const baselineResultsDir = 'out/baseline-results'; +export const latestResultFile = 'out/latest-result.json'; +export const artifactName = 'replay-sdk-metrics' diff --git a/packages/replay/metrics/configs/ci/process.ts b/packages/replay/metrics/configs/ci/process.ts new file mode 100644 index 000000000000..cf23744e4eab --- /dev/null +++ b/packages/replay/metrics/configs/ci/process.ts @@ -0,0 +1,44 @@ +import path from 'path'; + +import { ResultsAnalyzer } from '../../src/results/analyzer.js'; +import { PrCommentBuilder } from '../../src/results/pr-comment.js'; +import { Result } from '../../src/results/result.js'; +import { ResultsSet } from '../../src/results/results-set.js'; +import { Git } from '../../src/util/git.js'; +import { GitHub } from '../../src/util/github.js'; +import { artifactName, baselineResultsDir, latestResultFile, previousResultsDir } from './env.js'; + +const latestResult = Result.readFromFile(latestResultFile); +const branch = await Git.branch; +const baseBranch = await Git.baseBranch; + +await GitHub.downloadPreviousArtifact(baseBranch, baselineResultsDir, artifactName); +await GitHub.downloadPreviousArtifact(branch, previousResultsDir, artifactName); + +GitHub.writeOutput('artifactName', artifactName) +GitHub.writeOutput('artifactPath', path.resolve(previousResultsDir)); + +const previousResults = new ResultsSet(previousResultsDir); + +const prComment = new PrCommentBuilder(); +if (baseBranch != branch) { + const baseResults = new ResultsSet(baselineResultsDir); + await prComment.addCurrentResult(await ResultsAnalyzer.analyze(latestResult, baseResults), 'Baseline'); + await prComment.addAdditionalResultsSet( + `Baseline results on branch: ${baseBranch}`, + // We skip the first one here because it's already included as `Baseline` column above in addCurrentResult(). + baseResults.items().slice(1, 10) + ); +} else { + await prComment.addCurrentResult(await ResultsAnalyzer.analyze(latestResult, previousResults), 'Previous'); +} + +await prComment.addAdditionalResultsSet( + `Previous results on branch: ${branch}`, + previousResults.items().slice(0, 10) +); + +await GitHub.addOrUpdateComment(prComment); + +// Copy the latest test run results to the archived result dir. +await previousResults.add(latestResultFile, true); diff --git a/packages/replay/metrics/configs/dev/collect.ts b/packages/replay/metrics/configs/dev/collect.ts new file mode 100644 index 000000000000..a159d7a4f7b1 --- /dev/null +++ b/packages/replay/metrics/configs/dev/collect.ts @@ -0,0 +1,30 @@ +import { Metrics, MetricsCollector } from '../../src/collector.js'; +import { MetricsStats } from '../../src/results/metrics-stats.js'; +import { JankTestScenario } from '../../src/scenarios.js'; +import { printStats } from '../../src/util/console.js'; +import { latestResultFile } from './env.js'; + +const collector = new MetricsCollector(); +const result = await collector.execute({ + name: 'dummy', + scenarios: [ + new JankTestScenario('index.html'), + new JankTestScenario('with-sentry.html'), + new JankTestScenario('with-replay.html'), + ], + runs: 1, + tries: 1, + async shouldAccept(results: Metrics[]): Promise { + printStats(results); + + const cpuUsage = MetricsStats.mean(results, MetricsStats.cpu)!; + if (cpuUsage > 0.9) { + console.error(`CPU usage too high to be accurate: ${(cpuUsage * 100).toFixed(2)} %.`, + 'Consider simplifying the scenario or changing the CPU throttling factor.'); + return false; + } + return true; + }, +}); + +result.writeToFile(latestResultFile); diff --git a/packages/replay/metrics/configs/dev/env.ts b/packages/replay/metrics/configs/dev/env.ts new file mode 100644 index 000000000000..c2168763ea6e --- /dev/null +++ b/packages/replay/metrics/configs/dev/env.ts @@ -0,0 +1,2 @@ +export const outDir = 'out/results-dev'; +export const latestResultFile = 'out/latest-result.json'; diff --git a/packages/replay/metrics/configs/dev/process.ts b/packages/replay/metrics/configs/dev/process.ts new file mode 100644 index 000000000000..096244b5c750 --- /dev/null +++ b/packages/replay/metrics/configs/dev/process.ts @@ -0,0 +1,13 @@ +import { ResultsAnalyzer } from '../../src/results/analyzer.js'; +import { Result } from '../../src/results/result.js'; +import { ResultsSet } from '../../src/results/results-set.js'; +import { printAnalysis } from '../../src/util/console.js'; +import { latestResultFile, outDir } from './env.js'; + +const resultsSet = new ResultsSet(outDir); +const latestResult = Result.readFromFile(latestResultFile); + +const analysis = await ResultsAnalyzer.analyze(latestResult, resultsSet); +printAnalysis(analysis); + +await resultsSet.add(latestResultFile, true); diff --git a/packages/replay/metrics/package.json b/packages/replay/metrics/package.json new file mode 100644 index 000000000000..c119f1003c8f --- /dev/null +++ b/packages/replay/metrics/package.json @@ -0,0 +1,32 @@ +{ + "private": true, + "name": "metrics", + "main": "index.js", + "author": "Sentry", + "license": "MIT", + "type": "module", + "scripts": { + "build": "tsc", + "deps": "yarn --cwd ../ build:bundle && yarn --cwd ../../tracing/ build:bundle", + "dev:collect": "ts-node-esm ./configs/dev/collect.ts", + "dev:process": "ts-node-esm ./configs/dev/process.ts", + "ci:collect": "ts-node-esm ./configs/ci/collect.ts", + "ci:process": "ts-node-esm ./configs/ci/process.ts" + }, + "dependencies": { + "@octokit/rest": "^19.0.5", + "@types/node": "^18.11.17", + "axios": "^1.2.2", + "extract-zip": "^2.0.1", + "filesize": "^10.0.6", + "p-timeout": "^6.0.0", + "playwright": "^1.29.1", + "playwright-core": "^1.29.1", + "simple-git": "^3.15.1", + "simple-statistics": "^7.8.0", + "typescript": "^4.9.4" + }, + "devDependencies": { + "ts-node": "^10.9.1" + } +} diff --git a/packages/replay/metrics/src/collector.ts b/packages/replay/metrics/src/collector.ts new file mode 100644 index 000000000000..d8673a8c4021 --- /dev/null +++ b/packages/replay/metrics/src/collector.ts @@ -0,0 +1,166 @@ +import pTimeout from 'p-timeout'; +import * as playwright from 'playwright'; + +import { CpuUsage, CpuUsageSampler, CpuUsageSerialized } from './perf/cpu.js'; +import { JsHeapUsage, JsHeapUsageSampler, JsHeapUsageSerialized } from './perf/memory.js'; +import { PerfMetricsSampler } from './perf/sampler.js'; +import { Result } from './results/result.js'; +import { Scenario, TestCase } from './scenarios.js'; +import { consoleGroup } from './util/console.js'; +import { WebVitals, WebVitalsCollector } from './vitals/index.js'; + +const networkConditions = 'Fast 3G'; + +// Same as puppeteer-core PredefinedNetworkConditions +const PredefinedNetworkConditions = Object.freeze({ + 'Slow 3G': { + download: ((500 * 1000) / 8) * 0.8, + upload: ((500 * 1000) / 8) * 0.8, + latency: 400 * 5, + connectionType: 'cellular3g', + }, + 'Fast 3G': { + download: ((1.6 * 1000 * 1000) / 8) * 0.9, + upload: ((750 * 1000) / 8) * 0.9, + latency: 150 * 3.75, + connectionType: 'cellular3g', + }, +}); + +export class Metrics { + constructor(public readonly vitals: WebVitals, public readonly cpu: CpuUsage, public readonly memory: JsHeapUsage) { } + + public static fromJSON(data: Partial<{ vitals: Partial, cpu: CpuUsageSerialized, memory: JsHeapUsageSerialized }>): Metrics { + return new Metrics( + WebVitals.fromJSON(data.vitals || {}), + CpuUsage.fromJSON(data.cpu || {}), + JsHeapUsage.fromJSON(data.memory || {}), + ); + } +} + +export interface MetricsCollectorOptions { + headless: boolean; + cpuThrottling: number; +} + +export class MetricsCollector { + private _options: MetricsCollectorOptions; + + constructor(options?: Partial) { + this._options = { + headless: false, + cpuThrottling: 4, + ...options + }; + } + + public async execute(testCase: TestCase): Promise { + console.log(`Executing test case ${testCase.name}`); + return consoleGroup(async () => { + const scenarioResults: Metrics[][] = []; + for (let s = 0; s < testCase.scenarios.length; s++) { + scenarioResults.push(await this._collect(testCase, s.toString(), testCase.scenarios[s])); + } + return new Result(testCase.name, this._options.cpuThrottling, networkConditions, scenarioResults); + }); + } + + private async _collect(testCase: TestCase, name: string, scenario: Scenario): Promise { + const label = `Scenario ${name} data collection (total ${testCase.runs} runs)`; + for (let try_ = 1; try_ <= testCase.tries; try_++) { + console.time(label); + const results: Metrics[] = []; + for (let run = 1; run <= testCase.runs; run++) { + const innerLabel = `Scenario ${name} data collection, run ${run}/${testCase.runs}`; + console.time(innerLabel); + try { + results.push(await this._run(scenario)); + } catch (e) { + console.warn(`${innerLabel} failed with ${e}`); + break; + } finally { + console.timeEnd(innerLabel); + } + } + console.timeEnd(label); + if ((results.length == testCase.runs) && await testCase.shouldAccept(results)) { + console.log(`Test case ${testCase.name}, scenario ${name} passed on try ${try_}/${testCase.tries}`); + return results; + } else if (try_ != testCase.tries) { + console.log(`Test case ${testCase.name} failed on try ${try_}/${testCase.tries}, retrying`); + } else { + throw `Test case ${testCase.name}, scenario ${name} failed after ${testCase.tries} tries.`; + } + } + // Unreachable code, if configured properly: + console.assert(testCase.tries >= 1); + return []; + } + + private async _run(scenario: Scenario): Promise { + const disposeCallbacks: (() => Promise)[] = []; + try { + return await pTimeout((async () => { + const browser = await playwright.chromium.launch({ + headless: this._options.headless, + }); + disposeCallbacks.push(() => browser.close()); + const page = await browser.newPage(); + disposeCallbacks.push(() => page.close()); + + const errorLogs: Array = []; + await page.on('console', message => { if (message.type() === 'error') errorLogs.push(message.text()) }); + await page.on('crash', _ => { errorLogs.push('Page crashed') }); + await page.on('pageerror', error => { errorLogs.push(`${error.name}: ${error.message}`) }); + + const cdp = await page.context().newCDPSession(page); + + // Simulate throttling. + await cdp.send('Network.emulateNetworkConditions', { + offline: false, + latency: PredefinedNetworkConditions[networkConditions].latency, + uploadThroughput: PredefinedNetworkConditions[networkConditions].upload, + downloadThroughput: PredefinedNetworkConditions[networkConditions].download, + }); + await cdp.send('Emulation.setCPUThrottlingRate', { rate: this._options.cpuThrottling }); + + // Collect CPU and memory info 10 times per second. + const perfSampler = await PerfMetricsSampler.create(cdp, 100); + disposeCallbacks.push(async () => perfSampler.stop()); + const cpuSampler = new CpuUsageSampler(perfSampler); + const memSampler = new JsHeapUsageSampler(perfSampler); + + const vitalsCollector = await WebVitalsCollector.create(page); + await scenario.run(browser, page); + + // NOTE: FID needs some interaction to actually show a value + const vitals = await vitalsCollector.collect(); + + if (errorLogs.length > 0) { + throw `Error logs in browser console:\n\t\t${errorLogs.join('\n\t\t')}`; + } + + return new Metrics(vitals, cpuSampler.getData(), memSampler.getData()); + })(), { + milliseconds: 60 * 1000, + }); + } finally { + console.log('Disposing of browser and resources'); + disposeCallbacks.reverse(); + const errors = []; + for (const cb of disposeCallbacks) { + try { + await cb(); + } catch (e) { + errors.push(e instanceof Error ? `${e.name}: ${e.message}` : `${e}`); + } + } + if (errors.length > 0) { + console.warn(`All disposose callbacks have finished. Errors: ${errors}`); + } else { + console.warn(`All disposose callbacks have finished.`); + } + } + } +} diff --git a/packages/replay/metrics/src/perf/cpu.ts b/packages/replay/metrics/src/perf/cpu.ts new file mode 100644 index 000000000000..cd64fd6038f5 --- /dev/null +++ b/packages/replay/metrics/src/perf/cpu.ts @@ -0,0 +1,54 @@ +import { JsonObject } from '../util/json.js'; +import { PerfMetrics, PerfMetricsSampler, TimeBasedMap } from './sampler.js'; + +export { CpuUsageSampler, CpuUsage } + +export type CpuUsageSerialized = Partial<{ snapshots: JsonObject, average: number }>; + +class CpuUsage { + constructor(public snapshots: TimeBasedMap, public average: number) { }; + + public static fromJSON(data: CpuUsageSerialized): CpuUsage { + return new CpuUsage( + TimeBasedMap.fromJSON(data.snapshots || {}), + data.average as number, + ); + } +} + +class MetricsDataPoint { + constructor(public timestamp: number, public activeTime: number) { }; +} + +class CpuUsageSampler { + private _snapshots: TimeBasedMap = new TimeBasedMap(); + private _average: number = 0; + private _initial?: MetricsDataPoint = undefined; + private _startTime!: number; + private _lastTimestamp!: number; + private _cumulativeActiveTime!: number; + + public constructor(sampler: PerfMetricsSampler) { + sampler.subscribe(this._collect.bind(this)); + } + + public getData(): CpuUsage { + return new CpuUsage(this._snapshots, this._average); + } + + private async _collect(metrics: PerfMetrics): Promise { + const data = new MetricsDataPoint(metrics.Timestamp, metrics.Duration); + if (this._initial == undefined) { + this._initial = data; + this._startTime = data.timestamp; + } else { + const frameDuration = data.timestamp - this._lastTimestamp; + const usage = frameDuration == 0 ? 0 : (data.activeTime - this._cumulativeActiveTime) / frameDuration; + + this._snapshots.set(data.timestamp, usage); + this._average = data.activeTime / (data.timestamp - this._startTime); + } + this._lastTimestamp = data.timestamp; + this._cumulativeActiveTime = data.activeTime; + } +} diff --git a/packages/replay/metrics/src/perf/memory.ts b/packages/replay/metrics/src/perf/memory.ts new file mode 100644 index 000000000000..97ad3a490e04 --- /dev/null +++ b/packages/replay/metrics/src/perf/memory.ts @@ -0,0 +1,30 @@ +import { JsonObject } from '../util/json.js'; +import { PerfMetrics, PerfMetricsSampler, TimeBasedMap } from './sampler.js'; + +export { JsHeapUsageSampler, JsHeapUsage } + +export type JsHeapUsageSerialized = Partial<{ snapshots: JsonObject }>; + +class JsHeapUsage { + public constructor(public snapshots: TimeBasedMap) { } + + public static fromJSON(data: JsHeapUsageSerialized): JsHeapUsage { + return new JsHeapUsage(TimeBasedMap.fromJSON(data.snapshots || {})); + } +} + +class JsHeapUsageSampler { + private _snapshots: TimeBasedMap = new TimeBasedMap(); + + public constructor(sampler: PerfMetricsSampler) { + sampler.subscribe(this._collect.bind(this)); + } + + public getData(): JsHeapUsage { + return new JsHeapUsage(this._snapshots); + } + + private async _collect(metrics: PerfMetrics): Promise { + this._snapshots.set(metrics.Timestamp, metrics.JSHeapUsedSize!); + } +} diff --git a/packages/replay/metrics/src/perf/sampler.ts b/packages/replay/metrics/src/perf/sampler.ts new file mode 100644 index 000000000000..b4dd26013cd5 --- /dev/null +++ b/packages/replay/metrics/src/perf/sampler.ts @@ -0,0 +1,85 @@ +import * as playwright from 'playwright'; +import { Protocol } from 'playwright-core/types/protocol'; + +import { JsonObject } from '../util/json'; + +export type PerfMetricsConsumer = (metrics: PerfMetrics) => Promise; +export type TimestampSeconds = number; + +export class TimeBasedMap extends Map { + public static fromJSON(entries: JsonObject): TimeBasedMap { + const result = new TimeBasedMap(); + // eslint-disable-next-line guard-for-in + for (const key in entries) { + result.set(parseFloat(key), entries[key]); + } + return result; + } + + public toJSON(): JsonObject { + return Object.fromEntries(this.entries()); + } +} + +export class PerfMetrics { + constructor(private _metrics: Protocol.Performance.Metric[]) { } + + private _find(name: string): number { + return this._metrics.find((metric) => metric.name == name)!.value; + } + + public get Timestamp(): number { + return this._find('Timestamp'); + } + + public get Duration(): number { + return this._find('TaskDuration'); + } + + public get JSHeapUsedSize(): number { + return this._find('JSHeapUsedSize'); + } +} + +export class PerfMetricsSampler { + private _consumers: PerfMetricsConsumer[] = []; + private _timer!: NodeJS.Timer; + private _errorPrinted: boolean = false; + + private constructor(private _cdp: playwright.CDPSession) { } + + public static async create(cdp: playwright.CDPSession, interval: number): Promise { + const self = new PerfMetricsSampler(cdp); + await cdp.send('Performance.enable', { timeDomain: 'timeTicks' }) + + // collect first sample immediately + self._collectSample(); + + // and set up automatic collection in the given interval + self._timer = setInterval(self._collectSample.bind(self), interval); + + return self; + } + + public subscribe(consumer: PerfMetricsConsumer): void { + this._consumers.push(consumer); + } + + public stop(): void { + clearInterval(this._timer); + } + + private _collectSample(): void { + this._cdp.send('Performance.getMetrics').then(response => { + const metrics = new PerfMetrics(response.metrics); + this._consumers.forEach(cb => cb(metrics).catch(console.error)); + }, (e) => { + // This happens if the browser closed unexpectedly. No reason to try again. + if (!this._errorPrinted) { + this._errorPrinted = true; + console.log(e); + this.stop(); + } + }); + } +} diff --git a/packages/replay/metrics/src/results/analyzer.ts b/packages/replay/metrics/src/results/analyzer.ts new file mode 100644 index 000000000000..bdc2ee77864e --- /dev/null +++ b/packages/replay/metrics/src/results/analyzer.ts @@ -0,0 +1,147 @@ +import { filesize } from 'filesize'; + +import { GitHash } from '../util/git.js'; +import { JsonStringify } from '../util/json.js'; +import { AnalyticsFunction, MetricsStats, NumberProvider } from './metrics-stats.js'; +import { Result } from './result.js'; +import { ResultsSet } from './results-set.js'; + +// Compares latest result to previous/baseline results and produces the needed info. +export class ResultsAnalyzer { + private constructor(private _result: Result) { } + + public static async analyze(currentResult: Result, baselineResults?: ResultsSet): Promise { + const items = new ResultsAnalyzer(currentResult)._collect(); + + const baseline = baselineResults?.find( + (other) => other.cpuThrottling == currentResult.cpuThrottling + && other.name == currentResult.name + && other.networkConditions == currentResult.networkConditions + && JsonStringify(other) != JsonStringify(currentResult)); + + let otherHash: GitHash | undefined + if (baseline != undefined) { + otherHash = baseline[0]; + const baseItems = new ResultsAnalyzer(baseline[1])._collect(); + // update items with baseline results + for (const base of baseItems) { + for (const item of items) { + if (item.metric == base.metric) { + item.others = base.values; + } + } + } + } + + return { + items: items, + otherHash: otherHash, + }; + } + + private _collect(): AnalyzerItem[] { + const items = new Array(); + + const scenarioResults = this._result.scenarioResults; + + const pushIfDefined = function (metric: AnalyzerItemMetric, unit: AnalyzerItemUnit, source: NumberProvider, fn: AnalyticsFunction): void { + const values = scenarioResults.map(items => fn(items, source)); + // only push if at least one value is defined + if (values.findIndex(v => v != undefined) >= 0) { + items.push({ + metric: metric, + values: new AnalyzerItemNumberValues(unit, values) + }); + } + } + + pushIfDefined(AnalyzerItemMetric.lcp, AnalyzerItemUnit.ms, MetricsStats.lcp, MetricsStats.mean); + pushIfDefined(AnalyzerItemMetric.cls, AnalyzerItemUnit.ms, MetricsStats.cls, MetricsStats.mean); + pushIfDefined(AnalyzerItemMetric.cpu, AnalyzerItemUnit.ratio, MetricsStats.cpu, MetricsStats.mean); + pushIfDefined(AnalyzerItemMetric.memoryAvg, AnalyzerItemUnit.bytes, MetricsStats.memoryMean, MetricsStats.mean); + pushIfDefined(AnalyzerItemMetric.memoryMax, AnalyzerItemUnit.bytes, MetricsStats.memoryMax, MetricsStats.max); + + return items; + } +} + +export enum AnalyzerItemUnit { + ms, + ratio, // 1.0 == 100 % + bytes, +} + +export interface AnalyzerItemValues { + value(index: number): string; + diff(aIndex: number, bIndex: number): string; + percent(aIndex: number, bIndex: number): string; +} + +const AnalyzerItemValueNotAvailable = 'n/a'; + +class AnalyzerItemNumberValues implements AnalyzerItemValues { + constructor(private _unit: AnalyzerItemUnit, private _values: (number | undefined)[]) { } + + private _has(index: number): boolean { + return index >= 0 && index < this._values.length && this._values[index] != undefined; + } + + private _get(index: number): number { + return this._values[index]!; + } + + public value(index: number): string { + if (!this._has(index)) return AnalyzerItemValueNotAvailable; + return this._withUnit(this._get(index)); + } + + public diff(aIndex: number, bIndex: number): string { + if (!this._has(aIndex) || !this._has(bIndex)) return AnalyzerItemValueNotAvailable; + const diff = this._get(bIndex) - this._get(aIndex); + const str = this._withUnit(diff, true); + return diff > 0 ? `+${str}` : str; + } + + public percent(aIndex: number, bIndex: number): string { + if (!this._has(aIndex) || !this._has(bIndex) || this._get(aIndex) == 0.0) return AnalyzerItemValueNotAvailable; + const percent = this._get(bIndex) / this._get(aIndex) * 100 - 100; + const str = `${percent.toFixed(2)} %`; + return percent > 0 ? `+${str}` : str; + } + + private _withUnit(value: number, isDiff: boolean = false): string { + switch (this._unit) { + case AnalyzerItemUnit.bytes: + return filesize(value) as string; + case AnalyzerItemUnit.ratio: + return `${(value * 100).toFixed(2)} ${isDiff ? 'pp' : '%'}`; + default: + return `${value.toFixed(2)} ${AnalyzerItemUnit[this._unit]}`; + } + } +} + +export enum AnalyzerItemMetric { + lcp, + cls, + cpu, + memoryAvg, + memoryMax, +} + +export interface AnalyzerItem { + metric: AnalyzerItemMetric; + + // Current (latest) result. + values: AnalyzerItemValues; + + // Previous or baseline results, depending on the context. + others?: AnalyzerItemValues; +} + +export interface Analysis { + items: AnalyzerItem[]; + + // Commit hash that the the previous or baseline (depending on the context) result was collected for. + otherHash?: GitHash; +} diff --git a/packages/replay/metrics/src/results/metrics-stats.ts b/packages/replay/metrics/src/results/metrics-stats.ts new file mode 100644 index 000000000000..fd23d032c11d --- /dev/null +++ b/packages/replay/metrics/src/results/metrics-stats.ts @@ -0,0 +1,44 @@ +import * as ss from 'simple-statistics' + +import { Metrics } from '../collector'; + +export type NumberProvider = (metrics: Metrics) => number | undefined; +export type AnalyticsFunction = (items: Metrics[], dataProvider: NumberProvider) => number | undefined; + +export class MetricsStats { + static lcp: NumberProvider = metrics => metrics.vitals.lcp; + static cls: NumberProvider = metrics => metrics.vitals.cls; + static cpu: NumberProvider = metrics => metrics.cpu.average; + static memoryMean: NumberProvider = metrics => ss.mean(Array.from(metrics.memory.snapshots.values())); + static memoryMax: NumberProvider = metrics => ss.max(Array.from(metrics.memory.snapshots.values())); + + static mean: AnalyticsFunction = (items: Metrics[], dataProvider: NumberProvider) => { + const numbers = MetricsStats._filteredValues(MetricsStats._collect(items, dataProvider)); + return numbers.length > 0 ? ss.mean(numbers) : undefined; + } + + static max: AnalyticsFunction = (items: Metrics[], dataProvider: NumberProvider) => { + const numbers = MetricsStats._filteredValues(MetricsStats._collect(items, dataProvider)); + return numbers.length > 0 ? ss.max(numbers) : undefined; + } + + static stddev: AnalyticsFunction = (items: Metrics[], dataProvider: NumberProvider) => { + const numbers = MetricsStats._filteredValues(MetricsStats._collect(items, dataProvider)); + return numbers.length > 0 ? ss.standardDeviation(numbers) : undefined; + } + + private static _collect(items: Metrics[], dataProvider: NumberProvider): number[] { + return items.map(dataProvider).filter(v => v != undefined && !Number.isNaN(v)) as number[]; + } + + // See https://en.wikipedia.org/wiki/Interquartile_range#Outliers for details on filtering. + private static _filteredValues(numbers: number[]): number[] { + numbers.sort((a, b) => a - b) + + const q1 = ss.quantileSorted(numbers, 0.25); + const q3 = ss.quantileSorted(numbers, 0.75); + const iqr = q3 - q1 + + return numbers.filter(num => num >= (q1 - 1.5 * iqr) && num <= (q3 + 1.5 * iqr)) + } +} diff --git a/packages/replay/metrics/src/results/pr-comment.ts b/packages/replay/metrics/src/results/pr-comment.ts new file mode 100644 index 000000000000..618c1aa8d76e --- /dev/null +++ b/packages/replay/metrics/src/results/pr-comment.ts @@ -0,0 +1,153 @@ +import { Git } from '../util/git.js'; +import { Analysis, AnalyzerItemMetric, AnalyzerItemValues, ResultsAnalyzer } from './analyzer.js'; +import { Result } from './result.js'; +import { ResultSetItem } from './results-set.js'; + +function trimIndent(str: string): string { + return str.trim().split('\n').map(s => s.trim()).join('\n'); +} + +function printableMetricName(metric: AnalyzerItemMetric): string { + switch (metric) { + case AnalyzerItemMetric.lcp: + return '
LCP'; + case AnalyzerItemMetric.cls: + return 'CLS'; + case AnalyzerItemMetric.cpu: + return 'CPU'; + case AnalyzerItemMetric.memoryAvg: + return 'JS heap avg'; + case AnalyzerItemMetric.memoryMax: + return 'JS heap max'; + default: + return AnalyzerItemMetric[metric]; + } +} + +export class PrCommentBuilder { + private _buffer: string = ''; + + public get title(): string { + return 'Replay SDK metrics :rocket:'; + } + + public get body(): string { + const now = new Date(); + return trimIndent(` + ${this._buffer} +
+
+ *) pp - percentage points - an absolute difference between two percentages.
+ Last updated: +
+ `); + } + + public async addCurrentResult(analysis: Analysis, otherName: string): Promise { + // Decides whether to print the "Other" for comparison depending on it being set in the input data. + const hasOther = analysis.otherHash != undefined; + const maybeOther = function (content: () => string): string { + return hasOther ? content() : ''; + } + + const currentHash = await Git.hash + + this._buffer += `

${this.title}

`; + if (!hasOther) { + this._buffer += `Latest data for: ${currentHash}`; + } + this._buffer += ` + + + + + ${maybeOther(() => ``)} + + + + + + ${maybeOther(() => ``)} + + + + + + + + `; + + const valueColumns = function (values: AnalyzerItemValues): string { + return ` + + + + + + + + `; + } + + for (const item of analysis.items) { + if (hasOther) { + this._buffer += ` + + + + + ` + } else { + this._buffer += ` + + + ${valueColumns(item.values)} + ` + } + } + + this._buffer += ` +
  Plain+Sentry+Replay
RevisionValueValueDiffRatioValueDiffRatio
${values.value(0)}${values.value(1)}${values.diff(0, 1)}${values.percent(0, 1)}${values.value(2)}${values.diff(1, 2)}${values.percent(1, 2)}
${printableMetricName(item.metric)}This PR ${currentHash} + ${valueColumns(item.values)} +
${otherName} ${analysis.otherHash} + ${valueColumns(item.others!)} +
${printableMetricName(item.metric)}
`; + } + + public async addAdditionalResultsSet(name: string, resultFiles: ResultSetItem[]): Promise { + if (resultFiles.length == 0) return; + + this._buffer += ` +
+

${name}

+ `; + + // Each `resultFile` will be printed as a single row - with metrics as table columns. + for (let i = 0; i < resultFiles.length; i++) { + const resultFile = resultFiles[i]; + // Load the file and "analyse" - collect stats we want to print. + const analysis = await ResultsAnalyzer.analyze(Result.readFromFile(resultFile.path)); + + if (i == 0) { + // Add table header + this._buffer += ''; + for (const item of analysis.items) { + this._buffer += ``; + } + this._buffer += ''; + } + + // Add table row + this._buffer += ``; + for (const item of analysis.items) { + // TODO maybe find a better way of showing this. After the change to multiple scenarios, this shows diff between "With Sentry" and "With Sentry + Replay" + this._buffer += ``; + } + this._buffer += ''; + } + + this._buffer += ` +
Revision${printableMetricName(item.metric)}
${resultFile.hash}${item.values.diff(1, 2)}
+
`; + } +} diff --git a/packages/replay/metrics/src/results/result.ts b/packages/replay/metrics/src/results/result.ts new file mode 100644 index 000000000000..ed4ca476b039 --- /dev/null +++ b/packages/replay/metrics/src/results/result.ts @@ -0,0 +1,32 @@ +import * as fs from 'fs'; +import path from 'path'; + +import { Metrics } from '../collector.js'; +import { JsonObject, JsonStringify } from '../util/json.js'; + +export class Result { + constructor( + public readonly name: string, public readonly cpuThrottling: number, + public readonly networkConditions: string, + public readonly scenarioResults: Metrics[][]) { } + + public static readFromFile(filePath: string): Result { + const json = fs.readFileSync(filePath, { encoding: 'utf-8' }); + const data = JSON.parse(json) as JsonObject; + return new Result( + data.name as string, + data.cpuThrottling as number, + data.networkConditions as string, + (data.scenarioResults as Partial[][] || []).map(list => list.map(Metrics.fromJSON.bind(Metrics))) + ); + } + + public writeToFile(filePath: string): void { + const dir = path.dirname(filePath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir); + } + const json = JsonStringify(this); + fs.writeFileSync(filePath, json); + } +} diff --git a/packages/replay/metrics/src/results/results-set.ts b/packages/replay/metrics/src/results/results-set.ts new file mode 100644 index 000000000000..ee38efd8a9f9 --- /dev/null +++ b/packages/replay/metrics/src/results/results-set.ts @@ -0,0 +1,87 @@ +import assert from 'assert'; +import * as fs from 'fs'; +import path from 'path'; + +import { Git, GitHash } from '../util/git.js'; +import { Result } from './result.js'; + +const delimiter = '-'; + +export class ResultSetItem { + public constructor(public path: string) { } + + public get name(): string { + return path.basename(this.path); + } + + public get number(): number { + return parseInt(this.parts[0]); + } + + public get hash(): GitHash { + return this.parts[1]; + } + + get parts(): string[] { + return path.basename(this.path).split(delimiter); + } +} + +/// Wraps a directory containing multiple (N--result.json) files. +/// The files are numbered from the most recently added one, to the oldest one. +export class ResultsSet { + public constructor(private _directory: string) { + if (!fs.existsSync(_directory)) { + fs.mkdirSync(_directory, { recursive: true }); + } + } + + public find(predicate: (value: Result) => boolean): [GitHash, Result] | undefined { + for (const item of this.items()) { + const result = Result.readFromFile(item.path); + if (predicate(result)) { + return [item.hash, result]; + } + } + return undefined; + } + + public items(): ResultSetItem[] { + return this._files().map((file) => { + return new ResultSetItem(path.join(this._directory, file.name)); + }).filter((item) => !isNaN(item.number)).sort((a, b) => a.number - b.number); + } + + public async add(newFile: string, onlyIfDifferent: boolean = false): Promise { + console.log(`Preparing to add ${newFile} to ${this._directory}`); + assert(fs.existsSync(newFile)); + + // Get the list of file sorted by the prefix number in the descending order (starting with the oldest files). + const files = this.items().sort((a, b) => b.number - a.number); + + if (onlyIfDifferent && files.length > 0) { + const latestFile = files[files.length - 1]; + if (fs.readFileSync(latestFile.path, { encoding: 'utf-8' }) == fs.readFileSync(newFile, { encoding: 'utf-8' })) { + console.log(`Skipping - it's already stored as ${latestFile.name}`); + return; + } + } + + // Rename all existing files, increasing the prefix + for (const file of files) { + const parts = file.name.split(delimiter); + parts[0] = (file.number + 1).toString(); + const newPath = path.join(this._directory, parts.join(delimiter)); + console.log(`Renaming ${file.path} to ${newPath}`); + fs.renameSync(file.path, newPath); + } + + const newName = `1${delimiter}${await Git.hash}${delimiter}result.json`; + console.log(`Adding ${newFile} to ${this._directory} as ${newName}`); + fs.copyFileSync(newFile, path.join(this._directory, newName)); + } + + private _files(): fs.Dirent[] { + return fs.readdirSync(this._directory, { withFileTypes: true }).filter((v) => v.isFile()) + } +} diff --git a/packages/replay/metrics/src/scenarios.ts b/packages/replay/metrics/src/scenarios.ts new file mode 100644 index 000000000000..86974272394d --- /dev/null +++ b/packages/replay/metrics/src/scenarios.ts @@ -0,0 +1,47 @@ +import assert from 'assert'; +import * as fs from 'fs'; +import path from 'path'; +import * as playwright from 'playwright'; + +import { Metrics } from './collector'; + +// A testing scenario we want to collect metrics for. +export interface Scenario { + run(browser: playwright.Browser, page: playwright.Page): Promise; +} + +// Two scenarios that are compared to each other. +export interface TestCase { + name: string; + scenarios: Scenario[]; + runs: number; + tries: number; + + // Test function that will be executed and given a scenarios result set with exactly `runs` number of items. + // Should returns true if this "try" should be accepted and collected. + // If false is returned, `Collector` will retry up to `tries` number of times. + shouldAccept(results: Metrics[]): Promise; +} + +// A simple scenario that just loads the given URL. +export class LoadPageScenario implements Scenario { + public constructor(public url: string) { } + + public async run(_: playwright.Browser, page: playwright.Page): Promise { + await page.goto(this.url, { waitUntil: 'load', timeout: 60000 }); + } +} + +// Loads test-apps/jank/ as a page source & waits for a short time before quitting. +export class JankTestScenario implements Scenario { + public constructor(private _indexFile: string) { } + + public async run(_: playwright.Browser, page: playwright.Page): Promise { + let url = path.resolve(`./test-apps/jank/${this._indexFile}`); + assert(fs.existsSync(url)); + url = `file:///${url.replace('\\', '/')}`; + console.log('Navigating to ', url); + await page.goto(url, { waitUntil: 'load', timeout: 60000 }); + await new Promise(resolve => setTimeout(resolve, 5000)); + } +} diff --git a/packages/replay/metrics/src/util/console.ts b/packages/replay/metrics/src/util/console.ts new file mode 100644 index 000000000000..9af66f36eb90 --- /dev/null +++ b/packages/replay/metrics/src/util/console.ts @@ -0,0 +1,40 @@ +import { filesize } from 'filesize'; +import { Metrics } from '../collector.js'; + +import { Analysis, AnalyzerItemMetric } from '../results/analyzer.js'; +import { MetricsStats } from '../results/metrics-stats.js'; + +export async function consoleGroup(code: () => Promise): Promise { + console.group(); + return code().finally(console.groupEnd); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type PrintableTable = { [k: string]: any }; + +export function printStats(items: Metrics[]): void { + console.table({ + lcp: `${MetricsStats.mean(items, MetricsStats.lcp)?.toFixed(2)} ms`, + cls: `${MetricsStats.mean(items, MetricsStats.cls)?.toFixed(2)} ms`, + cpu: `${((MetricsStats.mean(items, MetricsStats.cpu) || 0) * 100).toFixed(2)} %`, + memoryMean: filesize(MetricsStats.mean(items, MetricsStats.memoryMean)), + memoryMax: filesize(MetricsStats.max(items, MetricsStats.memoryMax)), + }); +} + +export function printAnalysis(analysis: Analysis): void { + const table: PrintableTable = {}; + for (const item of analysis.items) { + table[AnalyzerItemMetric[item.metric]] = { + value: item.values.value(0), + withSentry: item.values.diff(0, 1), + withReplay: item.values.diff(1, 2), + ...((item.others == undefined) ? {} : { + previous: item.others.value(0), + previousWithSentry: item.others.diff(0, 1), + previousWithReplay: item.others.diff(1, 2) + }) + }; + } + console.table(table); +} diff --git a/packages/replay/metrics/src/util/git.ts b/packages/replay/metrics/src/util/git.ts new file mode 100644 index 000000000000..9ec6acae0600 --- /dev/null +++ b/packages/replay/metrics/src/util/git.ts @@ -0,0 +1,66 @@ +import { simpleGit } from 'simple-git'; + +export type GitHash = string; +const git = simpleGit(); + +async function defaultBranch(): Promise { + const remoteInfo = await git.remote(['show', 'origin']) as string; + for (let line of remoteInfo.split('\n')) { + line = line.trim(); + if (line.startsWith('HEAD branch:')) { + return line.substring('HEAD branch:'.length).trim(); + } + } + throw "Couldn't find base branch name"; +} + +export const Git = { + get repository(): Promise { + return (async () => { + if (typeof process.env.GITHUB_REPOSITORY == 'string' && process.env.GITHUB_REPOSITORY.length > 0) { + return `github.com/${process.env.GITHUB_REPOSITORY}`; + } else { + let url = await git.remote(['get-url', 'origin']) as string; + url = url.trim(); + url = url.replace(/^git@/, ''); + url = url.replace(/\.git$/, ''); + return url.replace(':', '/'); + } + })(); + }, + + get branch(): Promise { + return (async () => { + if (typeof process.env.GITHUB_HEAD_REF == 'string' && process.env.GITHUB_HEAD_REF.length > 0) { + return process.env.GITHUB_HEAD_REF; + } else if (typeof process.env.GITHUB_REF == 'string' && process.env.GITHUB_REF.startsWith('refs/heads/')) { + return process.env.GITHUB_REF.substring('refs/heads/'.length); + } else { + const branches = (await git.branchLocal()).branches; + for (const name in branches) { + if (branches[name].current) return name; + } + throw "Couldn't find current branch name"; + } + })(); + }, + + get baseBranch(): Promise { + if (typeof process.env.GITHUB_BASE_REF == 'string' && process.env.GITHUB_BASE_REF.length > 0) { + return Promise.resolve(process.env.GITHUB_BASE_REF); + } else { + return defaultBranch(); + } + }, + + get hash(): Promise { + return (async () => { + let gitHash = await git.revparse('HEAD'); + const diff = await git.diff(); + if (diff.trim().length > 0) { + gitHash += '+dirty'; + } + return gitHash; + })(); + } +} diff --git a/packages/replay/metrics/src/util/github.ts b/packages/replay/metrics/src/util/github.ts new file mode 100644 index 000000000000..01b0e9fbe991 --- /dev/null +++ b/packages/replay/metrics/src/util/github.ts @@ -0,0 +1,195 @@ +import { Octokit } from '@octokit/rest'; +import axios from 'axios'; +import extract from 'extract-zip'; +import * as fs from 'fs'; +import path from 'path'; + +import { PrCommentBuilder } from '../results/pr-comment.js'; +import { consoleGroup } from './console.js'; +import { Git } from './git.js'; + +const octokit = new Octokit({ + auth: process.env.GITHUB_TOKEN, + // log: console, +}); + +const [, owner, repo] = (await Git.repository).split('/'); +const defaultArgs = { owner, repo } + +async function downloadArtifact(url: string, path: string): Promise { + const writer = fs.createWriteStream(path); + return axios({ + method: 'get', + url: url, + responseType: 'stream', + headers: { + 'Authorization': `Bearer ${process.env.GITHUB_TOKEN}` + } + }).then(response => { + return new Promise((resolve, reject) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + response.data.pipe(writer); + let error: Error; + writer.on('error', err => { + error = err; + writer.close(); + reject(err); + }); + writer.on('close', () => { + if (!error) resolve(); + }); + }); + }); +} + +async function tryAddOrUpdateComment(commentBuilder: PrCommentBuilder): Promise { + /* Env var GITHUB_REF is only set if a branch or tag is available for the current CI event trigger type. + The ref given is fully-formed, meaning that + * for branches the format is refs/heads/, + * for pull requests it is refs/pull//merge, + * and for tags it is refs/tags/. + For example, refs/heads/feature-branch-1. + */ + let prNumber: number | undefined; + if (typeof process.env.GITHUB_REF == 'string' && process.env.GITHUB_REF.length > 0 && process.env.GITHUB_REF.startsWith('refs/pull/')) { + prNumber = parseInt(process.env.GITHUB_REF.split('/')[2]); + console.log(`Determined PR number ${prNumber} based on GITHUB_REF environment variable: '${process.env.GITHUB_REF}'`); + } else { + prNumber = (await octokit.rest.pulls.list({ + ...defaultArgs, + base: await Git.baseBranch, + head: await Git.branch + })).data[0].number; + if (prNumber != undefined) { + console.log(`Found PR number ${prNumber} based on base and head branches`); + } + } + + if (prNumber == undefined) return false; + + // Determine the PR comment author: + // Trying to fetch `octokit.users.getAuthenticated()` throws (in CI only): + // {"message":"Resource not accessible by integration","documentation_url":"https://docs.github.com/rest/reference/users#get-the-authenticated-user"} + // Let's make this conditional on some env variable that's unlikely to be set locally but will be set in GH Actions. + // Do not use "CI" because that's commonly set during local development and testing. + const author = typeof process.env.GITHUB_ACTION == 'string' ? 'github-actions[bot]' : (await octokit.users.getAuthenticated()).data.login; + + // Try to find an existing comment by the author and title. + const comment = await (async () => { + for await (const comments of octokit.paginate.iterator(octokit.rest.issues.listComments, { + ...defaultArgs, + issue_number: prNumber, + })) { + const found = comments.data.find((comment) => { + return comment.user?.login == author + && comment.body != undefined + && comment.body.indexOf(commentBuilder.title) >= 0; + }); + if (found) return found; + } + return undefined; + })(); + + if (comment != undefined) { + console.log(`Updating PR comment ${comment.html_url} body`) + await octokit.rest.issues.updateComment({ + ...defaultArgs, + comment_id: comment.id, + body: commentBuilder.body, + }); + } else { + console.log(`Adding a new comment to PR ${prNumber}`) + await octokit.rest.issues.createComment({ + ...defaultArgs, + issue_number: prNumber, + body: commentBuilder.body, + }); + } + + return true; +} + +export const GitHub = { + writeOutput(name: string, value: string): void { + if (typeof process.env.GITHUB_OUTPUT == 'string' && process.env.GITHUB_OUTPUT.length > 0) { + fs.appendFileSync(process.env.GITHUB_OUTPUT, `${name}=${value}\n`); + } + console.log(`Output ${name} = ${value}`); + }, + + downloadPreviousArtifact(branch: string, targetDir: string, artifactName: string): Promise { + console.log(`Trying to download previous artifact '${artifactName}' for branch '${branch}'`); + return consoleGroup(async () => { + fs.mkdirSync(targetDir, { recursive: true }); + + const workflow = await (async () => { + for await (const workflows of octokit.paginate.iterator(octokit.rest.actions.listRepoWorkflows, defaultArgs)) { + const found = workflows.data.find((w) => w.name == process.env.GITHUB_WORKFLOW); + if (found) return found; + } + return undefined; + })(); + if (workflow == undefined) { + console.log( + `Skipping previous artifact '${artifactName}' download for branch '${branch}' - not running in CI?`, + "Environment variable GITHUB_WORKFLOW isn't set." + ); + return; + } + + const workflowRuns = await octokit.actions.listWorkflowRuns({ + ...defaultArgs, + workflow_id: workflow.id, + branch: branch, + status: 'success', + }); + + if (workflowRuns.data.total_count == 0) { + console.warn(`Couldn't find any successful run for workflow '${workflow.name}'`); + return; + } + + const artifact = (await octokit.actions.listWorkflowRunArtifacts({ + ...defaultArgs, + run_id: workflowRuns.data.workflow_runs[0].id, + })).data.artifacts.find((it) => it.name == artifactName); + + if (artifact == undefined) { + console.warn(`Couldn't find any artifact matching ${artifactName}`); + return; + } + + console.log(`Downloading artifact ${artifact.archive_download_url} and extracting to ${targetDir}`); + + const tempFilePath = path.resolve(targetDir, '../tmp-artifacts.zip'); + if (fs.existsSync(tempFilePath)) { + fs.unlinkSync(tempFilePath); + } + + try { + await downloadArtifact(artifact.archive_download_url, tempFilePath); + await extract(tempFilePath, { dir: path.resolve(targetDir) }); + } finally { + if (fs.existsSync(tempFilePath)) { + fs.unlinkSync(tempFilePath); + } + } + }); + }, + + async addOrUpdateComment(commentBuilder: PrCommentBuilder): Promise { + console.log('Adding/updating PR comment'); + return consoleGroup(async () => { + let successful = false; + try { + successful = await tryAddOrUpdateComment(commentBuilder); + } finally { + if (!successful) { + const file = 'out/comment.html'; + console.log(`Writing built comment to ${path.resolve(file)}`); + fs.writeFileSync(file, commentBuilder.body); + } + } + }); + } +} diff --git a/packages/replay/metrics/src/util/json.ts b/packages/replay/metrics/src/util/json.ts new file mode 100644 index 000000000000..095614fd115e --- /dev/null +++ b/packages/replay/metrics/src/util/json.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ + +export type JsonObject = { [k: string]: T }; + +export function JsonStringify(object: T): string { + return JSON.stringify(object, (_: unknown, value: any): unknown => { + if (typeof value != 'undefined' && typeof value.toJSON == 'function') { + return value.toJSON(); + } else { + return value; + } + }, 2); +} diff --git a/packages/replay/metrics/src/vitals/cls.ts b/packages/replay/metrics/src/vitals/cls.ts new file mode 100644 index 000000000000..abe6d63fa58b --- /dev/null +++ b/packages/replay/metrics/src/vitals/cls.ts @@ -0,0 +1,41 @@ +import * as playwright from 'playwright'; + +export { CLS }; + +// https://web.dev/cls/ +class CLS { + constructor( + private _page: playwright.Page) { } + + public async setup(): Promise { + await this._page.context().addInitScript(`{ + window.cumulativeLayoutShiftScore = undefined; + + const observer = new PerformanceObserver((list) => { + for (const entry of list.getEntries()) { + if (!entry.hadRecentInput) { + if (window.cumulativeLayoutShiftScore === undefined) { + window.cumulativeLayoutShiftScore = entry.value; + } else { + window.cumulativeLayoutShiftScore += entry.value; + } + } + } + }); + + observer.observe({type: 'layout-shift', buffered: true}); + + document.addEventListener('visibilitychange', () => { + if (document.visibilityState === 'hidden') { + observer.takeRecords(); + observer.disconnect(); + } + }); + }`); + } + + public async collect(): Promise { + const result = await this._page.evaluate('window.cumulativeLayoutShiftScore'); + return result as number; + } +} diff --git a/packages/replay/metrics/src/vitals/fid.ts b/packages/replay/metrics/src/vitals/fid.ts new file mode 100644 index 000000000000..fb6baa41537a --- /dev/null +++ b/packages/replay/metrics/src/vitals/fid.ts @@ -0,0 +1,35 @@ +import * as playwright from 'playwright'; + +export { FID }; + +// https://web.dev/fid/ +class FID { + constructor( + private _page: playwright.Page) { } + + public async setup(): Promise { + await this._page.context().addInitScript(`{ + window.firstInputDelay = undefined; + + const observer = new PerformanceObserver((entryList) => { + for (const entry of entryList.getEntries()) { + window.firstInputDelay = entry.processingStart - entry.startTime; + } + }) + + observer.observe({type: 'first-input', buffered: true}); + + document.addEventListener('visibilitychange', () => { + if (document.visibilityState === 'hidden') { + observer.takeRecords(); + observer.disconnect(); + } + }); + }`); + } + + public async collect(): Promise { + const result = await this._page.evaluate('window.firstInputDelay'); + return result as number; + } +} diff --git a/packages/replay/metrics/src/vitals/index.ts b/packages/replay/metrics/src/vitals/index.ts new file mode 100644 index 000000000000..3170a6c73cb2 --- /dev/null +++ b/packages/replay/metrics/src/vitals/index.ts @@ -0,0 +1,38 @@ +import * as playwright from 'playwright'; + +import { CLS } from './cls.js'; +import { FID } from './fid.js'; +import { LCP } from './lcp.js'; + +export { WebVitals, WebVitalsCollector }; + +class WebVitals { + constructor(public lcp: number | undefined, public cls: number | undefined, public fid: number | undefined) { } + + public static fromJSON(data: Partial): WebVitals { + return new WebVitals(data.lcp as number, data.cls as number, data.fid as number); + } +} + +class WebVitalsCollector { + private constructor(private _lcp: LCP, private _cls: CLS, private _fid: FID) { + } + + public static async create(page: playwright.Page): + Promise { + const result = + new WebVitalsCollector(new LCP(page), new CLS(page), new FID(page)); + await result._lcp.setup(); + await result._cls.setup(); + await result._fid.setup(); + return result; + } + + public async collect(): Promise { + return new WebVitals( + await this._lcp.collect(), + await this._cls.collect(), + await this._fid.collect(), + ); + } +} diff --git a/packages/replay/metrics/src/vitals/lcp.ts b/packages/replay/metrics/src/vitals/lcp.ts new file mode 100644 index 000000000000..2f817ba97297 --- /dev/null +++ b/packages/replay/metrics/src/vitals/lcp.ts @@ -0,0 +1,35 @@ +import * as playwright from 'playwright'; + +export { LCP }; + +// https://web.dev/lcp/ +class LCP { + constructor( + private _page: playwright.Page) { } + + public async setup(): Promise { + await this._page.context().addInitScript(`{ + window.largestContentfulPaint = undefined; + + const observer = new PerformanceObserver((list) => { + const entries = list.getEntries(); + const lastEntry = entries[entries.length - 1]; + window.largestContentfulPaint = lastEntry.renderTime || lastEntry.loadTime; + }); + + observer.observe({ type: 'largest-contentful-paint', buffered: true }); + + document.addEventListener('visibilitychange', () => { + if (document.visibilityState === 'hidden') { + observer.takeRecords(); + observer.disconnect(); + } + }); + }`); + } + + public async collect(): Promise { + const result = await this._page.evaluate('window.largestContentfulPaint'); + return result as number; + } +} diff --git a/packages/replay/metrics/test-apps/jank/README.md b/packages/replay/metrics/test-apps/jank/README.md new file mode 100644 index 000000000000..3e0f46b66a1e --- /dev/null +++ b/packages/replay/metrics/test-apps/jank/README.md @@ -0,0 +1,4 @@ +# Chrome DevTools Jank article sample code + +* Originally coming from [devtools-samples](https://github.com/GoogleChrome/devtools-samples/tree/4818abc9dbcdb954d0eb9b70879f4ea18756451f/jank), licensed under Apache 2.0. +* Linking article: diff --git a/packages/replay/metrics/test-apps/jank/app.js b/packages/replay/metrics/test-apps/jank/app.js new file mode 100644 index 000000000000..a854fd00d187 --- /dev/null +++ b/packages/replay/metrics/test-apps/jank/app.js @@ -0,0 +1,171 @@ +/* Copyright 2016 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + +document.addEventListener("DOMContentLoaded", function() { + 'use strict'; + + var app = {}, + proto = document.querySelector('.proto'), + movers, + bodySize = document.body.getBoundingClientRect(), + ballSize = proto.getBoundingClientRect(), + maxHeight = Math.floor(bodySize.height - ballSize.height), + maxWidth = 97, // 100vw - width of square (3vw) + incrementor = 10, + distance = 3, + frame, + minimum = 20, + subtract = document.querySelector('.subtract'), + add = document.querySelector('.add'); + + app.optimize = true; + app.count = minimum; + app.enableApp = true; + + app.init = function () { + if (movers) { + bodySize = document.body.getBoundingClientRect(); + for (var i = 0; i < movers.length; i++) { + document.body.removeChild(movers[i]); + } + document.body.appendChild(proto); + ballSize = proto.getBoundingClientRect(); + document.body.removeChild(proto); + maxHeight = Math.floor(bodySize.height - ballSize.height); + } + for (var i = 0; i < app.count; i++) { + var m = proto.cloneNode(); + var top = Math.floor(Math.random() * (maxHeight)); + if (top === maxHeight) { + m.classList.add('up'); + } else { + m.classList.add('down'); + } + m.style.left = (i / (app.count / maxWidth)) + 'vw'; + m.style.top = top + 'px'; + document.body.appendChild(m); + } + movers = document.querySelectorAll('.mover'); + }; + + app.update = function (timestamp) { + for (var i = 0; i < app.count; i++) { + var m = movers[i]; + if (!app.optimize) { + var pos = m.classList.contains('down') ? + m.offsetTop + distance : m.offsetTop - distance; + if (pos < 0) pos = 0; + if (pos > maxHeight) pos = maxHeight; + m.style.top = pos + 'px'; + if (m.offsetTop === 0) { + m.classList.remove('up'); + m.classList.add('down'); + } + if (m.offsetTop === maxHeight) { + m.classList.remove('down'); + m.classList.add('up'); + } + } else { + var pos = parseInt(m.style.top.slice(0, m.style.top.indexOf('px'))); + m.classList.contains('down') ? pos += distance : pos -= distance; + if (pos < 0) pos = 0; + if (pos > maxHeight) pos = maxHeight; + m.style.top = pos + 'px'; + if (pos === 0) { + m.classList.remove('up'); + m.classList.add('down'); + } + if (pos === maxHeight) { + m.classList.remove('down'); + m.classList.add('up'); + } + } + } + frame = window.requestAnimationFrame(app.update); + } + + document.querySelector('.stop').addEventListener('click', function (e) { + if (app.enableApp) { + cancelAnimationFrame(frame); + e.target.textContent = 'Start'; + app.enableApp = false; + } else { + frame = window.requestAnimationFrame(app.update); + e.target.textContent = 'Stop'; + app.enableApp = true; + } + }); + + document.querySelector('.optimize').addEventListener('click', function (e) { + if (e.target.textContent === 'Optimize') { + app.optimize = true; + e.target.textContent = 'Un-Optimize'; + } else { + app.optimize = false; + e.target.textContent = 'Optimize'; + } + }); + + add.addEventListener('click', function (e) { + cancelAnimationFrame(frame); + app.count += incrementor; + subtract.disabled = false; + app.init(); + frame = requestAnimationFrame(app.update); + }); + + subtract.addEventListener('click', function () { + cancelAnimationFrame(frame); + app.count -= incrementor; + app.init(); + frame = requestAnimationFrame(app.update); + if (app.count === minimum) { + subtract.disabled = true; + } + }); + + function debounce(func, wait, immediate) { + var timeout; + return function() { + var context = this, args = arguments; + var later = function() { + timeout = null; + if (!immediate) func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) func.apply(context, args); + }; + }; + + var onResize = debounce(function () { + if (app.enableApp) { + cancelAnimationFrame(frame); + app.init(); + frame = requestAnimationFrame(app.update); + } + }, 500); + + window.addEventListener('resize', onResize); + + add.textContent = 'Add ' + incrementor; + subtract.textContent = 'Subtract ' + incrementor; + document.body.removeChild(proto); + proto.classList.remove('.proto'); + app.init(); + window.app = app; + frame = window.requestAnimationFrame(app.update); + +}); diff --git a/packages/replay/metrics/test-apps/jank/favicon-96x96.png b/packages/replay/metrics/test-apps/jank/favicon-96x96.png new file mode 100644 index 000000000000..7f01723cee0f Binary files /dev/null and b/packages/replay/metrics/test-apps/jank/favicon-96x96.png differ diff --git a/packages/replay/metrics/test-apps/jank/index.html b/packages/replay/metrics/test-apps/jank/index.html new file mode 100644 index 000000000000..bcdb2ee1acb9 --- /dev/null +++ b/packages/replay/metrics/test-apps/jank/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + Janky Animation + + + + + + + +
+ + + + + + + +
+ + + diff --git a/packages/replay/metrics/test-apps/jank/logo-1024px.png b/packages/replay/metrics/test-apps/jank/logo-1024px.png new file mode 100644 index 000000000000..84df3e22f6b0 Binary files /dev/null and b/packages/replay/metrics/test-apps/jank/logo-1024px.png differ diff --git a/packages/replay/metrics/test-apps/jank/styles.css b/packages/replay/metrics/test-apps/jank/styles.css new file mode 100644 index 000000000000..1f340f179d0f --- /dev/null +++ b/packages/replay/metrics/test-apps/jank/styles.css @@ -0,0 +1,59 @@ +/* Copyright 2016 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing permissions + * and limitations under the License. */ + + * { + margin: 0; + padding: 0; +} + +body { + height: 100vh; + width: 100vw; +} + +.controls { + position: fixed; + top: 2vw; + left: 2vw; + z-index: 1; +} + +.controls button { + display: block; + font-size: 1em; + padding: 1em; + margin: 1em; + background-color: beige; + color: black; +} + +.subtract:disabled { + opacity: 0.2; +} + +.mover { + height: 3vw; + position: absolute; + z-index: 0; +} + +.border { + border: 1px solid black; +} + +@media (max-width: 600px) { + .controls button { + min-width: 20vw; + } +} diff --git a/packages/replay/metrics/test-apps/jank/with-replay.html b/packages/replay/metrics/test-apps/jank/with-replay.html new file mode 100644 index 000000000000..7331eacfdd7f --- /dev/null +++ b/packages/replay/metrics/test-apps/jank/with-replay.html @@ -0,0 +1,56 @@ + + + + + + + + + + Janky Animation + + + + + + + + + + + +
+ + + + + + + +
+ + + diff --git a/packages/replay/metrics/test-apps/jank/with-sentry.html b/packages/replay/metrics/test-apps/jank/with-sentry.html new file mode 100644 index 000000000000..3d43051eaf5a --- /dev/null +++ b/packages/replay/metrics/test-apps/jank/with-sentry.html @@ -0,0 +1,50 @@ + + + + + + + + + + Janky Animation + + + + + + + + + + +
+ + + + + + + +
+ + + diff --git a/packages/replay/metrics/tsconfig.json b/packages/replay/metrics/tsconfig.json new file mode 100644 index 000000000000..1709316f0ea8 --- /dev/null +++ b/packages/replay/metrics/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "target": "es2020", + "module": "esnext", + "outDir": "build", + "esModuleInterop": true, + }, + "include": [ + "src/**/*.ts", + "configs/**/*.ts" + ] +} diff --git a/packages/replay/metrics/yarn.lock b/packages/replay/metrics/yarn.lock new file mode 100644 index 000000000000..b8837974c2a1 --- /dev/null +++ b/packages/replay/metrics/yarn.lock @@ -0,0 +1,457 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@kwsites/file-exists@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@kwsites/file-exists/-/file-exists-1.1.1.tgz#ad1efcac13e1987d8dbaf235ef3be5b0d96faa99" + integrity sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw== + dependencies: + debug "^4.1.1" + +"@kwsites/promise-deferred@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz#8ace5259254426ccef57f3175bc64ed7095ed919" + integrity sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw== + +"@octokit/auth-token@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-3.0.2.tgz#a0fc8de149fd15876e1ac78f6525c1c5ab48435f" + integrity sha512-pq7CwIMV1kmzkFTimdwjAINCXKTajZErLB4wMLYapR2nuB/Jpr66+05wOTZMSCBXP6n4DdDWT2W19Bm17vU69Q== + dependencies: + "@octokit/types" "^8.0.0" + +"@octokit/core@^4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-4.1.0.tgz#b6b03a478f1716de92b3f4ec4fd64d05ba5a9251" + integrity sha512-Czz/59VefU+kKDy+ZfDwtOIYIkFjExOKf+HA92aiTZJ6EfWpFzYQWw0l54ji8bVmyhc+mGaLUbSUmXazG7z5OQ== + dependencies: + "@octokit/auth-token" "^3.0.0" + "@octokit/graphql" "^5.0.0" + "@octokit/request" "^6.0.0" + "@octokit/request-error" "^3.0.0" + "@octokit/types" "^8.0.0" + before-after-hook "^2.2.0" + universal-user-agent "^6.0.0" + +"@octokit/endpoint@^7.0.0": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-7.0.3.tgz#0b96035673a9e3bedf8bab8f7335de424a2147ed" + integrity sha512-57gRlb28bwTsdNXq+O3JTQ7ERmBTuik9+LelgcLIVfYwf235VHbN9QNo4kXExtp/h8T423cR5iJThKtFYxC7Lw== + dependencies: + "@octokit/types" "^8.0.0" + is-plain-object "^5.0.0" + universal-user-agent "^6.0.0" + +"@octokit/graphql@^5.0.0": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-5.0.4.tgz#519dd5c05123868276f3ae4e50ad565ed7dff8c8" + integrity sha512-amO1M5QUQgYQo09aStR/XO7KAl13xpigcy/kI8/N1PnZYSS69fgte+xA4+c2DISKqUZfsh0wwjc2FaCt99L41A== + dependencies: + "@octokit/request" "^6.0.0" + "@octokit/types" "^8.0.0" + universal-user-agent "^6.0.0" + +"@octokit/openapi-types@^14.0.0": + version "14.0.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-14.0.0.tgz#949c5019028c93f189abbc2fb42f333290f7134a" + integrity sha512-HNWisMYlR8VCnNurDU6os2ikx0s0VyEjDYHNS/h4cgb8DeOxQ0n72HyinUtdDVxJhFy3FWLGl0DJhfEWk3P5Iw== + +"@octokit/plugin-paginate-rest@^5.0.0": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-5.0.1.tgz#93d7e74f1f69d68ba554fa6b888c2a9cf1f99a83" + integrity sha512-7A+rEkS70pH36Z6JivSlR7Zqepz3KVucEFVDnSrgHXzG7WLAzYwcHZbKdfTXHwuTHbkT1vKvz7dHl1+HNf6Qyw== + dependencies: + "@octokit/types" "^8.0.0" + +"@octokit/plugin-request-log@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz#5e50ed7083a613816b1e4a28aeec5fb7f1462e85" + integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA== + +"@octokit/plugin-rest-endpoint-methods@^6.7.0": + version "6.7.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-6.7.0.tgz#2f6f17f25b6babbc8b41d2bb0a95a8839672ce7c" + integrity sha512-orxQ0fAHA7IpYhG2flD2AygztPlGYNAdlzYz8yrD8NDgelPfOYoRPROfEyIe035PlxvbYrgkfUZIhSBKju/Cvw== + dependencies: + "@octokit/types" "^8.0.0" + deprecation "^2.3.1" + +"@octokit/request-error@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-3.0.2.tgz#f74c0f163d19463b87528efe877216c41d6deb0a" + integrity sha512-WMNOFYrSaX8zXWoJg9u/pKgWPo94JXilMLb2VManNOby9EZxrQaBe/QSC4a1TzpAlpxofg2X/jMnCyZgL6y7eg== + dependencies: + "@octokit/types" "^8.0.0" + deprecation "^2.0.0" + once "^1.4.0" + +"@octokit/request@^6.0.0": + version "6.2.2" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-6.2.2.tgz#a2ba5ac22bddd5dcb3f539b618faa05115c5a255" + integrity sha512-6VDqgj0HMc2FUX2awIs+sM6OwLgwHvAi4KCK3mT2H2IKRt6oH9d0fej5LluF5mck1lRR/rFWN0YIDSYXYSylbw== + dependencies: + "@octokit/endpoint" "^7.0.0" + "@octokit/request-error" "^3.0.0" + "@octokit/types" "^8.0.0" + is-plain-object "^5.0.0" + node-fetch "^2.6.7" + universal-user-agent "^6.0.0" + +"@octokit/rest@^19.0.5": + version "19.0.5" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-19.0.5.tgz#4dbde8ae69b27dca04b5f1d8119d282575818f6c" + integrity sha512-+4qdrUFq2lk7Va+Qff3ofREQWGBeoTKNqlJO+FGjFP35ZahP+nBenhZiGdu8USSgmq4Ky3IJ/i4u0xbLqHaeow== + dependencies: + "@octokit/core" "^4.1.0" + "@octokit/plugin-paginate-rest" "^5.0.0" + "@octokit/plugin-request-log" "^1.0.4" + "@octokit/plugin-rest-endpoint-methods" "^6.7.0" + +"@octokit/types@^8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-8.0.0.tgz#93f0b865786c4153f0f6924da067fe0bb7426a9f" + integrity sha512-65/TPpOJP1i3K4lBJMnWqPUJ6zuOtzhtagDvydAWbEXpbFYA0oMKKyLb95NFZZP0lSh/4b6K+DQlzvYQJQQePg== + dependencies: + "@octokit/openapi-types" "^14.0.0" + +"@tsconfig/node10@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" + integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" + integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== + +"@types/node@*", "@types/node@^18.11.17": + version "18.11.17" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.17.tgz#5c009e1d9c38f4a2a9d45c0b0c493fe6cdb4bcb5" + integrity sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng== + +"@types/yauzl@^2.9.1": + version "2.10.0" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599" + integrity sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw== + dependencies: + "@types/node" "*" + +acorn-walk@^8.1.1: + 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== + +acorn@^8.4.1: + version "8.8.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73" + integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA== + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +axios@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.2.2.tgz#72681724c6e6a43a9fea860fc558127dbe32f9f1" + integrity sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q== + dependencies: + follow-redirects "^1.15.0" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + +before-after-hook@^2.2.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c" + integrity sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ== + +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +debug@^4.1.1, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +deprecation@^2.0.0, deprecation@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" + integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +extract-zip@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" + integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== + dependencies: + debug "^4.1.1" + get-stream "^5.1.0" + yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" + +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== + dependencies: + pend "~1.2.0" + +filesize@^10.0.6: + version "10.0.6" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-10.0.6.tgz#5f4cd2721664cd925db3a7a5a87bbfd6ab5ebb1a" + integrity sha512-rzpOZ4C9vMFDqOa6dNpog92CoLYjD79dnjLk2TYDDtImRIyLTOzqojCb05Opd1WuiWjs+fshhCgTd8cl7y5t+g== + +follow-redirects@^1.15.0: + version "1.15.2" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" + integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +node-fetch@^2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + +once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +p-timeout@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-6.0.0.tgz#84c210f5500da1af4c31ab2768d794e5e081dd91" + integrity sha512-5iS61MOdUMemWH9CORQRxVXTp9g5K8rPnI9uQpo97aWgsH3vVXKjkIhDi+OgIDmN3Ly9+AZ2fZV01Wut1yzfKA== + +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== + +playwright-core@1.29.1, playwright-core@^1.29.1: + version "1.29.1" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.29.1.tgz#9ec15d61c4bd2f386ddf6ce010db53a030345a47" + integrity sha512-20Ai3d+lMkWpI9YZYlxk8gxatfgax5STW8GaMozAHwigLiyiKQrdkt7gaoT9UQR8FIVDg6qVXs9IoZUQrDjIIg== + +playwright@^1.29.1: + version "1.29.1" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.29.1.tgz#fc04b34f42e3bfc0edadb1c45ef9bffd53c21f70" + integrity sha512-lasC+pMqsQ2uWhNurt3YK3xo0gWlMjslYUylKbHcqF/NTjwp9KStRGO7S6wwz2f52GcSnop8XUK/GymJjdzrxw== + dependencies: + playwright-core "1.29.1" + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +simple-git@^3.15.1: + version "3.15.1" + resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-3.15.1.tgz#57f595682cb0c2475d5056da078a05c8715a25ef" + integrity sha512-73MVa5984t/JP4JcQt0oZlKGr42ROYWC3BcUZfuHtT3IHKPspIvL0cZBnvPXF7LL3S/qVeVHVdYYmJ3LOTw4Rg== + dependencies: + "@kwsites/file-exists" "^1.1.1" + "@kwsites/promise-deferred" "^1.1.1" + debug "^4.3.4" + +simple-statistics@^7.8.0: + version "7.8.0" + resolved "https://registry.yarnpkg.com/simple-statistics/-/simple-statistics-7.8.0.tgz#1033d2d613656c7bd34f0e134fd7e69c803e6836" + integrity sha512-lTWbfJc0u6GZhBojLOrlHJMTHu6PdUjSsYLrpiH902dVBiYJyWlN/LdSoG8b5VvfG1D30gIBgarqMNeNmU5nAA== + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +ts-node@^10.9.1: + version "10.9.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" + integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +typescript@^4.9.4: + version "4.9.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78" + integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg== + +universal-user-agent@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" + integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +yauzl@^2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== diff --git a/packages/replay/package.json b/packages/replay/package.json index c01ec6848e77..949dd4495ff8 100644 --- a/packages/replay/package.json +++ b/packages/replay/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/replay", - "version": "7.29.0", + "version": "7.30.0", "description": "User replays for Sentry", "main": "build/npm/cjs/index.js", "module": "build/npm/esm/index.js", @@ -8,9 +8,9 @@ "sideEffects": false, "scripts": { "build": "run-s build:worker && run-p build:core build:types build:bundle", - "build:rollup": "run-s build:worker build:core", + "build:transpile": "run-s build:worker build:core", "build:bundle": "rollup -c rollup.bundle.config.js", - "build:dev": "run-p build:worker build:rollup build:types", + "build:dev": "run-p build:worker build:transpile build:types", "build:worker": "rollup -c rollup.config.worker.js", "build:core": "rollup -c rollup.npm.config.js", "build:types": "tsc -p tsconfig.types.json", @@ -20,7 +20,7 @@ "build:worker:watch": "yarn build:worker --watch", "build:bundle:watch": "yarn build:bundle --watch", "build:types:watch": "tsc -p tsconfig.types.json --watch", - "build:npm": "ts-node ../../scripts/prepack.ts --bundles && npm pack ./build/npm", + "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", @@ -53,12 +53,9 @@ "tslib": "^1.9.3" }, "dependencies": { - "@sentry/core": "7.29.0", - "@sentry/types": "7.29.0", - "@sentry/utils": "7.29.0" - }, - "peerDependencies": { - "@sentry/browser": ">=7.24.0" + "@sentry/core": "7.30.0", + "@sentry/types": "7.30.0", + "@sentry/utils": "7.30.0" }, "engines": { "node": ">=12" diff --git a/packages/replay/src/constants.ts b/packages/replay/src/constants.ts index b0176293e13c..187d729cb6f5 100644 --- a/packages/replay/src/constants.ts +++ b/packages/replay/src/constants.ts @@ -33,3 +33,6 @@ export const MASK_ALL_TEXT_SELECTOR = 'body *:not(style), body *:not(script)'; export const DEFAULT_FLUSH_MIN_DELAY = 5_000; export const DEFAULT_FLUSH_MAX_DELAY = 15_000; export const INITIAL_FLUSH_DELAY = 5_000; + +export const RETRY_BASE_INTERVAL = 5000; +export const RETRY_MAX_COUNT = 3; diff --git a/packages/replay/src/coreHandlers/breadcrumbHandler.ts b/packages/replay/src/coreHandlers/breadcrumbHandler.ts index fe0504b0230f..919c46fa7ad1 100644 --- a/packages/replay/src/coreHandlers/breadcrumbHandler.ts +++ b/packages/replay/src/coreHandlers/breadcrumbHandler.ts @@ -1,9 +1,13 @@ -import { Breadcrumb, Scope } from '@sentry/types'; +import type { Breadcrumb, Scope } from '@sentry/types'; import type { InstrumentationTypeBreadcrumb } from '../types'; -import { DomHandlerData, handleDom } from './handleDom'; +import type { DomHandlerData } from './handleDom'; +import { handleDom } from './handleDom'; import { handleScope } from './handleScope'; +/** + * An event handler to react to breadcrumbs. + */ export function breadcrumbHandler(type: InstrumentationTypeBreadcrumb, handlerData: unknown): Breadcrumb | null { if (type === 'scope') { return handleScope(handlerData as Scope); diff --git a/packages/replay/src/coreHandlers/handleDom.ts b/packages/replay/src/coreHandlers/handleDom.ts index 412bbb6cefd9..7cc2dc684b51 100644 --- a/packages/replay/src/coreHandlers/handleDom.ts +++ b/packages/replay/src/coreHandlers/handleDom.ts @@ -1,4 +1,4 @@ -import { Breadcrumb } from '@sentry/types'; +import type { Breadcrumb } from '@sentry/types'; import { htmlTreeAsString } from '@sentry/utils'; import { record } from 'rrweb'; @@ -9,6 +9,9 @@ export interface DomHandlerData { event: Node | { target: Node }; } +/** + * An event handler to react to DOM events. + */ export function handleDom(handlerData: DomHandlerData): Breadcrumb | null { // Taken from https://github.com/getsentry/sentry-javascript/blob/master/packages/browser/src/integrations/breadcrumbs.ts#L112 let target; diff --git a/packages/replay/src/coreHandlers/handleFetch.ts b/packages/replay/src/coreHandlers/handleFetch.ts index 961a18b638d6..290a58d4531d 100644 --- a/packages/replay/src/coreHandlers/handleFetch.ts +++ b/packages/replay/src/coreHandlers/handleFetch.ts @@ -1,5 +1,4 @@ -import type { ReplayPerformanceEntry } from '../createPerformanceEntry'; -import type { ReplayContainer } from '../types'; +import type { ReplayContainer, ReplayPerformanceEntry } from '../types'; import { createPerformanceSpans } from '../util/createPerformanceSpans'; import { shouldFilterRequest } from '../util/shouldFilterRequest'; diff --git a/packages/replay/src/coreHandlers/handleGlobalEvent.ts b/packages/replay/src/coreHandlers/handleGlobalEvent.ts index 87dc6c790fad..719391e25491 100644 --- a/packages/replay/src/coreHandlers/handleGlobalEvent.ts +++ b/packages/replay/src/coreHandlers/handleGlobalEvent.ts @@ -1,5 +1,5 @@ import { addBreadcrumb } from '@sentry/core'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { logger } from '@sentry/utils'; import { REPLAY_EVENT_NAME, UNABLE_TO_SEND_REPLAY } from '../constants'; @@ -12,10 +12,7 @@ import { isRrwebError } from '../util/isRrwebError'; export function handleGlobalEventListener(replay: ReplayContainer): (event: Event) => Event | null { return (event: Event) => { // Do not apply replayId to the root event - if ( - // @ts-ignore new event type - event.type === REPLAY_EVENT_NAME - ) { + if (event.type === REPLAY_EVENT_NAME) { // Replays have separate set of breadcrumbs, do not include breadcrumbs // from core SDK delete event.breadcrumbs; @@ -31,7 +28,7 @@ export function handleGlobalEventListener(replay: ReplayContainer): (event: Even // Only tag transactions with replayId if not waiting for an error // @ts-ignore private - if (event.type !== 'transaction' || replay.recordingMode === 'session') { + if (!event.type || replay.recordingMode === 'session') { event.tags = { ...event.tags, replayId: replay.session?.id }; } diff --git a/packages/replay/src/coreHandlers/handleHistory.ts b/packages/replay/src/coreHandlers/handleHistory.ts index f806d2d3c75b..4732c0118831 100644 --- a/packages/replay/src/coreHandlers/handleHistory.ts +++ b/packages/replay/src/coreHandlers/handleHistory.ts @@ -1,5 +1,4 @@ -import { ReplayPerformanceEntry } from '../createPerformanceEntry'; -import type { ReplayContainer } from '../types'; +import type { ReplayContainer, ReplayPerformanceEntry } from '../types'; import { createPerformanceSpans } from '../util/createPerformanceSpans'; interface HistoryHandlerData { diff --git a/packages/replay/src/coreHandlers/handleScope.ts b/packages/replay/src/coreHandlers/handleScope.ts index 41cc4a6d4e02..6b5a8157cd9c 100644 --- a/packages/replay/src/coreHandlers/handleScope.ts +++ b/packages/replay/src/coreHandlers/handleScope.ts @@ -1,9 +1,12 @@ -import { Breadcrumb, Scope } from '@sentry/types'; +import type { Breadcrumb, Scope } from '@sentry/types'; import { createBreadcrumb } from '../util/createBreadcrumb'; let _LAST_BREADCRUMB: null | Breadcrumb = null; +/** + * An event handler to handle scope changes. + */ export function handleScope(scope: Scope): Breadcrumb | null { const newBreadcrumb = scope.getLastBreadcrumb(); diff --git a/packages/replay/src/coreHandlers/handleXhr.ts b/packages/replay/src/coreHandlers/handleXhr.ts index a225345afe2f..883201d825e9 100644 --- a/packages/replay/src/coreHandlers/handleXhr.ts +++ b/packages/replay/src/coreHandlers/handleXhr.ts @@ -1,5 +1,4 @@ -import { ReplayPerformanceEntry } from '../createPerformanceEntry'; -import type { ReplayContainer } from '../types'; +import type { ReplayContainer, ReplayPerformanceEntry } from '../types'; import { createPerformanceSpans } from '../util/createPerformanceSpans'; import { shouldFilterRequest } from '../util/shouldFilterRequest'; diff --git a/packages/replay/src/eventBuffer.ts b/packages/replay/src/eventBuffer.ts index 1d670506de67..ec129550a009 100644 --- a/packages/replay/src/eventBuffer.ts +++ b/packages/replay/src/eventBuffer.ts @@ -2,16 +2,18 @@ // TODO: figure out member access types and remove the line above import { captureException } from '@sentry/core'; -import { ReplayRecordingData } from '@sentry/types'; import { logger } from '@sentry/utils'; -import type { EventBuffer, RecordingEvent, WorkerRequest, WorkerResponse } from './types'; +import type { AddEventResult, EventBuffer, RecordingEvent, WorkerRequest } from './types'; import workerString from './worker/worker.js'; interface CreateEventBufferParams { useCompression: boolean; } +/** + * Create an event buffer for replays. + */ export function createEventBuffer({ useCompression }: CreateEventBufferParams): EventBuffer { // eslint-disable-next-line no-restricted-globals if (useCompression && window.Worker) { @@ -43,21 +45,30 @@ class EventBufferArray implements EventBuffer { this._events = []; } - public destroy(): void { - this._events = []; + public get pendingLength(): number { + return this._events.length; } - public get length(): number { - return this._events.length; + /** + * Returns the raw events that are buffered. In `EventBufferArray`, this is the + * same as `this._events`. + */ + public get pendingEvents(): RecordingEvent[] { + return this._events; + } + + public destroy(): void { + this._events = []; } - public addEvent(event: RecordingEvent, isCheckout?: boolean): void { + public async addEvent(event: RecordingEvent, isCheckout?: boolean): Promise { if (isCheckout) { this._events = [event]; return; } this._events.push(event); + return; } public finish(): Promise { @@ -72,8 +83,18 @@ class EventBufferArray implements EventBuffer { } } -// exporting for testing +/** + * Event buffer that uses a web worker to compress events. + * Exported only for testing. + */ export class EventBufferCompressionWorker implements EventBuffer { + /** + * Keeps track of the list of events since the last flush that have not been compressed. + * For example, page is reloaded and a flush attempt is made, but + * `finish()` (and thus the flush), does not complete. + */ + public _pendingEvents: RecordingEvent[] = []; + private _worker: null | Worker; private _eventBufferItemLength: number = 0; private _id: number = 0; @@ -82,6 +103,25 @@ export class EventBufferCompressionWorker implements EventBuffer { this._worker = worker; } + /** + * The number of raw events that are buffered. This may not be the same as + * the number of events that have been compresed in the worker because + * `addEvent` is async. + */ + public get pendingLength(): number { + return this._eventBufferItemLength; + } + + /** + * Returns a list of the raw recording events that are being compressed. + */ + public get pendingEvents(): RecordingEvent[] { + return this._pendingEvents; + } + + /** + * Destroy the event buffer. + */ public destroy(): void { __DEBUG_BUILD__ && logger.log('[Replay] Destroying compression worker'); this._worker?.terminate(); @@ -89,14 +129,11 @@ export class EventBufferCompressionWorker implements EventBuffer { } /** - * Note that this may not reflect what is actually in the event buffer. This - * is only a local count of the buffer size since `addEvent` is async. + * Add an event to the event buffer. + * + * Returns true if event was successfuly received and processed by worker. */ - public get length(): number { - return this._eventBufferItemLength; - } - - public async addEvent(event: RecordingEvent, isCheckout?: boolean): Promise { + public async addEvent(event: RecordingEvent, isCheckout?: boolean): Promise { if (isCheckout) { // This event is a checkout, make sure worker buffer is cleared before // proceeding. @@ -107,9 +144,17 @@ export class EventBufferCompressionWorker implements EventBuffer { }); } + // Don't store checkout events in `_pendingEvents` because they are too large + if (!isCheckout) { + this._pendingEvents.push(event); + } + return this._sendEventToWorker(event); } + /** + * Finish the event buffer and return the compressed data. + */ public finish(): Promise { return this._finishRequest(this._getAndIncrementId()); } @@ -117,7 +162,7 @@ export class EventBufferCompressionWorker implements EventBuffer { /** * Post message to worker and wait for response before resolving promise. */ - private _postMessage({ id, method, args }: WorkerRequest): Promise { + private _postMessage({ id, method, args }: WorkerRequest): Promise { return new Promise((resolve, reject) => { // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const listener = ({ data }: MessageEvent) => { @@ -160,8 +205,11 @@ export class EventBufferCompressionWorker implements EventBuffer { }); } - private _sendEventToWorker(event: RecordingEvent): Promise { - const promise = this._postMessage({ + /** + * Send the event to the worker. + */ + private async _sendEventToWorker(event: RecordingEvent): Promise { + const promise = this._postMessage({ id: this._getAndIncrementId(), method: 'addEvent', args: [event], @@ -173,15 +221,23 @@ export class EventBufferCompressionWorker implements EventBuffer { return promise; } + /** + * Finish the request and return the compressed data from the worker. + */ private async _finishRequest(id: number): Promise { - const promise = this._postMessage({ id, method: 'finish', args: [] }); + const promise = this._postMessage({ id, method: 'finish', args: [] }); // XXX: See note in `get length()` this._eventBufferItemLength = 0; - return promise as Promise; + await promise; + + this._pendingEvents = []; + + return promise; } + /** Get the current ID and increment it for the next call. */ private _getAndIncrementId(): number { return this._id++; } diff --git a/packages/replay/src/integration.ts b/packages/replay/src/integration.ts index 34862682b325..274ec6147e34 100644 --- a/packages/replay/src/integration.ts +++ b/packages/replay/src/integration.ts @@ -1,6 +1,5 @@ import { getCurrentHub } from '@sentry/core'; -import type { BrowserClientReplayOptions } from '@sentry/types'; -import { Integration } from '@sentry/types'; +import type { BrowserClientReplayOptions, Integration } from '@sentry/types'; import { DEFAULT_ERROR_SAMPLE_RATE, @@ -18,6 +17,9 @@ const MEDIA_SELECTORS = 'img,image,svg,path,rect,area,video,object,picture,embed let _initialized = false; +/** + * The main replay integration class, to be passed to `init({ integrations: [] })`. + */ export class Replay implements Integration { /** * @inheritDoc @@ -32,21 +34,13 @@ export class Replay implements Integration { /** * Options to pass to `rrweb.record()` */ - readonly recordingOptions: RecordingOptions; + private readonly _recordingOptions: RecordingOptions; - readonly options: ReplayPluginOptions; - - protected get _isInitialized(): boolean { - return _initialized; - } - - protected set _isInitialized(value: boolean) { - _initialized = value; - } + private readonly _options: ReplayPluginOptions; private _replay?: ReplayContainer; - constructor({ + public constructor({ flushMinDelay = DEFAULT_FLUSH_MIN_DELAY, flushMaxDelay = DEFAULT_FLUSH_MAX_DELAY, initialFlushDelay = INITIAL_FLUSH_DELAY, @@ -63,19 +57,19 @@ export class Replay implements Integration { ignoreClass = 'sentry-ignore', maskTextClass = 'sentry-mask', blockSelector = '[data-sentry-block]', - ...recordingOptions + ..._recordingOptions }: ReplayConfiguration = {}) { - this.recordingOptions = { + this._recordingOptions = { maskAllInputs, blockClass, ignoreClass, maskTextClass, maskTextSelector, blockSelector, - ...recordingOptions, + ..._recordingOptions, }; - this.options = { + this._options = { flushMinDelay, flushMaxDelay, stickySession, @@ -97,7 +91,7 @@ Instead, configure \`replaysSessionSampleRate\` directly in the SDK init options Sentry.init({ replaysSessionSampleRate: ${sessionSampleRate} })`, ); - this.options.sessionSampleRate = sessionSampleRate; + this._options.sessionSampleRate = sessionSampleRate; } if (typeof errorSampleRate === 'number') { @@ -109,22 +103,22 @@ Instead, configure \`replaysOnErrorSampleRate\` directly in the SDK init options Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`, ); - this.options.errorSampleRate = errorSampleRate; + this._options.errorSampleRate = errorSampleRate; } - if (this.options.maskAllText) { + if (this._options.maskAllText) { // `maskAllText` is a more user friendly option to configure // `maskTextSelector`. This means that all nodes will have their text // content masked. - this.recordingOptions.maskTextSelector = MASK_ALL_TEXT_SELECTOR; + this._recordingOptions.maskTextSelector = MASK_ALL_TEXT_SELECTOR; } - if (this.options.blockAllMedia) { + if (this._options.blockAllMedia) { // `blockAllMedia` is a more user friendly option to configure blocking // embedded media elements - this.recordingOptions.blockSelector = !this.recordingOptions.blockSelector + this._recordingOptions.blockSelector = !this._recordingOptions.blockSelector ? MEDIA_SELECTORS - : `${this.recordingOptions.blockSelector},${MEDIA_SELECTORS}`; + : `${this._recordingOptions.blockSelector},${MEDIA_SELECTORS}`; } if (this._isInitialized && isBrowser()) { @@ -134,6 +128,16 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`, this._isInitialized = true; } + /** If replay has already been initialized */ + protected get _isInitialized(): boolean { + return _initialized; + } + + /** Update _isInitialized */ + protected set _isInitialized(value: boolean) { + _initialized = value; + } + /** * We previously used to create a transaction in `setupOnce` and it would * potentially create a transaction before some native SDK integrations have run @@ -144,7 +148,7 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`, * global event processors to finish. This is no longer needed, but keeping it * here to avoid any future issues. */ - setupOnce(): void { + public setupOnce(): void { if (!isBrowser()) { return; } @@ -161,7 +165,7 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`, * Creates or loads a session, attaches listeners to varying events (DOM, * PerformanceObserver, Recording, Sentry SDK, etc) */ - start(): void { + public start(): void { if (!this._replay) { return; } @@ -173,7 +177,7 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`, * Currently, this needs to be manually called (e.g. for tests). Sentry SDK * does not support a teardown */ - stop(): void { + public stop(): void { if (!this._replay) { return; } @@ -181,13 +185,14 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`, this._replay.stop(); } + /** Setup the integration. */ private _setup(): void { // Client is not available in constructor, so we need to wait until setupOnce this._loadReplayOptionsFromClient(); this._replay = new ReplayContainer({ - options: this.options, - recordingOptions: this.recordingOptions, + options: this._options, + recordingOptions: this._recordingOptions, }); } @@ -197,11 +202,11 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`, const opt = client && (client.getOptions() as BrowserClientReplayOptions | undefined); if (opt && typeof opt.replaysSessionSampleRate === 'number') { - this.options.sessionSampleRate = opt.replaysSessionSampleRate; + this._options.sessionSampleRate = opt.replaysSessionSampleRate; } if (opt && typeof opt.replaysOnErrorSampleRate === 'number') { - this.options.errorSampleRate = opt.replaysOnErrorSampleRate; + this._options.errorSampleRate = opt.replaysOnErrorSampleRate; } } } diff --git a/packages/replay/src/replay.ts b/packages/replay/src/replay.ts index 50f34beb2f45..241d330dd412 100644 --- a/packages/replay/src/replay.ts +++ b/packages/replay/src/replay.ts @@ -1,29 +1,22 @@ /* eslint-disable max-lines */ // TODO: We might want to split this file up -import { addGlobalEventProcessor, captureException, getCurrentHub, setContext } from '@sentry/core'; -import { Breadcrumb, ReplayEvent } from '@sentry/types'; -import { addInstrumentationHandler, logger } from '@sentry/utils'; +import { addGlobalEventProcessor, captureException, getCurrentHub } from '@sentry/core'; +import type { Breadcrumb, ReplayRecordingMode } from '@sentry/types'; +import type { RateLimits } from '@sentry/utils'; +import { addInstrumentationHandler, disabledUntil, logger } from '@sentry/utils'; import { EventType, record } from 'rrweb'; -import { - MAX_SESSION_LIFE, - REPLAY_EVENT_NAME, - SESSION_IDLE_DURATION, - UNABLE_TO_SEND_REPLAY, - VISIBILITY_CHANGE_TIMEOUT, - WINDOW, -} from './constants'; +import { MAX_SESSION_LIFE, SESSION_IDLE_DURATION, VISIBILITY_CHANGE_TIMEOUT, WINDOW } from './constants'; import { breadcrumbHandler } from './coreHandlers/breadcrumbHandler'; import { handleFetchSpanListener } from './coreHandlers/handleFetch'; import { handleGlobalEventListener } from './coreHandlers/handleGlobalEvent'; import { handleHistorySpanListener } from './coreHandlers/handleHistory'; import { handleXhrSpanListener } from './coreHandlers/handleXhr'; import { setupPerformanceObserver } from './coreHandlers/performanceObserver'; -import { createPerformanceEntries } from './createPerformanceEntry'; import { createEventBuffer } from './eventBuffer'; -import { deleteSession } from './session/deleteSession'; import { getSession } from './session/getSession'; import { saveSession } from './session/saveSession'; import type { + AddEventResult, AddUpdateCallback, AllPerformanceEntry, EventBuffer, @@ -34,29 +27,23 @@ import type { RecordingOptions, ReplayContainer as ReplayContainerInterface, ReplayPluginOptions, - ReplayRecordingMode, - SendReplay, Session, } from './types'; import { addEvent } from './util/addEvent'; import { addMemoryEntry } from './util/addMemoryEntry'; import { createBreadcrumb } from './util/createBreadcrumb'; -import { createPayload } from './util/createPayload'; +import { createPerformanceEntries } from './util/createPerformanceEntries'; import { createPerformanceSpans } from './util/createPerformanceSpans'; -import { createReplayEnvelope } from './util/createReplayEnvelope'; import { debounce } from './util/debounce'; -import { getReplayEvent } from './util/getReplayEvent'; import { isExpired } from './util/isExpired'; import { isSessionExpired } from './util/isSessionExpired'; import { overwriteRecordDroppedEvent, restoreRecordDroppedEvent } from './util/monkeyPatchRecordDroppedEvent'; +import { sendReplay } from './util/sendReplay'; +import { RateLimitError } from './util/sendReplayRequest'; /** - * Returns true to return control to calling function, otherwise continue with normal batching + * The main replay container class, which holds all the state and methods for recording and sending replays. */ - -const BASE_RETRY_INTERVAL = 5000; -const MAX_RETRY_COUNT = 3; - export class ReplayContainer implements ReplayContainerInterface { public eventBuffer: EventBuffer | null = null; @@ -83,9 +70,6 @@ export class ReplayContainer implements ReplayContainerInterface { private _performanceObserver: PerformanceObserver | null = null; - private _retryCount: number = 0; - private _retryInterval: number = BASE_RETRY_INTERVAL; - private _debouncedFlush: ReturnType; private _flushLock: Promise | null = null; @@ -126,11 +110,17 @@ export class ReplayContainer implements ReplayContainerInterface { initialUrl: '', }; - constructor({ options, recordingOptions }: { options: ReplayPluginOptions; recordingOptions: RecordingOptions }) { + public constructor({ + options, + recordingOptions, + }: { + options: ReplayPluginOptions; + recordingOptions: RecordingOptions; + }) { this._recordingOptions = recordingOptions; this._options = options; - this._debouncedFlush = debounce(() => this.flush(), this._options.flushMinDelay, { + this._debouncedFlush = debounce(() => this._flush(), this._options.flushMinDelay, { maxWait: this._options.flushMaxDelay, }); } @@ -161,14 +151,14 @@ export class ReplayContainer implements ReplayContainerInterface { * Creates or loads a session, attaches listeners to varying events (DOM, * _performanceObserver, Recording, Sentry SDK, etc) */ - start(): void { - this.setInitialState(); + public start(): void { + this._setInitialState(); - this.loadSession({ expiry: SESSION_IDLE_DURATION }); + this._loadSession({ expiry: SESSION_IDLE_DURATION }); // If there is no session, then something bad has happened - can't continue if (!this.session) { - this.handleException(new Error('No session found')); + this._handleException(new Error('No session found')); return; } @@ -177,23 +167,21 @@ export class ReplayContainer implements ReplayContainerInterface { return; } - // Modify recording options to checkoutEveryNthSecond if - // sampling for error replay. This is because we don't know - // when an error will occur, so we need to keep a buffer of - // replay events. + // If session is sampled for errors, then we need to set the recordingMode + // to 'error', which will configure recording with different options. if (this.session.sampled === 'error') { this.recordingMode = 'error'; } // setup() is generally called on page load or manually - in both cases we // should treat it as an activity - this.updateSessionActivity(); + this._updateSessionActivity(); this.eventBuffer = createEventBuffer({ useCompression: Boolean(this._options.useCompression), }); - this.addListeners(); + this._addListeners(); // Need to set as enabled before we start recording, as `record()` can trigger a flush with a new checkout this._isEnabled = true; @@ -206,18 +194,18 @@ export class ReplayContainer implements ReplayContainerInterface { * * Note that this will cause a new DOM checkout */ - startRecording(): void { + public startRecording(): void { try { this._stopRecording = record({ ...this._recordingOptions, - // When running in error sampling mode, we need to overwrite `checkoutEveryNth` + // When running in error sampling mode, we need to overwrite `checkoutEveryNms` // Without this, it would record forever, until an error happens, which we don't want // instead, we'll always keep the last 60 seconds of replay before an error happened - ...(this.recordingMode === 'error' && { checkoutEveryNth: 60000 }), - emit: this.handleRecordingEmit, + ...(this.recordingMode === 'error' && { checkoutEveryNms: 60000 }), + emit: this._handleRecordingEmit, }); } catch (err) { - this.handleException(err); + this._handleException(err); } } @@ -238,16 +226,16 @@ export class ReplayContainer implements ReplayContainerInterface { * Currently, this needs to be manually called (e.g. for tests). Sentry SDK * does not support a teardown */ - stop(): void { + public stop(): void { try { __DEBUG_BUILD__ && logger.log('[Replay] Stopping Replays'); this._isEnabled = false; - this.removeListeners(); + this._removeListeners(); this._stopRecording?.(); this.eventBuffer?.destroy(); this.eventBuffer = null; } catch (err) { - this.handleException(err); + this._handleException(err); } } @@ -256,7 +244,7 @@ export class ReplayContainer implements ReplayContainerInterface { * This differs from stop as this only stops DOM recording, it is * not as thorough of a shutdown as `stop()`. */ - pause(): void { + public pause(): void { this._isPaused = true; try { if (this._stopRecording) { @@ -264,7 +252,7 @@ export class ReplayContainer implements ReplayContainerInterface { this._stopRecording = undefined; } } catch (err) { - this.handleException(err); + this._handleException(err); } } @@ -274,13 +262,80 @@ export class ReplayContainer implements ReplayContainerInterface { * Note that calling `startRecording()` here will cause a * new DOM checkout.` */ - resume(): void { + public resume(): void { this._isPaused = false; this.startRecording(); } + /** + * We want to batch uploads of replay events. Save events only if + * `` milliseconds have elapsed since the last event + * *OR* if `` milliseconds have elapsed. + * + * Accepts a callback to perform side-effects and returns true to stop batch + * processing and hand back control to caller. + */ + public addUpdate(cb: AddUpdateCallback): void { + // We need to always run `cb` (e.g. in the case of `this.recordingMode == 'error'`) + const cbResult = cb?.(); + + // If this option is turned on then we will only want to call `flush` + // explicitly + if (this.recordingMode === 'error') { + return; + } + + // If callback is true, we do not want to continue with flushing -- the + // caller will need to handle it. + if (cbResult === true) { + return; + } + + // addUpdate is called quite frequently - use _debouncedFlush so that it + // respects the flush delays and does not flush immediately + this._debouncedFlush(); + } + + /** + * Updates the user activity timestamp and resumes recording. This should be + * called in an event handler for a user action that we consider as the user + * being "active" (e.g. a mouse click). + */ + public triggerUserActivity(): void { + this._updateUserActivity(); + + // This case means that recording was once stopped due to inactivity. + // Ensure that recording is resumed. + if (!this._stopRecording) { + // Create a new session, otherwise when the user action is flushed, it + // will get rejected due to an expired session. + this._loadSession({ expiry: SESSION_IDLE_DURATION }); + + // Note: This will cause a new DOM checkout + this.resume(); + return; + } + + // Otherwise... recording was never suspended, continue as normalish + this._checkAndHandleExpiredSession(); + + this._updateSessionActivity(); + } + + /** + * + * Always flush via `_debouncedFlush` so that we do not have flushes triggered + * from calling both `flush` and `_debouncedFlush`. Otherwise, there could be + * cases of mulitple flushes happening closely together. + */ + public flushImmediate(): Promise { + this._debouncedFlush(); + // `.flush` is provided by the debounced function, analogously to lodash.debounce + return this._debouncedFlush.flush() as Promise; + } + /** A wrapper to conditionally capture exceptions. */ - handleException(error: unknown): void { + private _handleException(error: unknown): void { __DEBUG_BUILD__ && logger.error('[Replay]', error); if (__DEBUG_BUILD__ && this._options._experiments && this._options._experiments.captureExceptions) { @@ -288,21 +343,11 @@ export class ReplayContainer implements ReplayContainerInterface { } } - /** for tests only */ - clearSession(): void { - try { - deleteSession(); - this.session = undefined; - } catch (err) { - this.handleException(err); - } - } - /** * Loads a session from storage, or creates a new one if it does not exist or * is expired. */ - loadSession({ expiry }: { expiry: number }): void { + private _loadSession({ expiry }: { expiry: number }): void { const { type, session } = getSession({ expiry, stickySession: Boolean(this._options.stickySession), @@ -314,7 +359,7 @@ export class ReplayContainer implements ReplayContainerInterface { // If session was newly created (i.e. was not loaded from storage), then // enable flag to create the root replay if (type === 'new') { - this.setInitialState(); + this._setInitialState(); } if (session.id !== this.session?.id) { @@ -329,14 +374,14 @@ export class ReplayContainer implements ReplayContainerInterface { * replay. This is required because otherwise they would be captured at the * first flush. */ - setInitialState(): void { + private _setInitialState(): void { const urlPath = `${WINDOW.location.pathname}${WINDOW.location.hash}${WINDOW.location.search}`; const url = `${WINDOW.location.origin}${urlPath}`; this.performanceEvents = []; // Reset _context as well - this.clearContext(); + this._clearContext(); this._context.initialUrl = url; this._context.initialTimestamp = new Date().getTime(); @@ -346,11 +391,11 @@ export class ReplayContainer implements ReplayContainerInterface { /** * Adds listeners to record events for the replay */ - addListeners(): void { + private _addListeners(): void { try { - WINDOW.document.addEventListener('visibilitychange', this.handleVisibilityChange); - WINDOW.addEventListener('blur', this.handleWindowBlur); - WINDOW.addEventListener('focus', this.handleWindowFocus); + WINDOW.document.addEventListener('visibilitychange', this._handleVisibilityChange); + WINDOW.addEventListener('blur', this._handleWindowBlur); + WINDOW.addEventListener('focus', this._handleWindowFocus); // We need to filter out dropped events captured by `addGlobalEventProcessor(this.handleGlobalEvent)` below overwriteRecordDroppedEvent(this._context.errorIds); @@ -359,8 +404,8 @@ export class ReplayContainer implements ReplayContainerInterface { if (!this._hasInitializedCoreListeners) { // Listeners from core SDK // const scope = getCurrentHub().getScope(); - scope?.addScopeListener(this.handleCoreBreadcrumbListener('scope')); - addInstrumentationHandler('dom', this.handleCoreBreadcrumbListener('dom')); + scope?.addScopeListener(this._handleCoreBreadcrumbListener('scope')); + addInstrumentationHandler('dom', this._handleCoreBreadcrumbListener('dom')); addInstrumentationHandler('fetch', handleFetchSpanListener(this)); addInstrumentationHandler('xhr', handleXhrSpanListener(this)); addInstrumentationHandler('history', handleHistorySpanListener(this)); @@ -372,7 +417,7 @@ export class ReplayContainer implements ReplayContainerInterface { this._hasInitializedCoreListeners = true; } } catch (err) { - this.handleException(err); + this._handleException(err); } // _performanceObserver // @@ -384,14 +429,14 @@ export class ReplayContainer implements ReplayContainerInterface { } /** - * Cleans up listeners that were created in `addListeners` + * Cleans up listeners that were created in `_addListeners` */ - removeListeners(): void { + private _removeListeners(): void { try { - WINDOW.document.removeEventListener('visibilitychange', this.handleVisibilityChange); + WINDOW.document.removeEventListener('visibilitychange', this._handleVisibilityChange); - WINDOW.removeEventListener('blur', this.handleWindowBlur); - WINDOW.removeEventListener('focus', this.handleWindowFocus); + WINDOW.removeEventListener('blur', this._handleWindowBlur); + WINDOW.removeEventListener('focus', this._handleWindowFocus); restoreRecordDroppedEvent(); @@ -400,37 +445,8 @@ export class ReplayContainer implements ReplayContainerInterface { this._performanceObserver = null; } } catch (err) { - this.handleException(err); - } - } - - /** - * We want to batch uploads of replay events. Save events only if - * `` milliseconds have elapsed since the last event - * *OR* if `` milliseconds have elapsed. - * - * Accepts a callback to perform side-effects and returns true to stop batch - * processing and hand back control to caller. - */ - addUpdate(cb: AddUpdateCallback): void { - // We need to always run `cb` (e.g. in the case of `this.recordingMode == 'error'`) - const cbResult = cb?.(); - - // If this option is turned on then we will only want to call `flush` - // explicitly - if (this.recordingMode === 'error') { - return; - } - - // If callback is true, we do not want to continue with flushing -- the - // caller will need to handle it. - if (cbResult === true) { - return; + this._handleException(err); } - - // addUpdate is called quite frequently - use _debouncedFlush so that it - // respects the flush delays and does not flush immediately - this._debouncedFlush(); } /** @@ -438,12 +454,12 @@ export class ReplayContainer implements ReplayContainerInterface { * * Adds to event buffer, and has varying flushing behaviors if the event was a checkout. */ - handleRecordingEmit: (event: RecordingEvent, isCheckout?: boolean) => void = ( + private _handleRecordingEmit: (event: RecordingEvent, isCheckout?: boolean) => void = ( event: RecordingEvent, isCheckout?: boolean, ) => { // If this is false, it means session is expired, create and a new session and wait for checkout - if (!this.checkAndHandleExpiredSession()) { + if (!this._checkAndHandleExpiredSession()) { __DEBUG_BUILD__ && logger.error('[Replay] Received replay event after session expired.'); return; @@ -456,12 +472,12 @@ export class ReplayContainer implements ReplayContainerInterface { // checkout. This needs to happen before `addEvent()` which updates state // dependent on this reset. if (this.recordingMode === 'error' && event.type === 2) { - this.setInitialState(); + this._setInitialState(); } // We need to clear existing events on a checkout, otherwise they are // incremental event updates and should be appended - addEvent(this, event, isCheckout); + void addEvent(this, event, isCheckout); // Different behavior for full snapshots (type=2), ignore other event types // See https://github.com/rrweb-io/rrweb/blob/d8f9290ca496712aa1e7d472549480c4e7876594/packages/rrweb/src/types.ts#L16 @@ -504,38 +520,38 @@ export class ReplayContainer implements ReplayContainerInterface { * be hidden. Likewise, moving a different window to cover the contents of the * page will also trigger a change to a hidden state. */ - handleVisibilityChange: () => void = () => { + private _handleVisibilityChange: () => void = () => { if (WINDOW.document.visibilityState === 'visible') { - this.doChangeToForegroundTasks(); + this._doChangeToForegroundTasks(); } else { - this.doChangeToBackgroundTasks(); + this._doChangeToBackgroundTasks(); } }; /** * Handle when page is blurred */ - handleWindowBlur: () => void = () => { + private _handleWindowBlur: () => void = () => { const breadcrumb = createBreadcrumb({ category: 'ui.blur', }); // Do not count blur as a user action -- it's part of the process of them // leaving the page - this.doChangeToBackgroundTasks(breadcrumb); + this._doChangeToBackgroundTasks(breadcrumb); }; /** * Handle when page is focused */ - handleWindowFocus: () => void = () => { + private _handleWindowFocus: () => void = () => { const breadcrumb = createBreadcrumb({ category: 'ui.focus', }); // Do not count focus as a user action -- instead wait until they focus and // interactive with page - this.doChangeToForegroundTasks(breadcrumb); + this._doChangeToForegroundTasks(breadcrumb); }; /** @@ -543,7 +559,7 @@ export class ReplayContainer implements ReplayContainerInterface { * * These events will create breadcrumb-like objects in the recording. */ - handleCoreBreadcrumbListener: (type: InstrumentationTypeBreadcrumb) => (handlerData: unknown) => void = + private _handleCoreBreadcrumbListener: (type: InstrumentationTypeBreadcrumb) => (handlerData: unknown) => void = (type: InstrumentationTypeBreadcrumb) => (handlerData: unknown): void => { if (!this._isEnabled) { @@ -563,11 +579,11 @@ export class ReplayContainer implements ReplayContainerInterface { if (result.category === 'ui.click') { this.triggerUserActivity(); } else { - this.checkAndHandleExpiredSession(); + this._checkAndHandleExpiredSession(); } this.addUpdate(() => { - addEvent(this, { + void addEvent(this, { type: EventType.Custom, // TODO: We were converting from ms to seconds for breadcrumbs, spans, // but maybe we should just keep them as milliseconds @@ -586,7 +602,7 @@ export class ReplayContainer implements ReplayContainerInterface { /** * Tasks to run when we consider a page to be hidden (via blurring and/or visibility) */ - doChangeToBackgroundTasks(breadcrumb?: Breadcrumb): void { + private _doChangeToBackgroundTasks(breadcrumb?: Breadcrumb): void { if (!this.session) { return; } @@ -594,24 +610,24 @@ export class ReplayContainer implements ReplayContainerInterface { const expired = isSessionExpired(this.session, VISIBILITY_CHANGE_TIMEOUT); if (breadcrumb && !expired) { - this.createCustomBreadcrumb(breadcrumb); + this._createCustomBreadcrumb(breadcrumb); } // Send replay when the page/tab becomes hidden. There is no reason to send // replay if it becomes visible, since no actions we care about were done // while it was hidden - this.conditionalFlush(); + this._conditionalFlush(); } /** * Tasks to run when we consider a page to be visible (via focus and/or visibility) */ - doChangeToForegroundTasks(breadcrumb?: Breadcrumb): void { + private _doChangeToForegroundTasks(breadcrumb?: Breadcrumb): void { if (!this.session) { return; } - const isSessionActive = this.checkAndHandleExpiredSession({ + const isSessionActive = this._checkAndHandleExpiredSession({ expiry: VISIBILITY_CHANGE_TIMEOUT, }); @@ -624,7 +640,7 @@ export class ReplayContainer implements ReplayContainerInterface { } if (breadcrumb) { - this.createCustomBreadcrumb(breadcrumb); + this._createCustomBreadcrumb(breadcrumb); } } @@ -632,7 +648,7 @@ export class ReplayContainer implements ReplayContainerInterface { * Trigger rrweb to take a full snapshot which will cause this plugin to * create a new Replay event. */ - triggerFullSnapshot(): void { + private _triggerFullSnapshot(): void { __DEBUG_BUILD__ && logger.log('[Replay] Taking full rrweb snapshot'); record.takeFullSnapshot(true); } @@ -640,52 +656,26 @@ export class ReplayContainer implements ReplayContainerInterface { /** * Update user activity (across session lifespans) */ - updateUserActivity(_lastActivity: number = new Date().getTime()): void { + private _updateUserActivity(_lastActivity: number = new Date().getTime()): void { this._lastActivity = _lastActivity; } /** * Updates the session's last activity timestamp */ - updateSessionActivity(_lastActivity: number = new Date().getTime()): void { + private _updateSessionActivity(_lastActivity: number = new Date().getTime()): void { if (this.session) { this.session.lastActivity = _lastActivity; this._maybeSaveSession(); } } - /** - * Updates the user activity timestamp and resumes recording. This should be - * called in an event handler for a user action that we consider as the user - * being "active" (e.g. a mouse click). - */ - triggerUserActivity(): void { - this.updateUserActivity(); - - // This case means that recording was once stopped due to inactivity. - // Ensure that recording is resumed. - if (!this._stopRecording) { - // Create a new session, otherwise when the user action is flushed, it - // will get rejected due to an expired session. - this.loadSession({ expiry: SESSION_IDLE_DURATION }); - - // Note: This will cause a new DOM checkout - this.resume(); - return; - } - - // Otherwise... recording was never suspended, continue as normalish - this.checkAndHandleExpiredSession(); - - this.updateSessionActivity(); - } - /** * Helper to create (and buffer) a replay breadcrumb from a core SDK breadcrumb */ - createCustomBreadcrumb(breadcrumb: Breadcrumb): void { + private _createCustomBreadcrumb(breadcrumb: Breadcrumb): void { this.addUpdate(() => { - addEvent(this, { + void addEvent(this, { type: EventType.Custom, timestamp: breadcrumb.timestamp || 0, data: { @@ -700,12 +690,12 @@ export class ReplayContainer implements ReplayContainerInterface { * Observed performance events are added to `this.performanceEvents`. These * are included in the replay event before it is finished and sent to Sentry. */ - addPerformanceEntries(): void { + private _addPerformanceEntries(): Promise> { // Copy and reset entries before processing const entries = [...this.performanceEvents]; this.performanceEvents = []; - createPerformanceSpans(this, createPerformanceEntries(entries)); + return Promise.all(createPerformanceSpans(this, createPerformanceEntries(entries))); } /** @@ -715,7 +705,7 @@ export class ReplayContainer implements ReplayContainerInterface { * * Returns true if session is not expired, false otherwise. */ - checkAndHandleExpiredSession({ expiry = SESSION_IDLE_DURATION }: { expiry?: number } = {}): boolean | void { + private _checkAndHandleExpiredSession({ expiry = SESSION_IDLE_DURATION }: { expiry?: number } = {}): boolean | void { const oldSessionId = this.session?.id; // Prevent starting a new session if the last user activity is older than @@ -730,7 +720,7 @@ export class ReplayContainer implements ReplayContainerInterface { // --- There is recent user activity --- // // This will create a new session if expired, based on expiry length - this.loadSession({ expiry }); + this._loadSession({ expiry }); // Session was expired if session ids do not match const expired = oldSessionId !== this.session?.id; @@ -740,7 +730,7 @@ export class ReplayContainer implements ReplayContainerInterface { } // Session is expired, trigger a full snapshot (which will create a new session) - this.triggerFullSnapshot(); + this._triggerFullSnapshot(); return false; } @@ -748,7 +738,7 @@ export class ReplayContainer implements ReplayContainerInterface { /** * Only flush if `this.recordingMode === 'session'` */ - conditionalFlush(): void { + private _conditionalFlush(): void { if (this.recordingMode === 'error') { return; } @@ -759,7 +749,7 @@ export class ReplayContainer implements ReplayContainerInterface { /** * Clear _context */ - clearContext(): void { + private _clearContext(): void { // XXX: `initialTimestamp` and `initialUrl` do not get cleared this._context.errorIds.clear(); this._context.traceIds.clear(); @@ -770,7 +760,7 @@ export class ReplayContainer implements ReplayContainerInterface { /** * Return and clear _context */ - popEventContext(): PopEventContext { + private _popEventContext(): PopEventContext { if (this._context.earliestEvent && this._context.earliestEvent < this._context.initialTimestamp) { this._context.initialTimestamp = this._context.earliestEvent; } @@ -783,7 +773,7 @@ export class ReplayContainer implements ReplayContainerInterface { urls: this._context.urls, }; - this.clearContext(); + this._clearContext(); return _context; } @@ -796,15 +786,15 @@ export class ReplayContainer implements ReplayContainerInterface { * * Should never be called directly, only by `flush` */ - async runFlush(): Promise { + private async _runFlush(): Promise { if (!this.session) { __DEBUG_BUILD__ && logger.error('[Replay] No session found to flush.'); return; } - await this.addPerformanceEntries(); + await this._addPerformanceEntries(); - if (!this.eventBuffer?.length) { + if (!this.eventBuffer?.pendingLength) { return; } @@ -818,20 +808,26 @@ export class ReplayContainer implements ReplayContainerInterface { // NOTE: Copy values from instance members, as it's possible they could // change before the flush finishes. const replayId = this.session.id; - const eventContext = this.popEventContext(); + const eventContext = this._popEventContext(); // Always increment segmentId regardless of outcome of sending replay const segmentId = this.session.segmentId++; this._maybeSaveSession(); - await this.sendReplay({ + await sendReplay({ replayId, - events: recordingData, + recordingData, segmentId, includeReplayStartTimestamp: segmentId === 0, eventContext, + session: this.session, + options: this.getOptions(), + timestamp: new Date().getTime(), }); } catch (err) { - this.handleException(err); + if (err instanceof RateLimitError) { + this._handleRateLimit(err.rateLimits); + } + this._handleException(err); } } @@ -839,14 +835,14 @@ export class ReplayContainer implements ReplayContainerInterface { * Flush recording data to Sentry. Creates a lock so that only a single flush * can be active at a time. Do not call this directly. */ - flush: () => Promise = async () => { + private _flush: () => Promise = async () => { if (!this._isEnabled) { // This is just a precaution, there should be no listeners that would // cause a flush. return; } - if (!this.checkAndHandleExpiredSession()) { + if (!this._checkAndHandleExpiredSession()) { __DEBUG_BUILD__ && logger.error('[Replay] Attempting to finish replay event after session expired.'); return; } @@ -859,16 +855,16 @@ export class ReplayContainer implements ReplayContainerInterface { // A flush is about to happen, cancel any queued flushes this._debouncedFlush?.cancel(); - // this._flushLock acts as a lock so that future calls to `flush()` + // this._flushLock acts as a lock so that future calls to `_flush()` // will be blocked until this promise resolves if (!this._flushLock) { - this._flushLock = this.runFlush(); + this._flushLock = this._runFlush(); await this._flushLock; this._flushLock = null; return; } - // Wait for previous flush to finish, then call the debounced `flush()`. + // Wait for previous flush to finish, then call the debounced `_flush()`. // It's possible there are other flush requests queued and waiting for it // to resolve. We want to reduce all outstanding requests (as well as any // new flush requests that occur within a second of the locked flush @@ -883,192 +879,35 @@ export class ReplayContainer implements ReplayContainerInterface { } }; - /** - * - * Always flush via `_debouncedFlush` so that we do not have flushes triggered - * from calling both `flush` and `_debouncedFlush`. Otherwise, there could be - * cases of mulitple flushes happening closely together. - */ - flushImmediate(): Promise { - this._debouncedFlush(); - // `.flush` is provided by the debounced function, analogously to lodash.debounce - return this._debouncedFlush.flush() as Promise; - } - - /** - * Send replay attachment using `fetch()` - */ - async sendReplayRequest({ - events, - replayId, - segmentId: segment_id, - includeReplayStartTimestamp, - eventContext, - }: SendReplay): Promise { - const payloadWithSequence = createPayload({ - events, - headers: { - segment_id, - }, - }); - - const { urls, errorIds, traceIds, initialTimestamp } = eventContext; - - const currentTimestamp = new Date().getTime(); - - const hub = getCurrentHub(); - const client = hub.getClient(); - const scope = hub.getScope(); - const transport = client && client.getTransport(); - const dsn = client?.getDsn(); - - if (!client || !scope || !transport || !dsn) { - return; - } - - const baseEvent: ReplayEvent = { - // @ts-ignore private api - type: REPLAY_EVENT_NAME, - ...(includeReplayStartTimestamp ? { replay_start_timestamp: initialTimestamp / 1000 } : {}), - timestamp: currentTimestamp / 1000, - error_ids: errorIds, - trace_ids: traceIds, - urls, - replay_id: replayId, - segment_id, - }; - - const replayEvent = await getReplayEvent({ scope, client, event: baseEvent }); - - if (!replayEvent) { - // Taken from baseclient's `_processEvent` method, where this is handled for errors/transactions - client.recordDroppedEvent('event_processor', 'replay_event', baseEvent); - __DEBUG_BUILD__ && logger.log('An event processor returned `null`, will not send event.'); - return; - } - - replayEvent.tags = { - ...replayEvent.tags, - sessionSampleRate: this._options.sessionSampleRate, - errorSampleRate: this._options.errorSampleRate, - replayType: this.session?.sampled, - }; - - /* - For reference, the fully built event looks something like this: - { - "type": "replay_event", - "timestamp": 1670837008.634, - "error_ids": [ - "errorId" - ], - "trace_ids": [ - "traceId" - ], - "urls": [ - "https://example.com" - ], - "replay_id": "eventId", - "segment_id": 3, - "platform": "javascript", - "event_id": "generated-uuid", - "environment": "production", - "sdk": { - "integrations": [ - "BrowserTracing", - "Replay" - ], - "name": "sentry.javascript.browser", - "version": "7.25.0" - }, - "sdkProcessingMetadata": {}, - "tags": { - "sessionSampleRate": 1, - "errorSampleRate": 0, - "replayType": "error" - } - } - */ - - const envelope = createReplayEnvelope(replayEvent, payloadWithSequence, dsn, client.getOptions().tunnel); - - try { - return await transport.send(envelope); - } catch { - throw new Error(UNABLE_TO_SEND_REPLAY); + /** Save the session, if it is sticky */ + private _maybeSaveSession(): void { + if (this.session && this._options.stickySession) { + saveSession(this.session); } } - resetRetries(): void { - this._retryCount = 0; - this._retryInterval = BASE_RETRY_INTERVAL; - } - /** - * Finalize and send the current replay event to Sentry + * Pauses the replay and resumes it after the rate-limit duration is over. */ - async sendReplay({ - replayId, - events, - segmentId, - includeReplayStartTimestamp, - eventContext, - }: SendReplay): Promise { - // short circuit if there's no events to upload (this shouldn't happen as runFlush makes this check) - if (!events.length) { + private _handleRateLimit(rateLimits: RateLimits): void { + // in case recording is already paused, we don't need to do anything, as we might have already paused because of a + // rate limit + if (this.isPaused()) { return; } - try { - await this.sendReplayRequest({ - events, - replayId, - segmentId, - includeReplayStartTimestamp, - eventContext, - }); - this.resetRetries(); - return true; - } catch (err) { - // Capture error for every failed replay - setContext('Replays', { - _retryCount: this._retryCount, - }); - this.handleException(err); - - // If an error happened here, it's likely that uploading the attachment - // failed, we'll can retry with the same events payload - if (this._retryCount >= MAX_RETRY_COUNT) { - throw new Error(`${UNABLE_TO_SEND_REPLAY} - max retries exceeded`); - } + const rateLimitEnd = disabledUntil(rateLimits, 'replay'); + const rateLimitDuration = rateLimitEnd - Date.now(); - this._retryCount = this._retryCount + 1; - // will retry in intervals of 5, 10, 30 - this._retryInterval = this._retryCount * this._retryInterval; - - return await new Promise((resolve, reject) => { - setTimeout(async () => { - try { - await this.sendReplay({ - replayId, - events, - segmentId, - includeReplayStartTimestamp, - eventContext, - }); - resolve(true); - } catch (err) { - reject(err); - } - }, this._retryInterval); - }); - } - } + if (rateLimitDuration > 0) { + __DEBUG_BUILD__ && logger.warn('[Replay]', `Rate limit hit, pausing replay for ${rateLimitDuration}ms`); + this.pause(); + this._debouncedFlush && this._debouncedFlush.cancel(); - /** Save the session, if it is sticky */ - private _maybeSaveSession(): void { - if (this.session && this._options.stickySession) { - saveSession(this.session); + setTimeout(() => { + __DEBUG_BUILD__ && logger.info('[Replay]', 'Resuming replay after rate limit'); + this.resume(); + }, rateLimitDuration); } } } diff --git a/packages/replay/src/session/saveSession.ts b/packages/replay/src/session/saveSession.ts index a506625436f8..8f75d0ab50ed 100644 --- a/packages/replay/src/session/saveSession.ts +++ b/packages/replay/src/session/saveSession.ts @@ -1,6 +1,9 @@ import { REPLAY_SESSION_KEY, WINDOW } from '../constants'; import type { Session } from '../types'; +/** + * Save a session to session storage. + */ export function saveSession(session: Session): void { const hasSessionStorage = 'sessionStorage' in WINDOW; if (!hasSessionStorage) { diff --git a/packages/replay/src/types.ts b/packages/replay/src/types.ts index c331273b135e..5d97766d6d00 100644 --- a/packages/replay/src/types.ts +++ b/packages/replay/src/types.ts @@ -1,22 +1,21 @@ -import { ReplayRecordingData } from '@sentry/types'; +import type { ReplayRecordingData, ReplayRecordingMode } from '@sentry/types'; import type { eventWithTime, recordOptions } from './types/rrweb'; export type RecordingEvent = eventWithTime; export type RecordingOptions = recordOptions; -export type RecordedEvents = Uint8Array | string; - export type AllPerformanceEntry = PerformancePaintTiming | PerformanceResourceTiming | PerformanceNavigationTiming; -export type ReplayRecordingMode = 'session' | 'error'; - -export interface SendReplay { - events: RecordedEvents; +export interface SendReplayData { + recordingData: ReplayRecordingData; replayId: string; segmentId: number; includeReplayStartTimestamp: boolean; eventContext: PopEventContext; + timestamp: number; + session: Session; + options: ReplayPluginOptions; } export type InstrumentationTypeBreadcrumb = 'dom' | 'scope'; @@ -49,9 +48,11 @@ export interface WorkerResponse { id: number; method: string; success: boolean; - response: ReplayRecordingData; + response: unknown; } +export type AddEventResult = void; + export interface SampleRates { /** * The sample rate for session-long replays. 1.0 will record all sessions and @@ -210,9 +211,31 @@ export interface Session { } export interface EventBuffer { - readonly length: number; + /** + * The number of raw events that are buffered + */ + readonly pendingLength: number; + + /** + * The raw events that are buffered. + */ + readonly pendingEvents: RecordingEvent[]; + + /** + * Destroy the event buffer. + */ destroy(): void; - addEvent(event: RecordingEvent, isCheckout?: boolean): void; + + /** + * Add an event to the event buffer. + * + * Returns true if event was successfully added. + */ + addEvent(event: RecordingEvent, isCheckout?: boolean): Promise; + + /** + * Clears and returns the contents of the buffer. + */ finish(): Promise; } @@ -237,3 +260,30 @@ export interface ReplayContainer { addUpdate(cb: AddUpdateCallback): void; getOptions(): ReplayPluginOptions; } + +export interface ReplayPerformanceEntry { + /** + * One of these types https://developer.mozilla.org/en-US/docs/Web/API/PerformanceEntry/entryType + */ + type: string; + + /** + * A more specific description of the performance entry + */ + name: string; + + /** + * The start timestamp in seconds + */ + start: number; + + /** + * The end timestamp in seconds + */ + end: number; + + /** + * Additional unstructured data to be included + */ + data?: Record; +} diff --git a/packages/replay/src/util/addEvent.ts b/packages/replay/src/util/addEvent.ts index 34086a4725c5..3bcc16f03af5 100644 --- a/packages/replay/src/util/addEvent.ts +++ b/packages/replay/src/util/addEvent.ts @@ -1,18 +1,22 @@ import { SESSION_IDLE_DURATION } from '../constants'; -import type { RecordingEvent, ReplayContainer } from '../types'; +import type { AddEventResult, RecordingEvent, ReplayContainer } from '../types'; /** * Add an event to the event buffer */ -export function addEvent(replay: ReplayContainer, event: RecordingEvent, isCheckout?: boolean): void { +export async function addEvent( + replay: ReplayContainer, + event: RecordingEvent, + isCheckout?: boolean, +): Promise { if (!replay.eventBuffer) { // This implies that `_isEnabled` is false - return; + return null; } if (replay.isPaused()) { // Do not add to event buffer when recording is paused - return; + return null; } // TODO: sadness -- we will want to normalize timestamps to be in ms - @@ -25,7 +29,7 @@ export function addEvent(replay: ReplayContainer, event: RecordingEvent, isCheck // comes back to trigger a new session. The performance entries rely on // `performance.timeOrigin`, which is when the page first opened. if (timestampInMs + SESSION_IDLE_DURATION < new Date().getTime()) { - return; + return null; } // Only record earliest event if a new session was created, otherwise it @@ -35,5 +39,5 @@ export function addEvent(replay: ReplayContainer, event: RecordingEvent, isCheck replay.getContext().earliestEvent = timestampInMs; } - replay.eventBuffer.addEvent(event, isCheckout); + return replay.eventBuffer.addEvent(event, isCheckout); } diff --git a/packages/replay/src/util/addMemoryEntry.ts b/packages/replay/src/util/addMemoryEntry.ts index bac07200cd6c..8d31e06cd9d9 100644 --- a/packages/replay/src/util/addMemoryEntry.ts +++ b/packages/replay/src/util/addMemoryEntry.ts @@ -1,19 +1,50 @@ import { WINDOW } from '../constants'; -import type { ReplayContainer } from '../types'; +import type { AddEventResult, ReplayContainer, ReplayPerformanceEntry } from '../types'; import { createPerformanceSpans } from './createPerformanceSpans'; +type ReplayMemoryEntry = ReplayPerformanceEntry & { data: { memory: MemoryInfo } }; + +interface MemoryInfo { + jsHeapSizeLimit: number; + totalJSHeapSize: number; + usedJSHeapSize: number; +} + /** * Create a "span" for the total amount of memory being used by JS objects * (including v8 internal objects). */ -export function addMemoryEntry(replay: ReplayContainer): void { +export async function addMemoryEntry(replay: ReplayContainer): Promise> { // window.performance.memory is a non-standard API and doesn't work on all browsers, so we try-catch this try { - createPerformanceSpans(replay, [ - // @ts-ignore memory doesn't exist on type Performance as the API is non-standard (we check that it exists above) - createMemoryEntry(WINDOW.performance.memory), - ]); + return Promise.all( + createPerformanceSpans(replay, [ + // @ts-ignore memory doesn't exist on type Performance as the API is non-standard (we check that it exists above) + createMemoryEntry(WINDOW.performance.memory), + ]), + ); } catch (error) { // Do nothing + return []; } } + +function createMemoryEntry(memoryEntry: MemoryInfo): ReplayMemoryEntry { + const { jsHeapSizeLimit, totalJSHeapSize, usedJSHeapSize } = memoryEntry; + // we don't want to use `getAbsoluteTime` because it adds the event time to the + // time origin, so we get the current timestamp instead + const time = new Date().getTime() / 1000; + return { + type: 'memory', + name: 'memory', + start: time, + end: time, + data: { + memory: { + jsHeapSizeLimit, + totalJSHeapSize, + usedJSHeapSize, + }, + }, + }; +} diff --git a/packages/replay/src/util/createBreadcrumb.ts b/packages/replay/src/util/createBreadcrumb.ts index bb1d2eb49ec1..b9f7527b0180 100644 --- a/packages/replay/src/util/createBreadcrumb.ts +++ b/packages/replay/src/util/createBreadcrumb.ts @@ -2,6 +2,9 @@ import type { Breadcrumb } from '@sentry/types'; type RequiredProperties = 'category' | 'message'; +/** + * Create a breadcrumb for a replay. + */ export function createBreadcrumb( breadcrumb: Pick & Partial>, ): Breadcrumb { diff --git a/packages/replay/src/createPerformanceEntry.ts b/packages/replay/src/util/createPerformanceEntries.ts similarity index 73% rename from packages/replay/src/createPerformanceEntry.ts rename to packages/replay/src/util/createPerformanceEntries.ts index c4fb293429dd..8088ecc55171 100644 --- a/packages/replay/src/createPerformanceEntry.ts +++ b/packages/replay/src/util/createPerformanceEntries.ts @@ -1,41 +1,13 @@ import { browserPerformanceTimeOrigin } from '@sentry/utils'; import { record } from 'rrweb'; -import { WINDOW } from './constants'; -import type { AllPerformanceEntry, PerformanceNavigationTiming, PerformancePaintTiming } from './types'; - -export interface ReplayPerformanceEntry { - /** - * One of these types https://developer.mozilla.org/en-US/docs/Web/API/PerformanceEntry/entryType - */ - type: string; - - /** - * A more specific description of the performance entry - */ - name: string; - - /** - * The start timestamp in seconds - */ - start: number; - - /** - * The end timestamp in seconds - */ - end: number; - - /** - * Additional unstructured data to be included - */ - data?: Record; -} - -interface MemoryInfo { - jsHeapSizeLimit: number; - totalJSHeapSize: number; - usedJSHeapSize: number; -} +import { WINDOW } from '../constants'; +import type { + AllPerformanceEntry, + PerformanceNavigationTiming, + PerformancePaintTiming, + ReplayPerformanceEntry, +} from '../types'; // Map entryType -> function to normalize data for event // @ts-ignore TODO: entry type does not fit the create* functions entry type @@ -49,6 +21,9 @@ const ENTRY_TYPES: Record null | ReplayP ['largest-contentful-paint']: createLargestContentfulPaint, }; +/** + * Create replay performance entries from the browser performance entries. + */ export function createPerformanceEntries(entries: AllPerformanceEntry[]): ReplayPerformanceEntry[] { return entries.map(createPerformanceEntry).filter(Boolean) as ReplayPerformanceEntry[]; } @@ -147,25 +122,3 @@ function createLargestContentfulPaint(entry: PerformanceEntry & { size: number; }, }; } - -type ReplayMemoryEntry = ReplayPerformanceEntry & { data: { memory: MemoryInfo } }; - -export function createMemoryEntry(memoryEntry: MemoryInfo): ReplayMemoryEntry { - const { jsHeapSizeLimit, totalJSHeapSize, usedJSHeapSize } = memoryEntry; - // we don't want to use `getAbsoluteTime` because it adds the event time to the - // time origin, so we get the current timestamp instead - const time = new Date().getTime() / 1000; - return { - type: 'memory', - name: 'memory', - start: time, - end: time, - data: { - memory: { - jsHeapSizeLimit, - totalJSHeapSize, - usedJSHeapSize, - }, - }, - }; -} diff --git a/packages/replay/src/util/createPerformanceSpans.ts b/packages/replay/src/util/createPerformanceSpans.ts index 9bb999a0faa3..ba8e86577972 100644 --- a/packages/replay/src/util/createPerformanceSpans.ts +++ b/packages/replay/src/util/createPerformanceSpans.ts @@ -1,14 +1,16 @@ import { EventType } from 'rrweb'; -import { ReplayPerformanceEntry } from '../createPerformanceEntry'; -import type { ReplayContainer } from '../types'; +import type { AddEventResult, ReplayContainer, ReplayPerformanceEntry } from '../types'; import { addEvent } from './addEvent'; /** * Create a "span" for each performance entry. The parent transaction is `this.replayEvent`. */ -export function createPerformanceSpans(replay: ReplayContainer, entries: ReplayPerformanceEntry[]): void { - entries.map(({ type, start, end, name, data }) => +export function createPerformanceSpans( + replay: ReplayContainer, + entries: ReplayPerformanceEntry[], +): Promise[] { + return entries.map(({ type, start, end, name, data }) => addEvent(replay, { type: EventType.Custom, timestamp: start, diff --git a/packages/replay/src/util/createReplayEnvelope.ts b/packages/replay/src/util/createReplayEnvelope.ts index 3d950e06cb29..afc445e83d71 100644 --- a/packages/replay/src/util/createReplayEnvelope.ts +++ b/packages/replay/src/util/createReplayEnvelope.ts @@ -1,6 +1,10 @@ -import { DsnComponents, ReplayEnvelope, ReplayEvent, ReplayRecordingData } from '@sentry/types'; +import type { DsnComponents, ReplayEnvelope, ReplayEvent, ReplayRecordingData } from '@sentry/types'; import { createEnvelope, createEventEnvelopeHeaders, getSdkMetadataForEnvelopeHeader } from '@sentry/utils'; +/** + * Create a replay envelope ready to be sent. + * This includes both the replay event, as well as the recording data. + */ export function createReplayEnvelope( replayEvent: ReplayEvent, recordingData: ReplayRecordingData, diff --git a/packages/replay/src/util/debounce.ts b/packages/replay/src/util/debounce.ts index fafd541fa98b..88057c14a4c5 100644 --- a/packages/replay/src/util/debounce.ts +++ b/packages/replay/src/util/debounce.ts @@ -1,7 +1,7 @@ type DebouncedCallback = { + (): void | unknown; flush: () => void | unknown; cancel: () => void; - (): void | unknown; }; type CallbackFunction = () => unknown; type DebounceOptions = { maxWait?: number }; diff --git a/packages/replay/src/util/isBrowser.ts b/packages/replay/src/util/isBrowser.ts index 3ad78dce93a5..6a64317ba3fa 100644 --- a/packages/replay/src/util/isBrowser.ts +++ b/packages/replay/src/util/isBrowser.ts @@ -1,5 +1,8 @@ import { isNodeEnv } from '@sentry/utils'; +/** + * Returns true if we are in the browser. + */ export function isBrowser(): boolean { // eslint-disable-next-line no-restricted-globals return typeof window !== 'undefined' && (!isNodeEnv() || isElectronNodeRenderer()); diff --git a/packages/replay/src/util/isRrwebError.ts b/packages/replay/src/util/isRrwebError.ts index d9b065857062..b706e27f97cd 100644 --- a/packages/replay/src/util/isRrwebError.ts +++ b/packages/replay/src/util/isRrwebError.ts @@ -1,5 +1,8 @@ -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; +/** + * Returns true if we think the given event is an error originating inside of rrweb. + */ export function isRrwebError(event: Event): boolean { if (event.type || !event.exception?.values?.length) { return false; diff --git a/packages/replay/src/util/monkeyPatchRecordDroppedEvent.ts b/packages/replay/src/util/monkeyPatchRecordDroppedEvent.ts index 76825f727e6b..6a819ef917b2 100644 --- a/packages/replay/src/util/monkeyPatchRecordDroppedEvent.ts +++ b/packages/replay/src/util/monkeyPatchRecordDroppedEvent.ts @@ -1,8 +1,11 @@ import { getCurrentHub } from '@sentry/core'; -import { Client, DataCategory, Event, EventDropReason } from '@sentry/types'; +import type { Client, DataCategory, Event, EventDropReason } from '@sentry/types'; let _originalRecordDroppedEvent: Client['recordDroppedEvent'] | undefined; +/** + * Overwrite the `recordDroppedEvent` method on the client, so we can find out which events were dropped. + * */ export function overwriteRecordDroppedEvent(errorIds: Set): void { const client = getCurrentHub().getClient(); @@ -28,6 +31,9 @@ export function overwriteRecordDroppedEvent(errorIds: Set): void { _originalRecordDroppedEvent = _originalCallback; } +/** + * Restore the original method. + * */ export function restoreRecordDroppedEvent(): void { const client = getCurrentHub().getClient(); diff --git a/packages/replay/src/util/createPayload.ts b/packages/replay/src/util/prepareRecordingData.ts similarity index 52% rename from packages/replay/src/util/createPayload.ts rename to packages/replay/src/util/prepareRecordingData.ts index b3b6615b1b40..2ee3391d6f42 100644 --- a/packages/replay/src/util/createPayload.ts +++ b/packages/replay/src/util/prepareRecordingData.ts @@ -1,12 +1,13 @@ -import { ReplayRecordingData } from '@sentry/types'; +import type { ReplayRecordingData } from '@sentry/types'; -import type { RecordedEvents } from '../types'; - -export function createPayload({ - events, +/** + * Prepare the recording data ready to be sent. + */ +export function prepareRecordingData({ + recordingData, headers, }: { - events: RecordedEvents; + recordingData: ReplayRecordingData; headers: Record; }): ReplayRecordingData { let payloadWithSequence; @@ -15,16 +16,16 @@ export function createPayload({ const replayHeaders = `${JSON.stringify(headers)} `; - if (typeof events === 'string') { - payloadWithSequence = `${replayHeaders}${events}`; + if (typeof recordingData === 'string') { + payloadWithSequence = `${replayHeaders}${recordingData}`; } else { const enc = new TextEncoder(); // XXX: newline is needed to separate sequence id from events const sequence = enc.encode(replayHeaders); // Merge the two Uint8Arrays - payloadWithSequence = new Uint8Array(sequence.length + events.length); + payloadWithSequence = new Uint8Array(sequence.length + recordingData.length); payloadWithSequence.set(sequence); - payloadWithSequence.set(events, sequence.length); + payloadWithSequence.set(recordingData, sequence.length); } return payloadWithSequence; diff --git a/packages/replay/src/util/getReplayEvent.ts b/packages/replay/src/util/prepareReplayEvent.ts similarity index 72% rename from packages/replay/src/util/getReplayEvent.ts rename to packages/replay/src/util/prepareReplayEvent.ts index f41c6f549515..d9227f50cfca 100644 --- a/packages/replay/src/util/getReplayEvent.ts +++ b/packages/replay/src/util/prepareReplayEvent.ts @@ -1,16 +1,22 @@ -import { prepareEvent, Scope } from '@sentry/core'; -import { Client, ReplayEvent } from '@sentry/types'; +import type { Scope } from '@sentry/core'; +import { prepareEvent } from '@sentry/core'; +import type { Client, ReplayEvent } from '@sentry/types'; -export async function getReplayEvent({ +/** + * Prepare a replay event & enrich it with the SDK metadata. + */ +export async function prepareReplayEvent({ client, scope, + replayId: event_id, event, }: { client: Client; scope: Scope; + replayId: string; event: ReplayEvent; }): Promise { - const preparedEvent = (await prepareEvent(client.getOptions(), event, {}, scope)) as ReplayEvent | null; + const preparedEvent = (await prepareEvent(client.getOptions(), event, { event_id }, scope)) as ReplayEvent | null; // If e.g. a global event processor returned null if (!preparedEvent) { diff --git a/packages/replay/src/util/sendReplay.ts b/packages/replay/src/util/sendReplay.ts new file mode 100644 index 000000000000..aa2e4649dda7 --- /dev/null +++ b/packages/replay/src/util/sendReplay.ts @@ -0,0 +1,61 @@ +import { captureException, setContext } from '@sentry/core'; + +import { RETRY_BASE_INTERVAL, RETRY_MAX_COUNT, UNABLE_TO_SEND_REPLAY } from '../constants'; +import type { SendReplayData } from '../types'; +import { RateLimitError, sendReplayRequest } from './sendReplayRequest'; + +/** + * Finalize and send the current replay event to Sentry + */ +export async function sendReplay( + replayData: SendReplayData, + retryConfig = { + count: 0, + interval: RETRY_BASE_INTERVAL, + }, +): Promise { + const { recordingData, options } = replayData; + + // short circuit if there's no events to upload (this shouldn't happen as _runFlush makes this check) + if (!recordingData.length) { + return; + } + + try { + await sendReplayRequest(replayData); + return true; + } catch (err) { + if (err instanceof RateLimitError) { + throw err; + } + + // Capture error for every failed replay + setContext('Replays', { + _retryCount: retryConfig.count, + }); + + if (__DEBUG_BUILD__ && options._experiments && options._experiments.captureExceptions) { + captureException(err); + } + + // If an error happened here, it's likely that uploading the attachment + // failed, we'll can retry with the same events payload + if (retryConfig.count >= RETRY_MAX_COUNT) { + throw new Error(`${UNABLE_TO_SEND_REPLAY} - max retries exceeded`); + } + + // will retry in intervals of 5, 10, 30 + retryConfig.interval *= ++retryConfig.count; + + return await new Promise((resolve, reject) => { + setTimeout(async () => { + try { + await sendReplay(replayData, retryConfig); + resolve(true); + } catch (err) { + reject(err); + } + }, retryConfig.interval); + }); + } +} diff --git a/packages/replay/src/util/sendReplayRequest.ts b/packages/replay/src/util/sendReplayRequest.ts new file mode 100644 index 000000000000..23df14ffcdda --- /dev/null +++ b/packages/replay/src/util/sendReplayRequest.ts @@ -0,0 +1,138 @@ +import { getCurrentHub } from '@sentry/core'; +import type { ReplayEvent, TransportMakeRequestResponse } from '@sentry/types'; +import type { RateLimits } from '@sentry/utils'; +import { isRateLimited, logger, updateRateLimits } from '@sentry/utils'; + +import { REPLAY_EVENT_NAME, UNABLE_TO_SEND_REPLAY } from '../constants'; +import type { SendReplayData } from '../types'; +import { createReplayEnvelope } from './createReplayEnvelope'; +import { prepareRecordingData } from './prepareRecordingData'; +import { prepareReplayEvent } from './prepareReplayEvent'; + +/** + * Send replay attachment using `fetch()` + */ +export async function sendReplayRequest({ + recordingData, + replayId, + segmentId: segment_id, + includeReplayStartTimestamp, + eventContext, + timestamp, + session, + options, +}: SendReplayData): Promise { + const preparedRecordingData = prepareRecordingData({ + recordingData, + headers: { + segment_id, + }, + }); + + const { urls, errorIds, traceIds, initialTimestamp } = eventContext; + + const hub = getCurrentHub(); + const client = hub.getClient(); + const scope = hub.getScope(); + const transport = client && client.getTransport(); + const dsn = client?.getDsn(); + + if (!client || !scope || !transport || !dsn || !session.sampled) { + return; + } + + const baseEvent: ReplayEvent = { + // @ts-ignore private api + type: REPLAY_EVENT_NAME, + ...(includeReplayStartTimestamp ? { replay_start_timestamp: initialTimestamp / 1000 } : {}), + timestamp: timestamp / 1000, + error_ids: errorIds, + trace_ids: traceIds, + urls, + replay_id: replayId, + segment_id, + replay_type: session.sampled, + }; + + const replayEvent = await prepareReplayEvent({ scope, client, replayId, event: baseEvent }); + + if (!replayEvent) { + // Taken from baseclient's `_processEvent` method, where this is handled for errors/transactions + client.recordDroppedEvent('event_processor', 'replay_event', baseEvent); + __DEBUG_BUILD__ && logger.log('An event processor returned `null`, will not send event.'); + return; + } + + replayEvent.tags = { + ...replayEvent.tags, + sessionSampleRate: options.sessionSampleRate, + errorSampleRate: options.errorSampleRate, + }; + + /* + For reference, the fully built event looks something like this: + { + "type": "replay_event", + "timestamp": 1670837008.634, + "error_ids": [ + "errorId" + ], + "trace_ids": [ + "traceId" + ], + "urls": [ + "https://example.com" + ], + "replay_id": "eventId", + "segment_id": 3, + "replay_type": "error", + "platform": "javascript", + "event_id": "eventId", + "environment": "production", + "sdk": { + "integrations": [ + "BrowserTracing", + "Replay" + ], + "name": "sentry.javascript.browser", + "version": "7.25.0" + }, + "sdkProcessingMetadata": {}, + "tags": { + "sessionSampleRate": 1, + "errorSampleRate": 0, + } + } + */ + + const envelope = createReplayEnvelope(replayEvent, preparedRecordingData, dsn, client.getOptions().tunnel); + + let response: void | TransportMakeRequestResponse; + + try { + response = await transport.send(envelope); + } catch { + throw new Error(UNABLE_TO_SEND_REPLAY); + } + + // TODO (v8): we can remove this guard once transport.send's type signature doesn't include void anymore + if (response) { + const rateLimits = updateRateLimits({}, response); + if (isRateLimited(rateLimits, 'replay')) { + throw new RateLimitError(rateLimits); + } + } + return response; +} + +/** + * This error indicates that we hit a rate limit API error. + */ +export class RateLimitError extends Error { + public rateLimits: RateLimits; + + public constructor(rateLimits: RateLimits) { + super('Rate limit hit'); + this.rateLimits = rateLimits; + } +} diff --git a/packages/replay/src/worker/worker.js b/packages/replay/src/worker/worker.js index 9d043a3c5857..553a89077500 100644 --- a/packages/replay/src/worker/worker.js +++ b/packages/replay/src/worker/worker.js @@ -1,2 +1,2 @@ export default `/*! pako 2.1.0 https://github.com/nodeca/pako @license (MIT AND Zlib) */ -function t(t){let e=t.length;for(;--e>=0;)t[e]=0}const e=new Uint8Array([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0]),a=new Uint8Array([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13]),i=new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7]),n=new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),s=new Array(576);t(s);const r=new Array(60);t(r);const o=new Array(512);t(o);const l=new Array(256);t(l);const h=new Array(29);t(h);const d=new Array(30);function _(t,e,a,i,n){this.static_tree=t,this.extra_bits=e,this.extra_base=a,this.elems=i,this.max_length=n,this.has_stree=t&&t.length}let f,c,u;function w(t,e){this.dyn_tree=t,this.max_code=0,this.stat_desc=e}t(d);const m=t=>t<256?o[t]:o[256+(t>>>7)],b=(t,e)=>{t.pending_buf[t.pending++]=255&e,t.pending_buf[t.pending++]=e>>>8&255},g=(t,e,a)=>{t.bi_valid>16-a?(t.bi_buf|=e<>16-t.bi_valid,t.bi_valid+=a-16):(t.bi_buf|=e<{g(t,a[2*e],a[2*e+1])},k=(t,e)=>{let a=0;do{a|=1&t,t>>>=1,a<<=1}while(--e>0);return a>>>1},v=(t,e,a)=>{const i=new Array(16);let n,s,r=0;for(n=1;n<=15;n++)r=r+a[n-1]<<1,i[n]=r;for(s=0;s<=e;s++){let e=t[2*s+1];0!==e&&(t[2*s]=k(i[e]++,e))}},y=t=>{let e;for(e=0;e<286;e++)t.dyn_ltree[2*e]=0;for(e=0;e<30;e++)t.dyn_dtree[2*e]=0;for(e=0;e<19;e++)t.bl_tree[2*e]=0;t.dyn_ltree[512]=1,t.opt_len=t.static_len=0,t.sym_next=t.matches=0},x=t=>{t.bi_valid>8?b(t,t.bi_buf):t.bi_valid>0&&(t.pending_buf[t.pending++]=t.bi_buf),t.bi_buf=0,t.bi_valid=0},z=(t,e,a,i)=>{const n=2*e,s=2*a;return t[n]{const i=t.heap[a];let n=a<<1;for(;n<=t.heap_len&&(n{let s,r,o,_,f=0;if(0!==t.sym_next)do{s=255&t.pending_buf[t.sym_buf+f++],s+=(255&t.pending_buf[t.sym_buf+f++])<<8,r=t.pending_buf[t.sym_buf+f++],0===s?p(t,r,i):(o=l[r],p(t,o+256+1,i),_=e[o],0!==_&&(r-=h[o],g(t,r,_)),s--,o=m(s),p(t,o,n),_=a[o],0!==_&&(s-=d[o],g(t,s,_)))}while(f{const a=e.dyn_tree,i=e.stat_desc.static_tree,n=e.stat_desc.has_stree,s=e.stat_desc.elems;let r,o,l,h=-1;for(t.heap_len=0,t.heap_max=573,r=0;r>1;r>=1;r--)A(t,a,r);l=s;do{r=t.heap[1],t.heap[1]=t.heap[t.heap_len--],A(t,a,1),o=t.heap[1],t.heap[--t.heap_max]=r,t.heap[--t.heap_max]=o,a[2*l]=a[2*r]+a[2*o],t.depth[l]=(t.depth[r]>=t.depth[o]?t.depth[r]:t.depth[o])+1,a[2*r+1]=a[2*o+1]=l,t.heap[1]=l++,A(t,a,1)}while(t.heap_len>=2);t.heap[--t.heap_max]=t.heap[1],((t,e)=>{const a=e.dyn_tree,i=e.max_code,n=e.stat_desc.static_tree,s=e.stat_desc.has_stree,r=e.stat_desc.extra_bits,o=e.stat_desc.extra_base,l=e.stat_desc.max_length;let h,d,_,f,c,u,w=0;for(f=0;f<=15;f++)t.bl_count[f]=0;for(a[2*t.heap[t.heap_max]+1]=0,h=t.heap_max+1;h<573;h++)d=t.heap[h],f=a[2*a[2*d+1]+1]+1,f>l&&(f=l,w++),a[2*d+1]=f,d>i||(t.bl_count[f]++,c=0,d>=o&&(c=r[d-o]),u=a[2*d],t.opt_len+=u*(f+c),s&&(t.static_len+=u*(n[2*d+1]+c)));if(0!==w){do{for(f=l-1;0===t.bl_count[f];)f--;t.bl_count[f]--,t.bl_count[f+1]+=2,t.bl_count[l]--,w-=2}while(w>0);for(f=l;0!==f;f--)for(d=t.bl_count[f];0!==d;)_=t.heap[--h],_>i||(a[2*_+1]!==f&&(t.opt_len+=(f-a[2*_+1])*a[2*_],a[2*_+1]=f),d--)}})(t,e),v(a,h,t.bl_count)},Z=(t,e,a)=>{let i,n,s=-1,r=e[1],o=0,l=7,h=4;for(0===r&&(l=138,h=3),e[2*(a+1)+1]=65535,i=0;i<=a;i++)n=r,r=e[2*(i+1)+1],++o{let i,n,s=-1,r=e[1],o=0,l=7,h=4;for(0===r&&(l=138,h=3),i=0;i<=a;i++)if(n=r,r=e[2*(i+1)+1],!(++o{g(t,0+(i?1:0),3),x(t),b(t,a),b(t,~a),a&&t.pending_buf.set(t.window.subarray(e,e+a),t.pending),t.pending+=a};var O=(t,e,a,i)=>{let o,l,h=0;t.level>0?(2===t.strm.data_type&&(t.strm.data_type=(t=>{let e,a=4093624447;for(e=0;e<=31;e++,a>>>=1)if(1&a&&0!==t.dyn_ltree[2*e])return 0;if(0!==t.dyn_ltree[18]||0!==t.dyn_ltree[20]||0!==t.dyn_ltree[26])return 1;for(e=32;e<256;e++)if(0!==t.dyn_ltree[2*e])return 1;return 0})(t)),R(t,t.l_desc),R(t,t.d_desc),h=(t=>{let e;for(Z(t,t.dyn_ltree,t.l_desc.max_code),Z(t,t.dyn_dtree,t.d_desc.max_code),R(t,t.bl_desc),e=18;e>=3&&0===t.bl_tree[2*n[e]+1];e--);return t.opt_len+=3*(e+1)+5+5+4,e})(t),o=t.opt_len+3+7>>>3,l=t.static_len+3+7>>>3,l<=o&&(o=l)):o=l=a+5,a+4<=o&&-1!==e?D(t,e,a,i):4===t.strategy||l===o?(g(t,2+(i?1:0),3),E(t,s,r)):(g(t,4+(i?1:0),3),((t,e,a,i)=>{let s;for(g(t,e-257,5),g(t,a-1,5),g(t,i-4,4),s=0;s{U||((()=>{let t,n,w,m,b;const g=new Array(16);for(w=0,m=0;m<28;m++)for(h[m]=w,t=0;t<1<>=7;m<30;m++)for(d[m]=b<<7,t=0;t<1<(t.pending_buf[t.sym_buf+t.sym_next++]=e,t.pending_buf[t.sym_buf+t.sym_next++]=e>>8,t.pending_buf[t.sym_buf+t.sym_next++]=a,0===e?t.dyn_ltree[2*a]++:(t.matches++,e--,t.dyn_ltree[2*(l[a]+256+1)]++,t.dyn_dtree[2*m(e)]++),t.sym_next===t.sym_end),_tr_align:t=>{g(t,2,3),p(t,256,s),(t=>{16===t.bi_valid?(b(t,t.bi_buf),t.bi_buf=0,t.bi_valid=0):t.bi_valid>=8&&(t.pending_buf[t.pending++]=255&t.bi_buf,t.bi_buf>>=8,t.bi_valid-=8)})(t)}};var N=(t,e,a,i)=>{let n=65535&t|0,s=t>>>16&65535|0,r=0;for(;0!==a;){r=a>2e3?2e3:a,a-=r;do{n=n+e[i++]|0,s=s+n|0}while(--r);n%=65521,s%=65521}return n|s<<16|0};const F=new Uint32Array((()=>{let t,e=[];for(var a=0;a<256;a++){t=a;for(var i=0;i<8;i++)t=1&t?3988292384^t>>>1:t>>>1;e[a]=t}return e})());var L=(t,e,a,i)=>{const n=F,s=i+a;t^=-1;for(let a=i;a>>8^n[255&(t^e[a])];return-1^t},I={2:"need dictionary",1:"stream end",0:"","-1":"file error","-2":"stream error","-3":"data error","-4":"insufficient memory","-5":"buffer error","-6":"incompatible version"},B={Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_TREES:6,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_MEM_ERROR:-4,Z_BUF_ERROR:-5,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,Z_BINARY:0,Z_TEXT:1,Z_UNKNOWN:2,Z_DEFLATED:8};const{_tr_init:C,_tr_stored_block:H,_tr_flush_block:M,_tr_tally:j,_tr_align:K}=T,{Z_NO_FLUSH:P,Z_PARTIAL_FLUSH:Y,Z_FULL_FLUSH:G,Z_FINISH:X,Z_BLOCK:J,Z_OK:W,Z_STREAM_END:q,Z_STREAM_ERROR:Q,Z_DATA_ERROR:V,Z_BUF_ERROR:$,Z_DEFAULT_COMPRESSION:tt,Z_FILTERED:et,Z_HUFFMAN_ONLY:at,Z_RLE:it,Z_FIXED:nt,Z_DEFAULT_STRATEGY:st,Z_UNKNOWN:rt,Z_DEFLATED:ot}=B,lt=(t,e)=>(t.msg=I[e],e),ht=t=>2*t-(t>4?9:0),dt=t=>{let e=t.length;for(;--e>=0;)t[e]=0},_t=t=>{let e,a,i,n=t.w_size;e=t.hash_size,i=e;do{a=t.head[--i],t.head[i]=a>=n?a-n:0}while(--e);e=n,i=e;do{a=t.prev[--i],t.prev[i]=a>=n?a-n:0}while(--e)};let ft=(t,e,a)=>(e<{const e=t.state;let a=e.pending;a>t.avail_out&&(a=t.avail_out),0!==a&&(t.output.set(e.pending_buf.subarray(e.pending_out,e.pending_out+a),t.next_out),t.next_out+=a,e.pending_out+=a,t.total_out+=a,t.avail_out-=a,e.pending-=a,0===e.pending&&(e.pending_out=0))},ut=(t,e)=>{M(t,t.block_start>=0?t.block_start:-1,t.strstart-t.block_start,e),t.block_start=t.strstart,ct(t.strm)},wt=(t,e)=>{t.pending_buf[t.pending++]=e},mt=(t,e)=>{t.pending_buf[t.pending++]=e>>>8&255,t.pending_buf[t.pending++]=255&e},bt=(t,e,a,i)=>{let n=t.avail_in;return n>i&&(n=i),0===n?0:(t.avail_in-=n,e.set(t.input.subarray(t.next_in,t.next_in+n),a),1===t.state.wrap?t.adler=N(t.adler,e,n,a):2===t.state.wrap&&(t.adler=L(t.adler,e,n,a)),t.next_in+=n,t.total_in+=n,n)},gt=(t,e)=>{let a,i,n=t.max_chain_length,s=t.strstart,r=t.prev_length,o=t.nice_match;const l=t.strstart>t.w_size-262?t.strstart-(t.w_size-262):0,h=t.window,d=t.w_mask,_=t.prev,f=t.strstart+258;let c=h[s+r-1],u=h[s+r];t.prev_length>=t.good_match&&(n>>=2),o>t.lookahead&&(o=t.lookahead);do{if(a=e,h[a+r]===u&&h[a+r-1]===c&&h[a]===h[s]&&h[++a]===h[s+1]){s+=2,a++;do{}while(h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&sr){if(t.match_start=e,r=i,i>=o)break;c=h[s+r-1],u=h[s+r]}}}while((e=_[e&d])>l&&0!=--n);return r<=t.lookahead?r:t.lookahead},pt=t=>{const e=t.w_size;let a,i,n;do{if(i=t.window_size-t.lookahead-t.strstart,t.strstart>=e+(e-262)&&(t.window.set(t.window.subarray(e,e+e-i),0),t.match_start-=e,t.strstart-=e,t.block_start-=e,t.insert>t.strstart&&(t.insert=t.strstart),_t(t),i+=e),0===t.strm.avail_in)break;if(a=bt(t.strm,t.window,t.strstart+t.lookahead,i),t.lookahead+=a,t.lookahead+t.insert>=3)for(n=t.strstart-t.insert,t.ins_h=t.window[n],t.ins_h=ft(t,t.ins_h,t.window[n+1]);t.insert&&(t.ins_h=ft(t,t.ins_h,t.window[n+3-1]),t.prev[n&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=n,n++,t.insert--,!(t.lookahead+t.insert<3)););}while(t.lookahead<262&&0!==t.strm.avail_in)},kt=(t,e)=>{let a,i,n,s=t.pending_buf_size-5>t.w_size?t.w_size:t.pending_buf_size-5,r=0,o=t.strm.avail_in;do{if(a=65535,n=t.bi_valid+42>>3,t.strm.avail_outi+t.strm.avail_in&&(a=i+t.strm.avail_in),a>n&&(a=n),a>8,t.pending_buf[t.pending-2]=~a,t.pending_buf[t.pending-1]=~a>>8,ct(t.strm),i&&(i>a&&(i=a),t.strm.output.set(t.window.subarray(t.block_start,t.block_start+i),t.strm.next_out),t.strm.next_out+=i,t.strm.avail_out-=i,t.strm.total_out+=i,t.block_start+=i,a-=i),a&&(bt(t.strm,t.strm.output,t.strm.next_out,a),t.strm.next_out+=a,t.strm.avail_out-=a,t.strm.total_out+=a)}while(0===r);return o-=t.strm.avail_in,o&&(o>=t.w_size?(t.matches=2,t.window.set(t.strm.input.subarray(t.strm.next_in-t.w_size,t.strm.next_in),0),t.strstart=t.w_size,t.insert=t.strstart):(t.window_size-t.strstart<=o&&(t.strstart-=t.w_size,t.window.set(t.window.subarray(t.w_size,t.w_size+t.strstart),0),t.matches<2&&t.matches++,t.insert>t.strstart&&(t.insert=t.strstart)),t.window.set(t.strm.input.subarray(t.strm.next_in-o,t.strm.next_in),t.strstart),t.strstart+=o,t.insert+=o>t.w_size-t.insert?t.w_size-t.insert:o),t.block_start=t.strstart),t.high_watern&&t.block_start>=t.w_size&&(t.block_start-=t.w_size,t.strstart-=t.w_size,t.window.set(t.window.subarray(t.w_size,t.w_size+t.strstart),0),t.matches<2&&t.matches++,n+=t.w_size,t.insert>t.strstart&&(t.insert=t.strstart)),n>t.strm.avail_in&&(n=t.strm.avail_in),n&&(bt(t.strm,t.window,t.strstart,n),t.strstart+=n,t.insert+=n>t.w_size-t.insert?t.w_size-t.insert:n),t.high_water>3,n=t.pending_buf_size-n>65535?65535:t.pending_buf_size-n,s=n>t.w_size?t.w_size:n,i=t.strstart-t.block_start,(i>=s||(i||e===X)&&e!==P&&0===t.strm.avail_in&&i<=n)&&(a=i>n?n:i,r=e===X&&0===t.strm.avail_in&&a===i?1:0,H(t,t.block_start,a,r),t.block_start+=a,ct(t.strm)),r?3:1)},vt=(t,e)=>{let a,i;for(;;){if(t.lookahead<262){if(pt(t),t.lookahead<262&&e===P)return 1;if(0===t.lookahead)break}if(a=0,t.lookahead>=3&&(t.ins_h=ft(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart),0!==a&&t.strstart-a<=t.w_size-262&&(t.match_length=gt(t,a)),t.match_length>=3)if(i=j(t,t.strstart-t.match_start,t.match_length-3),t.lookahead-=t.match_length,t.match_length<=t.max_lazy_match&&t.lookahead>=3){t.match_length--;do{t.strstart++,t.ins_h=ft(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart}while(0!=--t.match_length);t.strstart++}else t.strstart+=t.match_length,t.match_length=0,t.ins_h=t.window[t.strstart],t.ins_h=ft(t,t.ins_h,t.window[t.strstart+1]);else i=j(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++;if(i&&(ut(t,!1),0===t.strm.avail_out))return 1}return t.insert=t.strstart<2?t.strstart:2,e===X?(ut(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(ut(t,!1),0===t.strm.avail_out)?1:2},yt=(t,e)=>{let a,i,n;for(;;){if(t.lookahead<262){if(pt(t),t.lookahead<262&&e===P)return 1;if(0===t.lookahead)break}if(a=0,t.lookahead>=3&&(t.ins_h=ft(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart),t.prev_length=t.match_length,t.prev_match=t.match_start,t.match_length=2,0!==a&&t.prev_length4096)&&(t.match_length=2)),t.prev_length>=3&&t.match_length<=t.prev_length){n=t.strstart+t.lookahead-3,i=j(t,t.strstart-1-t.prev_match,t.prev_length-3),t.lookahead-=t.prev_length-1,t.prev_length-=2;do{++t.strstart<=n&&(t.ins_h=ft(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart)}while(0!=--t.prev_length);if(t.match_available=0,t.match_length=2,t.strstart++,i&&(ut(t,!1),0===t.strm.avail_out))return 1}else if(t.match_available){if(i=j(t,0,t.window[t.strstart-1]),i&&ut(t,!1),t.strstart++,t.lookahead--,0===t.strm.avail_out)return 1}else t.match_available=1,t.strstart++,t.lookahead--}return t.match_available&&(i=j(t,0,t.window[t.strstart-1]),t.match_available=0),t.insert=t.strstart<2?t.strstart:2,e===X?(ut(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(ut(t,!1),0===t.strm.avail_out)?1:2};function xt(t,e,a,i,n){this.good_length=t,this.max_lazy=e,this.nice_length=a,this.max_chain=i,this.func=n}const zt=[new xt(0,0,0,0,kt),new xt(4,4,8,4,vt),new xt(4,5,16,8,vt),new xt(4,6,32,32,vt),new xt(4,4,16,16,yt),new xt(8,16,32,32,yt),new xt(8,16,128,128,yt),new xt(8,32,128,256,yt),new xt(32,128,258,1024,yt),new xt(32,258,258,4096,yt)];function At(){this.strm=null,this.status=0,this.pending_buf=null,this.pending_buf_size=0,this.pending_out=0,this.pending=0,this.wrap=0,this.gzhead=null,this.gzindex=0,this.method=ot,this.last_flush=-1,this.w_size=0,this.w_bits=0,this.w_mask=0,this.window=null,this.window_size=0,this.prev=null,this.head=null,this.ins_h=0,this.hash_size=0,this.hash_bits=0,this.hash_mask=0,this.hash_shift=0,this.block_start=0,this.match_length=0,this.prev_match=0,this.match_available=0,this.strstart=0,this.match_start=0,this.lookahead=0,this.prev_length=0,this.max_chain_length=0,this.max_lazy_match=0,this.level=0,this.strategy=0,this.good_match=0,this.nice_match=0,this.dyn_ltree=new Uint16Array(1146),this.dyn_dtree=new Uint16Array(122),this.bl_tree=new Uint16Array(78),dt(this.dyn_ltree),dt(this.dyn_dtree),dt(this.bl_tree),this.l_desc=null,this.d_desc=null,this.bl_desc=null,this.bl_count=new Uint16Array(16),this.heap=new Uint16Array(573),dt(this.heap),this.heap_len=0,this.heap_max=0,this.depth=new Uint16Array(573),dt(this.depth),this.sym_buf=0,this.lit_bufsize=0,this.sym_next=0,this.sym_end=0,this.opt_len=0,this.static_len=0,this.matches=0,this.insert=0,this.bi_buf=0,this.bi_valid=0}const Et=t=>{if(!t)return 1;const e=t.state;return!e||e.strm!==t||42!==e.status&&57!==e.status&&69!==e.status&&73!==e.status&&91!==e.status&&103!==e.status&&113!==e.status&&666!==e.status?1:0},Rt=t=>{if(Et(t))return lt(t,Q);t.total_in=t.total_out=0,t.data_type=rt;const e=t.state;return e.pending=0,e.pending_out=0,e.wrap<0&&(e.wrap=-e.wrap),e.status=2===e.wrap?57:e.wrap?42:113,t.adler=2===e.wrap?0:1,e.last_flush=-2,C(e),W},Zt=t=>{const e=Rt(t);var a;return e===W&&((a=t.state).window_size=2*a.w_size,dt(a.head),a.max_lazy_match=zt[a.level].max_lazy,a.good_match=zt[a.level].good_length,a.nice_match=zt[a.level].nice_length,a.max_chain_length=zt[a.level].max_chain,a.strstart=0,a.block_start=0,a.lookahead=0,a.insert=0,a.match_length=a.prev_length=2,a.match_available=0,a.ins_h=0),e},St=(t,e,a,i,n,s)=>{if(!t)return Q;let r=1;if(e===tt&&(e=6),i<0?(r=0,i=-i):i>15&&(r=2,i-=16),n<1||n>9||a!==ot||i<8||i>15||e<0||e>9||s<0||s>nt||8===i&&1!==r)return lt(t,Q);8===i&&(i=9);const o=new At;return t.state=o,o.strm=t,o.status=42,o.wrap=r,o.gzhead=null,o.w_bits=i,o.w_size=1<St(t,e,ot,15,8,st),deflateInit2:St,deflateReset:Zt,deflateResetKeep:Rt,deflateSetHeader:(t,e)=>Et(t)||2!==t.state.wrap?Q:(t.state.gzhead=e,W),deflate:(t,e)=>{if(Et(t)||e>J||e<0)return t?lt(t,Q):Q;const a=t.state;if(!t.output||0!==t.avail_in&&!t.input||666===a.status&&e!==X)return lt(t,0===t.avail_out?$:Q);const i=a.last_flush;if(a.last_flush=e,0!==a.pending){if(ct(t),0===t.avail_out)return a.last_flush=-1,W}else if(0===t.avail_in&&ht(e)<=ht(i)&&e!==X)return lt(t,$);if(666===a.status&&0!==t.avail_in)return lt(t,$);if(42===a.status&&0===a.wrap&&(a.status=113),42===a.status){let e=ot+(a.w_bits-8<<4)<<8,i=-1;if(i=a.strategy>=at||a.level<2?0:a.level<6?1:6===a.level?2:3,e|=i<<6,0!==a.strstart&&(e|=32),e+=31-e%31,mt(a,e),0!==a.strstart&&(mt(a,t.adler>>>16),mt(a,65535&t.adler)),t.adler=1,a.status=113,ct(t),0!==a.pending)return a.last_flush=-1,W}if(57===a.status)if(t.adler=0,wt(a,31),wt(a,139),wt(a,8),a.gzhead)wt(a,(a.gzhead.text?1:0)+(a.gzhead.hcrc?2:0)+(a.gzhead.extra?4:0)+(a.gzhead.name?8:0)+(a.gzhead.comment?16:0)),wt(a,255&a.gzhead.time),wt(a,a.gzhead.time>>8&255),wt(a,a.gzhead.time>>16&255),wt(a,a.gzhead.time>>24&255),wt(a,9===a.level?2:a.strategy>=at||a.level<2?4:0),wt(a,255&a.gzhead.os),a.gzhead.extra&&a.gzhead.extra.length&&(wt(a,255&a.gzhead.extra.length),wt(a,a.gzhead.extra.length>>8&255)),a.gzhead.hcrc&&(t.adler=L(t.adler,a.pending_buf,a.pending,0)),a.gzindex=0,a.status=69;else if(wt(a,0),wt(a,0),wt(a,0),wt(a,0),wt(a,0),wt(a,9===a.level?2:a.strategy>=at||a.level<2?4:0),wt(a,3),a.status=113,ct(t),0!==a.pending)return a.last_flush=-1,W;if(69===a.status){if(a.gzhead.extra){let e=a.pending,i=(65535&a.gzhead.extra.length)-a.gzindex;for(;a.pending+i>a.pending_buf_size;){let n=a.pending_buf_size-a.pending;if(a.pending_buf.set(a.gzhead.extra.subarray(a.gzindex,a.gzindex+n),a.pending),a.pending=a.pending_buf_size,a.gzhead.hcrc&&a.pending>e&&(t.adler=L(t.adler,a.pending_buf,a.pending-e,e)),a.gzindex+=n,ct(t),0!==a.pending)return a.last_flush=-1,W;e=0,i-=n}let n=new Uint8Array(a.gzhead.extra);a.pending_buf.set(n.subarray(a.gzindex,a.gzindex+i),a.pending),a.pending+=i,a.gzhead.hcrc&&a.pending>e&&(t.adler=L(t.adler,a.pending_buf,a.pending-e,e)),a.gzindex=0}a.status=73}if(73===a.status){if(a.gzhead.name){let e,i=a.pending;do{if(a.pending===a.pending_buf_size){if(a.gzhead.hcrc&&a.pending>i&&(t.adler=L(t.adler,a.pending_buf,a.pending-i,i)),ct(t),0!==a.pending)return a.last_flush=-1,W;i=0}e=a.gzindexi&&(t.adler=L(t.adler,a.pending_buf,a.pending-i,i)),a.gzindex=0}a.status=91}if(91===a.status){if(a.gzhead.comment){let e,i=a.pending;do{if(a.pending===a.pending_buf_size){if(a.gzhead.hcrc&&a.pending>i&&(t.adler=L(t.adler,a.pending_buf,a.pending-i,i)),ct(t),0!==a.pending)return a.last_flush=-1,W;i=0}e=a.gzindexi&&(t.adler=L(t.adler,a.pending_buf,a.pending-i,i))}a.status=103}if(103===a.status){if(a.gzhead.hcrc){if(a.pending+2>a.pending_buf_size&&(ct(t),0!==a.pending))return a.last_flush=-1,W;wt(a,255&t.adler),wt(a,t.adler>>8&255),t.adler=0}if(a.status=113,ct(t),0!==a.pending)return a.last_flush=-1,W}if(0!==t.avail_in||0!==a.lookahead||e!==P&&666!==a.status){let i=0===a.level?kt(a,e):a.strategy===at?((t,e)=>{let a;for(;;){if(0===t.lookahead&&(pt(t),0===t.lookahead)){if(e===P)return 1;break}if(t.match_length=0,a=j(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++,a&&(ut(t,!1),0===t.strm.avail_out))return 1}return t.insert=0,e===X?(ut(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(ut(t,!1),0===t.strm.avail_out)?1:2})(a,e):a.strategy===it?((t,e)=>{let a,i,n,s;const r=t.window;for(;;){if(t.lookahead<=258){if(pt(t),t.lookahead<=258&&e===P)return 1;if(0===t.lookahead)break}if(t.match_length=0,t.lookahead>=3&&t.strstart>0&&(n=t.strstart-1,i=r[n],i===r[++n]&&i===r[++n]&&i===r[++n])){s=t.strstart+258;do{}while(i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&nt.lookahead&&(t.match_length=t.lookahead)}if(t.match_length>=3?(a=j(t,1,t.match_length-3),t.lookahead-=t.match_length,t.strstart+=t.match_length,t.match_length=0):(a=j(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++),a&&(ut(t,!1),0===t.strm.avail_out))return 1}return t.insert=0,e===X?(ut(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(ut(t,!1),0===t.strm.avail_out)?1:2})(a,e):zt[a.level].func(a,e);if(3!==i&&4!==i||(a.status=666),1===i||3===i)return 0===t.avail_out&&(a.last_flush=-1),W;if(2===i&&(e===Y?K(a):e!==J&&(H(a,0,0,!1),e===G&&(dt(a.head),0===a.lookahead&&(a.strstart=0,a.block_start=0,a.insert=0))),ct(t),0===t.avail_out))return a.last_flush=-1,W}return e!==X?W:a.wrap<=0?q:(2===a.wrap?(wt(a,255&t.adler),wt(a,t.adler>>8&255),wt(a,t.adler>>16&255),wt(a,t.adler>>24&255),wt(a,255&t.total_in),wt(a,t.total_in>>8&255),wt(a,t.total_in>>16&255),wt(a,t.total_in>>24&255)):(mt(a,t.adler>>>16),mt(a,65535&t.adler)),ct(t),a.wrap>0&&(a.wrap=-a.wrap),0!==a.pending?W:q)},deflateEnd:t=>{if(Et(t))return Q;const e=t.state.status;return t.state=null,113===e?lt(t,V):W},deflateSetDictionary:(t,e)=>{let a=e.length;if(Et(t))return Q;const i=t.state,n=i.wrap;if(2===n||1===n&&42!==i.status||i.lookahead)return Q;if(1===n&&(t.adler=N(t.adler,e,a,0)),i.wrap=0,a>=i.w_size){0===n&&(dt(i.head),i.strstart=0,i.block_start=0,i.insert=0);let t=new Uint8Array(i.w_size);t.set(e.subarray(a-i.w_size,a),0),e=t,a=i.w_size}const s=t.avail_in,r=t.next_in,o=t.input;for(t.avail_in=a,t.next_in=0,t.input=e,pt(i);i.lookahead>=3;){let t=i.strstart,e=i.lookahead-2;do{i.ins_h=ft(i,i.ins_h,i.window[t+3-1]),i.prev[t&i.w_mask]=i.head[i.ins_h],i.head[i.ins_h]=t,t++}while(--e);i.strstart=t,i.lookahead=2,pt(i)}return i.strstart+=i.lookahead,i.block_start=i.strstart,i.insert=i.lookahead,i.lookahead=0,i.match_length=i.prev_length=2,i.match_available=0,t.next_in=r,t.input=o,t.avail_in=s,i.wrap=n,W},deflateInfo:"pako deflate (from Nodeca project)"};const Dt=(t,e)=>Object.prototype.hasOwnProperty.call(t,e);var Ot=function(t){const e=Array.prototype.slice.call(arguments,1);for(;e.length;){const a=e.shift();if(a){if("object"!=typeof a)throw new TypeError(a+"must be non-object");for(const e in a)Dt(a,e)&&(t[e]=a[e])}}return t},Tt=t=>{let e=0;for(let a=0,i=t.length;a=252?6:t>=248?5:t>=240?4:t>=224?3:t>=192?2:1;Ft[254]=Ft[254]=1;var Lt=t=>{if("function"==typeof TextEncoder&&TextEncoder.prototype.encode)return(new TextEncoder).encode(t);let e,a,i,n,s,r=t.length,o=0;for(n=0;n>>6,e[s++]=128|63&a):a<65536?(e[s++]=224|a>>>12,e[s++]=128|a>>>6&63,e[s++]=128|63&a):(e[s++]=240|a>>>18,e[s++]=128|a>>>12&63,e[s++]=128|a>>>6&63,e[s++]=128|63&a);return e},It=(t,e)=>{const a=e||t.length;if("function"==typeof TextDecoder&&TextDecoder.prototype.decode)return(new TextDecoder).decode(t.subarray(0,e));let i,n;const s=new Array(2*a);for(n=0,i=0;i4)s[n++]=65533,i+=r-1;else{for(e&=2===r?31:3===r?15:7;r>1&&i1?s[n++]=65533:e<65536?s[n++]=e:(e-=65536,s[n++]=55296|e>>10&1023,s[n++]=56320|1023&e)}}return((t,e)=>{if(e<65534&&t.subarray&&Nt)return String.fromCharCode.apply(null,t.length===e?t:t.subarray(0,e));let a="";for(let i=0;i{(e=e||t.length)>t.length&&(e=t.length);let a=e-1;for(;a>=0&&128==(192&t[a]);)a--;return a<0||0===a?e:a+Ft[t[a]]>e?a:e};var Ct=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0};const Ht=Object.prototype.toString,{Z_NO_FLUSH:Mt,Z_SYNC_FLUSH:jt,Z_FULL_FLUSH:Kt,Z_FINISH:Pt,Z_OK:Yt,Z_STREAM_END:Gt,Z_DEFAULT_COMPRESSION:Xt,Z_DEFAULT_STRATEGY:Jt,Z_DEFLATED:Wt}=B;function qt(t){this.options=Ot({level:Xt,method:Wt,chunkSize:16384,windowBits:15,memLevel:8,strategy:Jt},t||{});let e=this.options;e.raw&&e.windowBits>0?e.windowBits=-e.windowBits:e.gzip&&e.windowBits>0&&e.windowBits<16&&(e.windowBits+=16),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new Ct,this.strm.avail_out=0;let a=Ut.deflateInit2(this.strm,e.level,e.method,e.windowBits,e.memLevel,e.strategy);if(a!==Yt)throw new Error(I[a]);if(e.header&&Ut.deflateSetHeader(this.strm,e.header),e.dictionary){let t;if(t="string"==typeof e.dictionary?Lt(e.dictionary):"[object ArrayBuffer]"===Ht.call(e.dictionary)?new Uint8Array(e.dictionary):e.dictionary,a=Ut.deflateSetDictionary(this.strm,t),a!==Yt)throw new Error(I[a]);this._dict_set=!0}}function Qt(t,e){const a=new qt(e);if(a.push(t,!0),a.err)throw a.msg||I[a.err];return a.result}qt.prototype.push=function(t,e){const a=this.strm,i=this.options.chunkSize;let n,s;if(this.ended)return!1;for(s=e===~~e?e:!0===e?Pt:Mt,"string"==typeof t?a.input=Lt(t):"[object ArrayBuffer]"===Ht.call(t)?a.input=new Uint8Array(t):a.input=t,a.next_in=0,a.avail_in=a.input.length;;)if(0===a.avail_out&&(a.output=new Uint8Array(i),a.next_out=0,a.avail_out=i),(s===jt||s===Kt)&&a.avail_out<=6)this.onData(a.output.subarray(0,a.next_out)),a.avail_out=0;else{if(n=Ut.deflate(a,s),n===Gt)return a.next_out>0&&this.onData(a.output.subarray(0,a.next_out)),n=Ut.deflateEnd(this.strm),this.onEnd(n),this.ended=!0,n===Yt;if(0!==a.avail_out){if(s>0&&a.next_out>0)this.onData(a.output.subarray(0,a.next_out)),a.avail_out=0;else if(0===a.avail_in)break}else this.onData(a.output)}return!0},qt.prototype.onData=function(t){this.chunks.push(t)},qt.prototype.onEnd=function(t){t===Yt&&(this.result=Tt(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg};var Vt={Deflate:qt,deflate:Qt,deflateRaw:function(t,e){return(e=e||{}).raw=!0,Qt(t,e)},gzip:function(t,e){return(e=e||{}).gzip=!0,Qt(t,e)},constants:B};var $t=function(t,e){let a,i,n,s,r,o,l,h,d,_,f,c,u,w,m,b,g,p,k,v,y,x,z,A;const E=t.state;a=t.next_in,z=t.input,i=a+(t.avail_in-5),n=t.next_out,A=t.output,s=n-(e-t.avail_out),r=n+(t.avail_out-257),o=E.dmax,l=E.wsize,h=E.whave,d=E.wnext,_=E.window,f=E.hold,c=E.bits,u=E.lencode,w=E.distcode,m=(1<>>24,f>>>=p,c-=p,p=g>>>16&255,0===p)A[n++]=65535&g;else{if(!(16&p)){if(0==(64&p)){g=u[(65535&g)+(f&(1<>>=p,c-=p),c<15&&(f+=z[a++]<>>24,f>>>=p,c-=p,p=g>>>16&255,!(16&p)){if(0==(64&p)){g=w[(65535&g)+(f&(1<o){t.msg="invalid distance too far back",E.mode=16209;break t}if(f>>>=p,c-=p,p=n-s,v>p){if(p=v-p,p>h&&E.sane){t.msg="invalid distance too far back",E.mode=16209;break t}if(y=0,x=_,0===d){if(y+=l-p,p2;)A[n++]=x[y++],A[n++]=x[y++],A[n++]=x[y++],k-=3;k&&(A[n++]=x[y++],k>1&&(A[n++]=x[y++]))}else{y=n-v;do{A[n++]=A[y++],A[n++]=A[y++],A[n++]=A[y++],k-=3}while(k>2);k&&(A[n++]=A[y++],k>1&&(A[n++]=A[y++]))}break}}break}}while(a>3,a-=k,c-=k<<3,f&=(1<{const l=o.bits;let h,d,_,f,c,u,w=0,m=0,b=0,g=0,p=0,k=0,v=0,y=0,x=0,z=0,A=null;const E=new Uint16Array(16),R=new Uint16Array(16);let Z,S,U,D=null;for(w=0;w<=15;w++)E[w]=0;for(m=0;m=1&&0===E[g];g--);if(p>g&&(p=g),0===g)return n[s++]=20971520,n[s++]=20971520,o.bits=1,0;for(b=1;b0&&(0===t||1!==g))return-1;for(R[1]=0,w=1;w<15;w++)R[w+1]=R[w]+E[w];for(m=0;m852||2===t&&x>592)return 1;for(;;){Z=w-v,r[m]+1=u?(S=D[r[m]-u],U=A[r[m]-u]):(S=96,U=0),h=1<>v)+d]=Z<<24|S<<16|U|0}while(0!==d);for(h=1<>=1;if(0!==h?(z&=h-1,z+=h):z=0,m++,0==--E[w]){if(w===g)break;w=e[a+r[m]]}if(w>p&&(z&f)!==_){for(0===v&&(v=p),c+=b,k=w-v,y=1<852||2===t&&x>592)return 1;_=z&f,n[_]=p<<24|k<<16|c-s|0}}return 0!==z&&(n[c+z]=w-v<<24|64<<16|0),o.bits=p,0};const{Z_FINISH:se,Z_BLOCK:re,Z_TREES:oe,Z_OK:le,Z_STREAM_END:he,Z_NEED_DICT:de,Z_STREAM_ERROR:_e,Z_DATA_ERROR:fe,Z_MEM_ERROR:ce,Z_BUF_ERROR:ue,Z_DEFLATED:we}=B,me=16209,be=t=>(t>>>24&255)+(t>>>8&65280)+((65280&t)<<8)+((255&t)<<24);function ge(){this.strm=null,this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new Uint16Array(320),this.work=new Uint16Array(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}const pe=t=>{if(!t)return 1;const e=t.state;return!e||e.strm!==t||e.mode<16180||e.mode>16211?1:0},ke=t=>{if(pe(t))return _e;const e=t.state;return t.total_in=t.total_out=e.total=0,t.msg="",e.wrap&&(t.adler=1&e.wrap),e.mode=16180,e.last=0,e.havedict=0,e.flags=-1,e.dmax=32768,e.head=null,e.hold=0,e.bits=0,e.lencode=e.lendyn=new Int32Array(852),e.distcode=e.distdyn=new Int32Array(592),e.sane=1,e.back=-1,le},ve=t=>{if(pe(t))return _e;const e=t.state;return e.wsize=0,e.whave=0,e.wnext=0,ke(t)},ye=(t,e)=>{let a;if(pe(t))return _e;const i=t.state;return e<0?(a=0,e=-e):(a=5+(e>>4),e<48&&(e&=15)),e&&(e<8||e>15)?_e:(null!==i.window&&i.wbits!==e&&(i.window=null),i.wrap=a,i.wbits=e,ve(t))},xe=(t,e)=>{if(!t)return _e;const a=new ge;t.state=a,a.strm=t,a.window=null,a.mode=16180;const i=ye(t,e);return i!==le&&(t.state=null),i};let ze,Ae,Ee=!0;const Re=t=>{if(Ee){ze=new Int32Array(512),Ae=new Int32Array(32);let e=0;for(;e<144;)t.lens[e++]=8;for(;e<256;)t.lens[e++]=9;for(;e<280;)t.lens[e++]=7;for(;e<288;)t.lens[e++]=8;for(ne(1,t.lens,0,288,ze,0,t.work,{bits:9}),e=0;e<32;)t.lens[e++]=5;ne(2,t.lens,0,32,Ae,0,t.work,{bits:5}),Ee=!1}t.lencode=ze,t.lenbits=9,t.distcode=Ae,t.distbits=5},Ze=(t,e,a,i)=>{let n;const s=t.state;return null===s.window&&(s.wsize=1<=s.wsize?(s.window.set(e.subarray(a-s.wsize,a),0),s.wnext=0,s.whave=s.wsize):(n=s.wsize-s.wnext,n>i&&(n=i),s.window.set(e.subarray(a-i,a-i+n),s.wnext),(i-=n)?(s.window.set(e.subarray(a-i,a),0),s.wnext=i,s.whave=s.wsize):(s.wnext+=n,s.wnext===s.wsize&&(s.wnext=0),s.whavexe(t,15),inflateInit2:xe,inflate:(t,e)=>{let a,i,n,s,r,o,l,h,d,_,f,c,u,w,m,b,g,p,k,v,y,x,z=0;const A=new Uint8Array(4);let E,R;const Z=new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]);if(pe(t)||!t.output||!t.input&&0!==t.avail_in)return _e;a=t.state,16191===a.mode&&(a.mode=16192),r=t.next_out,n=t.output,l=t.avail_out,s=t.next_in,i=t.input,o=t.avail_in,h=a.hold,d=a.bits,_=o,f=l,x=le;t:for(;;)switch(a.mode){case 16180:if(0===a.wrap){a.mode=16192;break}for(;d<16;){if(0===o)break t;o--,h+=i[s++]<>>8&255,a.check=L(a.check,A,2,0),h=0,d=0,a.mode=16181;break}if(a.head&&(a.head.done=!1),!(1&a.wrap)||(((255&h)<<8)+(h>>8))%31){t.msg="incorrect header check",a.mode=me;break}if((15&h)!==we){t.msg="unknown compression method",a.mode=me;break}if(h>>>=4,d-=4,y=8+(15&h),0===a.wbits&&(a.wbits=y),y>15||y>a.wbits){t.msg="invalid window size",a.mode=me;break}a.dmax=1<>8&1),512&a.flags&&4&a.wrap&&(A[0]=255&h,A[1]=h>>>8&255,a.check=L(a.check,A,2,0)),h=0,d=0,a.mode=16182;case 16182:for(;d<32;){if(0===o)break t;o--,h+=i[s++]<>>8&255,A[2]=h>>>16&255,A[3]=h>>>24&255,a.check=L(a.check,A,4,0)),h=0,d=0,a.mode=16183;case 16183:for(;d<16;){if(0===o)break t;o--,h+=i[s++]<>8),512&a.flags&&4&a.wrap&&(A[0]=255&h,A[1]=h>>>8&255,a.check=L(a.check,A,2,0)),h=0,d=0,a.mode=16184;case 16184:if(1024&a.flags){for(;d<16;){if(0===o)break t;o--,h+=i[s++]<>>8&255,a.check=L(a.check,A,2,0)),h=0,d=0}else a.head&&(a.head.extra=null);a.mode=16185;case 16185:if(1024&a.flags&&(c=a.length,c>o&&(c=o),c&&(a.head&&(y=a.head.extra_len-a.length,a.head.extra||(a.head.extra=new Uint8Array(a.head.extra_len)),a.head.extra.set(i.subarray(s,s+c),y)),512&a.flags&&4&a.wrap&&(a.check=L(a.check,i,c,s)),o-=c,s+=c,a.length-=c),a.length))break t;a.length=0,a.mode=16186;case 16186:if(2048&a.flags){if(0===o)break t;c=0;do{y=i[s+c++],a.head&&y&&a.length<65536&&(a.head.name+=String.fromCharCode(y))}while(y&&c>9&1,a.head.done=!0),t.adler=a.check=0,a.mode=16191;break;case 16189:for(;d<32;){if(0===o)break t;o--,h+=i[s++]<>>=7&d,d-=7&d,a.mode=16206;break}for(;d<3;){if(0===o)break t;o--,h+=i[s++]<>>=1,d-=1,3&h){case 0:a.mode=16193;break;case 1:if(Re(a),a.mode=16199,e===oe){h>>>=2,d-=2;break t}break;case 2:a.mode=16196;break;case 3:t.msg="invalid block type",a.mode=me}h>>>=2,d-=2;break;case 16193:for(h>>>=7&d,d-=7&d;d<32;){if(0===o)break t;o--,h+=i[s++]<>>16^65535)){t.msg="invalid stored block lengths",a.mode=me;break}if(a.length=65535&h,h=0,d=0,a.mode=16194,e===oe)break t;case 16194:a.mode=16195;case 16195:if(c=a.length,c){if(c>o&&(c=o),c>l&&(c=l),0===c)break t;n.set(i.subarray(s,s+c),r),o-=c,s+=c,l-=c,r+=c,a.length-=c;break}a.mode=16191;break;case 16196:for(;d<14;){if(0===o)break t;o--,h+=i[s++]<>>=5,d-=5,a.ndist=1+(31&h),h>>>=5,d-=5,a.ncode=4+(15&h),h>>>=4,d-=4,a.nlen>286||a.ndist>30){t.msg="too many length or distance symbols",a.mode=me;break}a.have=0,a.mode=16197;case 16197:for(;a.have>>=3,d-=3}for(;a.have<19;)a.lens[Z[a.have++]]=0;if(a.lencode=a.lendyn,a.lenbits=7,E={bits:a.lenbits},x=ne(0,a.lens,0,19,a.lencode,0,a.work,E),a.lenbits=E.bits,x){t.msg="invalid code lengths set",a.mode=me;break}a.have=0,a.mode=16198;case 16198:for(;a.have>>24,b=z>>>16&255,g=65535&z,!(m<=d);){if(0===o)break t;o--,h+=i[s++]<>>=m,d-=m,a.lens[a.have++]=g;else{if(16===g){for(R=m+2;d>>=m,d-=m,0===a.have){t.msg="invalid bit length repeat",a.mode=me;break}y=a.lens[a.have-1],c=3+(3&h),h>>>=2,d-=2}else if(17===g){for(R=m+3;d>>=m,d-=m,y=0,c=3+(7&h),h>>>=3,d-=3}else{for(R=m+7;d>>=m,d-=m,y=0,c=11+(127&h),h>>>=7,d-=7}if(a.have+c>a.nlen+a.ndist){t.msg="invalid bit length repeat",a.mode=me;break}for(;c--;)a.lens[a.have++]=y}}if(a.mode===me)break;if(0===a.lens[256]){t.msg="invalid code -- missing end-of-block",a.mode=me;break}if(a.lenbits=9,E={bits:a.lenbits},x=ne(1,a.lens,0,a.nlen,a.lencode,0,a.work,E),a.lenbits=E.bits,x){t.msg="invalid literal/lengths set",a.mode=me;break}if(a.distbits=6,a.distcode=a.distdyn,E={bits:a.distbits},x=ne(2,a.lens,a.nlen,a.ndist,a.distcode,0,a.work,E),a.distbits=E.bits,x){t.msg="invalid distances set",a.mode=me;break}if(a.mode=16199,e===oe)break t;case 16199:a.mode=16200;case 16200:if(o>=6&&l>=258){t.next_out=r,t.avail_out=l,t.next_in=s,t.avail_in=o,a.hold=h,a.bits=d,$t(t,f),r=t.next_out,n=t.output,l=t.avail_out,s=t.next_in,i=t.input,o=t.avail_in,h=a.hold,d=a.bits,16191===a.mode&&(a.back=-1);break}for(a.back=0;z=a.lencode[h&(1<>>24,b=z>>>16&255,g=65535&z,!(m<=d);){if(0===o)break t;o--,h+=i[s++]<>p)],m=z>>>24,b=z>>>16&255,g=65535&z,!(p+m<=d);){if(0===o)break t;o--,h+=i[s++]<>>=p,d-=p,a.back+=p}if(h>>>=m,d-=m,a.back+=m,a.length=g,0===b){a.mode=16205;break}if(32&b){a.back=-1,a.mode=16191;break}if(64&b){t.msg="invalid literal/length code",a.mode=me;break}a.extra=15&b,a.mode=16201;case 16201:if(a.extra){for(R=a.extra;d>>=a.extra,d-=a.extra,a.back+=a.extra}a.was=a.length,a.mode=16202;case 16202:for(;z=a.distcode[h&(1<>>24,b=z>>>16&255,g=65535&z,!(m<=d);){if(0===o)break t;o--,h+=i[s++]<>p)],m=z>>>24,b=z>>>16&255,g=65535&z,!(p+m<=d);){if(0===o)break t;o--,h+=i[s++]<>>=p,d-=p,a.back+=p}if(h>>>=m,d-=m,a.back+=m,64&b){t.msg="invalid distance code",a.mode=me;break}a.offset=g,a.extra=15&b,a.mode=16203;case 16203:if(a.extra){for(R=a.extra;d>>=a.extra,d-=a.extra,a.back+=a.extra}if(a.offset>a.dmax){t.msg="invalid distance too far back",a.mode=me;break}a.mode=16204;case 16204:if(0===l)break t;if(c=f-l,a.offset>c){if(c=a.offset-c,c>a.whave&&a.sane){t.msg="invalid distance too far back",a.mode=me;break}c>a.wnext?(c-=a.wnext,u=a.wsize-c):u=a.wnext-c,c>a.length&&(c=a.length),w=a.window}else w=n,u=r-a.offset,c=a.length;c>l&&(c=l),l-=c,a.length-=c;do{n[r++]=w[u++]}while(--c);0===a.length&&(a.mode=16200);break;case 16205:if(0===l)break t;n[r++]=a.length,l--,a.mode=16200;break;case 16206:if(a.wrap){for(;d<32;){if(0===o)break t;o--,h|=i[s++]<{if(pe(t))return _e;let e=t.state;return e.window&&(e.window=null),t.state=null,le},inflateGetHeader:(t,e)=>{if(pe(t))return _e;const a=t.state;return 0==(2&a.wrap)?_e:(a.head=e,e.done=!1,le)},inflateSetDictionary:(t,e)=>{const a=e.length;let i,n,s;return pe(t)?_e:(i=t.state,0!==i.wrap&&16190!==i.mode?_e:16190===i.mode&&(n=1,n=N(n,e,a,0),n!==i.check)?fe:(s=Ze(t,e,a,a),s?(i.mode=16210,ce):(i.havedict=1,le)))},inflateInfo:"pako inflate (from Nodeca project)"};var Ue=function(){this.text=0,this.time=0,this.xflags=0,this.os=0,this.extra=null,this.extra_len=0,this.name="",this.comment="",this.hcrc=0,this.done=!1};const De=Object.prototype.toString,{Z_NO_FLUSH:Oe,Z_FINISH:Te,Z_OK:Ne,Z_STREAM_END:Fe,Z_NEED_DICT:Le,Z_STREAM_ERROR:Ie,Z_DATA_ERROR:Be,Z_MEM_ERROR:Ce}=B;function He(t){this.options=Ot({chunkSize:65536,windowBits:15,to:""},t||{});const e=this.options;e.raw&&e.windowBits>=0&&e.windowBits<16&&(e.windowBits=-e.windowBits,0===e.windowBits&&(e.windowBits=-15)),!(e.windowBits>=0&&e.windowBits<16)||t&&t.windowBits||(e.windowBits+=32),e.windowBits>15&&e.windowBits<48&&0==(15&e.windowBits)&&(e.windowBits|=15),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new Ct,this.strm.avail_out=0;let a=Se.inflateInit2(this.strm,e.windowBits);if(a!==Ne)throw new Error(I[a]);if(this.header=new Ue,Se.inflateGetHeader(this.strm,this.header),e.dictionary&&("string"==typeof e.dictionary?e.dictionary=Lt(e.dictionary):"[object ArrayBuffer]"===De.call(e.dictionary)&&(e.dictionary=new Uint8Array(e.dictionary)),e.raw&&(a=Se.inflateSetDictionary(this.strm,e.dictionary),a!==Ne)))throw new Error(I[a])}He.prototype.push=function(t,e){const a=this.strm,i=this.options.chunkSize,n=this.options.dictionary;let s,r,o;if(this.ended)return!1;for(r=e===~~e?e:!0===e?Te:Oe,"[object ArrayBuffer]"===De.call(t)?a.input=new Uint8Array(t):a.input=t,a.next_in=0,a.avail_in=a.input.length;;){for(0===a.avail_out&&(a.output=new Uint8Array(i),a.next_out=0,a.avail_out=i),s=Se.inflate(a,r),s===Le&&n&&(s=Se.inflateSetDictionary(a,n),s===Ne?s=Se.inflate(a,r):s===Be&&(s=Le));a.avail_in>0&&s===Fe&&a.state.wrap>0&&0!==t[a.next_in];)Se.inflateReset(a),s=Se.inflate(a,r);switch(s){case Ie:case Be:case Le:case Ce:return this.onEnd(s),this.ended=!0,!1}if(o=a.avail_out,a.next_out&&(0===a.avail_out||s===Fe))if("string"===this.options.to){let t=Bt(a.output,a.next_out),e=a.next_out-t,n=It(a.output,t);a.next_out=e,a.avail_out=i-e,e&&a.output.set(a.output.subarray(t,t+e),0),this.onData(n)}else this.onData(a.output.length===a.next_out?a.output:a.output.subarray(0,a.next_out));if(s!==Ne||0!==o){if(s===Fe)return s=Se.inflateEnd(this.strm),this.onEnd(s),this.ended=!0,!0;if(0===a.avail_in)break}}return!0},He.prototype.onData=function(t){this.chunks.push(t)},He.prototype.onEnd=function(t){t===Ne&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=Tt(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg};const{Deflate:Me,deflate:je,deflateRaw:Ke,gzip:Pe}=Vt;var Ye=Me,Ge=B;const Xe=new class{constructor(){this.added=0,this.init()}init(){this.added=0,this.deflate=new Ye,this.deflate.push("[",Ge.Z_NO_FLUSH)}addEvent(t){if(!t)return;const e=this.added>0?",":"";this.deflate.push(e+JSON.stringify(t),Ge.Z_NO_FLUSH),this.added++}finish(){if(this.deflate.push("]",Ge.Z_FINISH),this.deflate.err)throw this.deflate.err;const t=this.deflate.result;return this.init(),t}},Je={init:()=>(Xe.init(),""),addEvent:t=>(Xe.addEvent(t),""),finish:()=>Xe.finish()};addEventListener("message",(function(t){const e=t.data.method,a=t.data.id,[i]=t.data.args?JSON.parse(t.data.args):[];if(e in Je&&"function"==typeof Je[e])try{const t=Je[e](i);postMessage({id:a,method:e,success:!0,response:t})}catch(t){postMessage({id:a,method:e,success:!1,response:t}),console.error(t)}}));`; +function t(t){let e=t.length;for(;--e>=0;)t[e]=0}const e=new Uint8Array([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0]),a=new Uint8Array([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13]),i=new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7]),n=new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),s=new Array(576);t(s);const r=new Array(60);t(r);const o=new Array(512);t(o);const l=new Array(256);t(l);const h=new Array(29);t(h);const d=new Array(30);function _(t,e,a,i,n){this.static_tree=t,this.extra_bits=e,this.extra_base=a,this.elems=i,this.max_length=n,this.has_stree=t&&t.length}let f,c,u;function w(t,e){this.dyn_tree=t,this.max_code=0,this.stat_desc=e}t(d);const m=t=>t<256?o[t]:o[256+(t>>>7)],b=(t,e)=>{t.pending_buf[t.pending++]=255&e,t.pending_buf[t.pending++]=e>>>8&255},g=(t,e,a)=>{t.bi_valid>16-a?(t.bi_buf|=e<>16-t.bi_valid,t.bi_valid+=a-16):(t.bi_buf|=e<{g(t,a[2*e],a[2*e+1])},k=(t,e)=>{let a=0;do{a|=1&t,t>>>=1,a<<=1}while(--e>0);return a>>>1},v=(t,e,a)=>{const i=new Array(16);let n,s,r=0;for(n=1;n<=15;n++)r=r+a[n-1]<<1,i[n]=r;for(s=0;s<=e;s++){let e=t[2*s+1];0!==e&&(t[2*s]=k(i[e]++,e))}},y=t=>{let e;for(e=0;e<286;e++)t.dyn_ltree[2*e]=0;for(e=0;e<30;e++)t.dyn_dtree[2*e]=0;for(e=0;e<19;e++)t.bl_tree[2*e]=0;t.dyn_ltree[512]=1,t.opt_len=t.static_len=0,t.sym_next=t.matches=0},x=t=>{t.bi_valid>8?b(t,t.bi_buf):t.bi_valid>0&&(t.pending_buf[t.pending++]=t.bi_buf),t.bi_buf=0,t.bi_valid=0},z=(t,e,a,i)=>{const n=2*e,s=2*a;return t[n]{const i=t.heap[a];let n=a<<1;for(;n<=t.heap_len&&(n{let s,r,o,_,f=0;if(0!==t.sym_next)do{s=255&t.pending_buf[t.sym_buf+f++],s+=(255&t.pending_buf[t.sym_buf+f++])<<8,r=t.pending_buf[t.sym_buf+f++],0===s?p(t,r,i):(o=l[r],p(t,o+256+1,i),_=e[o],0!==_&&(r-=h[o],g(t,r,_)),s--,o=m(s),p(t,o,n),_=a[o],0!==_&&(s-=d[o],g(t,s,_)))}while(f{const a=e.dyn_tree,i=e.stat_desc.static_tree,n=e.stat_desc.has_stree,s=e.stat_desc.elems;let r,o,l,h=-1;for(t.heap_len=0,t.heap_max=573,r=0;r>1;r>=1;r--)A(t,a,r);l=s;do{r=t.heap[1],t.heap[1]=t.heap[t.heap_len--],A(t,a,1),o=t.heap[1],t.heap[--t.heap_max]=r,t.heap[--t.heap_max]=o,a[2*l]=a[2*r]+a[2*o],t.depth[l]=(t.depth[r]>=t.depth[o]?t.depth[r]:t.depth[o])+1,a[2*r+1]=a[2*o+1]=l,t.heap[1]=l++,A(t,a,1)}while(t.heap_len>=2);t.heap[--t.heap_max]=t.heap[1],((t,e)=>{const a=e.dyn_tree,i=e.max_code,n=e.stat_desc.static_tree,s=e.stat_desc.has_stree,r=e.stat_desc.extra_bits,o=e.stat_desc.extra_base,l=e.stat_desc.max_length;let h,d,_,f,c,u,w=0;for(f=0;f<=15;f++)t.bl_count[f]=0;for(a[2*t.heap[t.heap_max]+1]=0,h=t.heap_max+1;h<573;h++)d=t.heap[h],f=a[2*a[2*d+1]+1]+1,f>l&&(f=l,w++),a[2*d+1]=f,d>i||(t.bl_count[f]++,c=0,d>=o&&(c=r[d-o]),u=a[2*d],t.opt_len+=u*(f+c),s&&(t.static_len+=u*(n[2*d+1]+c)));if(0!==w){do{for(f=l-1;0===t.bl_count[f];)f--;t.bl_count[f]--,t.bl_count[f+1]+=2,t.bl_count[l]--,w-=2}while(w>0);for(f=l;0!==f;f--)for(d=t.bl_count[f];0!==d;)_=t.heap[--h],_>i||(a[2*_+1]!==f&&(t.opt_len+=(f-a[2*_+1])*a[2*_],a[2*_+1]=f),d--)}})(t,e),v(a,h,t.bl_count)},Z=(t,e,a)=>{let i,n,s=-1,r=e[1],o=0,l=7,h=4;for(0===r&&(l=138,h=3),e[2*(a+1)+1]=65535,i=0;i<=a;i++)n=r,r=e[2*(i+1)+1],++o{let i,n,s=-1,r=e[1],o=0,l=7,h=4;for(0===r&&(l=138,h=3),i=0;i<=a;i++)if(n=r,r=e[2*(i+1)+1],!(++o{g(t,0+(i?1:0),3),x(t),b(t,a),b(t,~a),a&&t.pending_buf.set(t.window.subarray(e,e+a),t.pending),t.pending+=a};var O=(t,e,a,i)=>{let o,l,h=0;t.level>0?(2===t.strm.data_type&&(t.strm.data_type=(t=>{let e,a=4093624447;for(e=0;e<=31;e++,a>>>=1)if(1&a&&0!==t.dyn_ltree[2*e])return 0;if(0!==t.dyn_ltree[18]||0!==t.dyn_ltree[20]||0!==t.dyn_ltree[26])return 1;for(e=32;e<256;e++)if(0!==t.dyn_ltree[2*e])return 1;return 0})(t)),R(t,t.l_desc),R(t,t.d_desc),h=(t=>{let e;for(Z(t,t.dyn_ltree,t.l_desc.max_code),Z(t,t.dyn_dtree,t.d_desc.max_code),R(t,t.bl_desc),e=18;e>=3&&0===t.bl_tree[2*n[e]+1];e--);return t.opt_len+=3*(e+1)+5+5+4,e})(t),o=t.opt_len+3+7>>>3,l=t.static_len+3+7>>>3,l<=o&&(o=l)):o=l=a+5,a+4<=o&&-1!==e?D(t,e,a,i):4===t.strategy||l===o?(g(t,2+(i?1:0),3),E(t,s,r)):(g(t,4+(i?1:0),3),((t,e,a,i)=>{let s;for(g(t,e-257,5),g(t,a-1,5),g(t,i-4,4),s=0;s{U||((()=>{let t,n,w,m,b;const g=new Array(16);for(w=0,m=0;m<28;m++)for(h[m]=w,t=0;t<1<>=7;m<30;m++)for(d[m]=b<<7,t=0;t<1<(t.pending_buf[t.sym_buf+t.sym_next++]=e,t.pending_buf[t.sym_buf+t.sym_next++]=e>>8,t.pending_buf[t.sym_buf+t.sym_next++]=a,0===e?t.dyn_ltree[2*a]++:(t.matches++,e--,t.dyn_ltree[2*(l[a]+256+1)]++,t.dyn_dtree[2*m(e)]++),t.sym_next===t.sym_end),_tr_align:t=>{g(t,2,3),p(t,256,s),(t=>{16===t.bi_valid?(b(t,t.bi_buf),t.bi_buf=0,t.bi_valid=0):t.bi_valid>=8&&(t.pending_buf[t.pending++]=255&t.bi_buf,t.bi_buf>>=8,t.bi_valid-=8)})(t)}};var N=(t,e,a,i)=>{let n=65535&t|0,s=t>>>16&65535|0,r=0;for(;0!==a;){r=a>2e3?2e3:a,a-=r;do{n=n+e[i++]|0,s=s+n|0}while(--r);n%=65521,s%=65521}return n|s<<16|0};const F=new Uint32Array((()=>{let t,e=[];for(var a=0;a<256;a++){t=a;for(var i=0;i<8;i++)t=1&t?3988292384^t>>>1:t>>>1;e[a]=t}return e})());var L=(t,e,a,i)=>{const n=F,s=i+a;t^=-1;for(let a=i;a>>8^n[255&(t^e[a])];return-1^t},I={2:"need dictionary",1:"stream end",0:"","-1":"file error","-2":"stream error","-3":"data error","-4":"insufficient memory","-5":"buffer error","-6":"incompatible version"},B={Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_TREES:6,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_MEM_ERROR:-4,Z_BUF_ERROR:-5,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,Z_BINARY:0,Z_TEXT:1,Z_UNKNOWN:2,Z_DEFLATED:8};const{_tr_init:C,_tr_stored_block:H,_tr_flush_block:M,_tr_tally:j,_tr_align:K}=T,{Z_NO_FLUSH:P,Z_PARTIAL_FLUSH:Y,Z_FULL_FLUSH:G,Z_FINISH:X,Z_BLOCK:J,Z_OK:W,Z_STREAM_END:q,Z_STREAM_ERROR:Q,Z_DATA_ERROR:V,Z_BUF_ERROR:$,Z_DEFAULT_COMPRESSION:tt,Z_FILTERED:et,Z_HUFFMAN_ONLY:at,Z_RLE:it,Z_FIXED:nt,Z_DEFAULT_STRATEGY:st,Z_UNKNOWN:rt,Z_DEFLATED:ot}=B,lt=(t,e)=>(t.msg=I[e],e),ht=t=>2*t-(t>4?9:0),dt=t=>{let e=t.length;for(;--e>=0;)t[e]=0},_t=t=>{let e,a,i,n=t.w_size;e=t.hash_size,i=e;do{a=t.head[--i],t.head[i]=a>=n?a-n:0}while(--e);e=n,i=e;do{a=t.prev[--i],t.prev[i]=a>=n?a-n:0}while(--e)};let ft=(t,e,a)=>(e<{const e=t.state;let a=e.pending;a>t.avail_out&&(a=t.avail_out),0!==a&&(t.output.set(e.pending_buf.subarray(e.pending_out,e.pending_out+a),t.next_out),t.next_out+=a,e.pending_out+=a,t.total_out+=a,t.avail_out-=a,e.pending-=a,0===e.pending&&(e.pending_out=0))},ut=(t,e)=>{M(t,t.block_start>=0?t.block_start:-1,t.strstart-t.block_start,e),t.block_start=t.strstart,ct(t.strm)},wt=(t,e)=>{t.pending_buf[t.pending++]=e},mt=(t,e)=>{t.pending_buf[t.pending++]=e>>>8&255,t.pending_buf[t.pending++]=255&e},bt=(t,e,a,i)=>{let n=t.avail_in;return n>i&&(n=i),0===n?0:(t.avail_in-=n,e.set(t.input.subarray(t.next_in,t.next_in+n),a),1===t.state.wrap?t.adler=N(t.adler,e,n,a):2===t.state.wrap&&(t.adler=L(t.adler,e,n,a)),t.next_in+=n,t.total_in+=n,n)},gt=(t,e)=>{let a,i,n=t.max_chain_length,s=t.strstart,r=t.prev_length,o=t.nice_match;const l=t.strstart>t.w_size-262?t.strstart-(t.w_size-262):0,h=t.window,d=t.w_mask,_=t.prev,f=t.strstart+258;let c=h[s+r-1],u=h[s+r];t.prev_length>=t.good_match&&(n>>=2),o>t.lookahead&&(o=t.lookahead);do{if(a=e,h[a+r]===u&&h[a+r-1]===c&&h[a]===h[s]&&h[++a]===h[s+1]){s+=2,a++;do{}while(h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&sr){if(t.match_start=e,r=i,i>=o)break;c=h[s+r-1],u=h[s+r]}}}while((e=_[e&d])>l&&0!=--n);return r<=t.lookahead?r:t.lookahead},pt=t=>{const e=t.w_size;let a,i,n;do{if(i=t.window_size-t.lookahead-t.strstart,t.strstart>=e+(e-262)&&(t.window.set(t.window.subarray(e,e+e-i),0),t.match_start-=e,t.strstart-=e,t.block_start-=e,t.insert>t.strstart&&(t.insert=t.strstart),_t(t),i+=e),0===t.strm.avail_in)break;if(a=bt(t.strm,t.window,t.strstart+t.lookahead,i),t.lookahead+=a,t.lookahead+t.insert>=3)for(n=t.strstart-t.insert,t.ins_h=t.window[n],t.ins_h=ft(t,t.ins_h,t.window[n+1]);t.insert&&(t.ins_h=ft(t,t.ins_h,t.window[n+3-1]),t.prev[n&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=n,n++,t.insert--,!(t.lookahead+t.insert<3)););}while(t.lookahead<262&&0!==t.strm.avail_in)},kt=(t,e)=>{let a,i,n,s=t.pending_buf_size-5>t.w_size?t.w_size:t.pending_buf_size-5,r=0,o=t.strm.avail_in;do{if(a=65535,n=t.bi_valid+42>>3,t.strm.avail_outi+t.strm.avail_in&&(a=i+t.strm.avail_in),a>n&&(a=n),a>8,t.pending_buf[t.pending-2]=~a,t.pending_buf[t.pending-1]=~a>>8,ct(t.strm),i&&(i>a&&(i=a),t.strm.output.set(t.window.subarray(t.block_start,t.block_start+i),t.strm.next_out),t.strm.next_out+=i,t.strm.avail_out-=i,t.strm.total_out+=i,t.block_start+=i,a-=i),a&&(bt(t.strm,t.strm.output,t.strm.next_out,a),t.strm.next_out+=a,t.strm.avail_out-=a,t.strm.total_out+=a)}while(0===r);return o-=t.strm.avail_in,o&&(o>=t.w_size?(t.matches=2,t.window.set(t.strm.input.subarray(t.strm.next_in-t.w_size,t.strm.next_in),0),t.strstart=t.w_size,t.insert=t.strstart):(t.window_size-t.strstart<=o&&(t.strstart-=t.w_size,t.window.set(t.window.subarray(t.w_size,t.w_size+t.strstart),0),t.matches<2&&t.matches++,t.insert>t.strstart&&(t.insert=t.strstart)),t.window.set(t.strm.input.subarray(t.strm.next_in-o,t.strm.next_in),t.strstart),t.strstart+=o,t.insert+=o>t.w_size-t.insert?t.w_size-t.insert:o),t.block_start=t.strstart),t.high_watern&&t.block_start>=t.w_size&&(t.block_start-=t.w_size,t.strstart-=t.w_size,t.window.set(t.window.subarray(t.w_size,t.w_size+t.strstart),0),t.matches<2&&t.matches++,n+=t.w_size,t.insert>t.strstart&&(t.insert=t.strstart)),n>t.strm.avail_in&&(n=t.strm.avail_in),n&&(bt(t.strm,t.window,t.strstart,n),t.strstart+=n,t.insert+=n>t.w_size-t.insert?t.w_size-t.insert:n),t.high_water>3,n=t.pending_buf_size-n>65535?65535:t.pending_buf_size-n,s=n>t.w_size?t.w_size:n,i=t.strstart-t.block_start,(i>=s||(i||e===X)&&e!==P&&0===t.strm.avail_in&&i<=n)&&(a=i>n?n:i,r=e===X&&0===t.strm.avail_in&&a===i?1:0,H(t,t.block_start,a,r),t.block_start+=a,ct(t.strm)),r?3:1)},vt=(t,e)=>{let a,i;for(;;){if(t.lookahead<262){if(pt(t),t.lookahead<262&&e===P)return 1;if(0===t.lookahead)break}if(a=0,t.lookahead>=3&&(t.ins_h=ft(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart),0!==a&&t.strstart-a<=t.w_size-262&&(t.match_length=gt(t,a)),t.match_length>=3)if(i=j(t,t.strstart-t.match_start,t.match_length-3),t.lookahead-=t.match_length,t.match_length<=t.max_lazy_match&&t.lookahead>=3){t.match_length--;do{t.strstart++,t.ins_h=ft(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart}while(0!=--t.match_length);t.strstart++}else t.strstart+=t.match_length,t.match_length=0,t.ins_h=t.window[t.strstart],t.ins_h=ft(t,t.ins_h,t.window[t.strstart+1]);else i=j(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++;if(i&&(ut(t,!1),0===t.strm.avail_out))return 1}return t.insert=t.strstart<2?t.strstart:2,e===X?(ut(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(ut(t,!1),0===t.strm.avail_out)?1:2},yt=(t,e)=>{let a,i,n;for(;;){if(t.lookahead<262){if(pt(t),t.lookahead<262&&e===P)return 1;if(0===t.lookahead)break}if(a=0,t.lookahead>=3&&(t.ins_h=ft(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart),t.prev_length=t.match_length,t.prev_match=t.match_start,t.match_length=2,0!==a&&t.prev_length4096)&&(t.match_length=2)),t.prev_length>=3&&t.match_length<=t.prev_length){n=t.strstart+t.lookahead-3,i=j(t,t.strstart-1-t.prev_match,t.prev_length-3),t.lookahead-=t.prev_length-1,t.prev_length-=2;do{++t.strstart<=n&&(t.ins_h=ft(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart)}while(0!=--t.prev_length);if(t.match_available=0,t.match_length=2,t.strstart++,i&&(ut(t,!1),0===t.strm.avail_out))return 1}else if(t.match_available){if(i=j(t,0,t.window[t.strstart-1]),i&&ut(t,!1),t.strstart++,t.lookahead--,0===t.strm.avail_out)return 1}else t.match_available=1,t.strstart++,t.lookahead--}return t.match_available&&(i=j(t,0,t.window[t.strstart-1]),t.match_available=0),t.insert=t.strstart<2?t.strstart:2,e===X?(ut(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(ut(t,!1),0===t.strm.avail_out)?1:2};function xt(t,e,a,i,n){this.good_length=t,this.max_lazy=e,this.nice_length=a,this.max_chain=i,this.func=n}const zt=[new xt(0,0,0,0,kt),new xt(4,4,8,4,vt),new xt(4,5,16,8,vt),new xt(4,6,32,32,vt),new xt(4,4,16,16,yt),new xt(8,16,32,32,yt),new xt(8,16,128,128,yt),new xt(8,32,128,256,yt),new xt(32,128,258,1024,yt),new xt(32,258,258,4096,yt)];function At(){this.strm=null,this.status=0,this.pending_buf=null,this.pending_buf_size=0,this.pending_out=0,this.pending=0,this.wrap=0,this.gzhead=null,this.gzindex=0,this.method=ot,this.last_flush=-1,this.w_size=0,this.w_bits=0,this.w_mask=0,this.window=null,this.window_size=0,this.prev=null,this.head=null,this.ins_h=0,this.hash_size=0,this.hash_bits=0,this.hash_mask=0,this.hash_shift=0,this.block_start=0,this.match_length=0,this.prev_match=0,this.match_available=0,this.strstart=0,this.match_start=0,this.lookahead=0,this.prev_length=0,this.max_chain_length=0,this.max_lazy_match=0,this.level=0,this.strategy=0,this.good_match=0,this.nice_match=0,this.dyn_ltree=new Uint16Array(1146),this.dyn_dtree=new Uint16Array(122),this.bl_tree=new Uint16Array(78),dt(this.dyn_ltree),dt(this.dyn_dtree),dt(this.bl_tree),this.l_desc=null,this.d_desc=null,this.bl_desc=null,this.bl_count=new Uint16Array(16),this.heap=new Uint16Array(573),dt(this.heap),this.heap_len=0,this.heap_max=0,this.depth=new Uint16Array(573),dt(this.depth),this.sym_buf=0,this.lit_bufsize=0,this.sym_next=0,this.sym_end=0,this.opt_len=0,this.static_len=0,this.matches=0,this.insert=0,this.bi_buf=0,this.bi_valid=0}const Et=t=>{if(!t)return 1;const e=t.state;return!e||e.strm!==t||42!==e.status&&57!==e.status&&69!==e.status&&73!==e.status&&91!==e.status&&103!==e.status&&113!==e.status&&666!==e.status?1:0},Rt=t=>{if(Et(t))return lt(t,Q);t.total_in=t.total_out=0,t.data_type=rt;const e=t.state;return e.pending=0,e.pending_out=0,e.wrap<0&&(e.wrap=-e.wrap),e.status=2===e.wrap?57:e.wrap?42:113,t.adler=2===e.wrap?0:1,e.last_flush=-2,C(e),W},Zt=t=>{const e=Rt(t);var a;return e===W&&((a=t.state).window_size=2*a.w_size,dt(a.head),a.max_lazy_match=zt[a.level].max_lazy,a.good_match=zt[a.level].good_length,a.nice_match=zt[a.level].nice_length,a.max_chain_length=zt[a.level].max_chain,a.strstart=0,a.block_start=0,a.lookahead=0,a.insert=0,a.match_length=a.prev_length=2,a.match_available=0,a.ins_h=0),e},St=(t,e,a,i,n,s)=>{if(!t)return Q;let r=1;if(e===tt&&(e=6),i<0?(r=0,i=-i):i>15&&(r=2,i-=16),n<1||n>9||a!==ot||i<8||i>15||e<0||e>9||s<0||s>nt||8===i&&1!==r)return lt(t,Q);8===i&&(i=9);const o=new At;return t.state=o,o.strm=t,o.status=42,o.wrap=r,o.gzhead=null,o.w_bits=i,o.w_size=1<St(t,e,ot,15,8,st),deflateInit2:St,deflateReset:Zt,deflateResetKeep:Rt,deflateSetHeader:(t,e)=>Et(t)||2!==t.state.wrap?Q:(t.state.gzhead=e,W),deflate:(t,e)=>{if(Et(t)||e>J||e<0)return t?lt(t,Q):Q;const a=t.state;if(!t.output||0!==t.avail_in&&!t.input||666===a.status&&e!==X)return lt(t,0===t.avail_out?$:Q);const i=a.last_flush;if(a.last_flush=e,0!==a.pending){if(ct(t),0===t.avail_out)return a.last_flush=-1,W}else if(0===t.avail_in&&ht(e)<=ht(i)&&e!==X)return lt(t,$);if(666===a.status&&0!==t.avail_in)return lt(t,$);if(42===a.status&&0===a.wrap&&(a.status=113),42===a.status){let e=ot+(a.w_bits-8<<4)<<8,i=-1;if(i=a.strategy>=at||a.level<2?0:a.level<6?1:6===a.level?2:3,e|=i<<6,0!==a.strstart&&(e|=32),e+=31-e%31,mt(a,e),0!==a.strstart&&(mt(a,t.adler>>>16),mt(a,65535&t.adler)),t.adler=1,a.status=113,ct(t),0!==a.pending)return a.last_flush=-1,W}if(57===a.status)if(t.adler=0,wt(a,31),wt(a,139),wt(a,8),a.gzhead)wt(a,(a.gzhead.text?1:0)+(a.gzhead.hcrc?2:0)+(a.gzhead.extra?4:0)+(a.gzhead.name?8:0)+(a.gzhead.comment?16:0)),wt(a,255&a.gzhead.time),wt(a,a.gzhead.time>>8&255),wt(a,a.gzhead.time>>16&255),wt(a,a.gzhead.time>>24&255),wt(a,9===a.level?2:a.strategy>=at||a.level<2?4:0),wt(a,255&a.gzhead.os),a.gzhead.extra&&a.gzhead.extra.length&&(wt(a,255&a.gzhead.extra.length),wt(a,a.gzhead.extra.length>>8&255)),a.gzhead.hcrc&&(t.adler=L(t.adler,a.pending_buf,a.pending,0)),a.gzindex=0,a.status=69;else if(wt(a,0),wt(a,0),wt(a,0),wt(a,0),wt(a,0),wt(a,9===a.level?2:a.strategy>=at||a.level<2?4:0),wt(a,3),a.status=113,ct(t),0!==a.pending)return a.last_flush=-1,W;if(69===a.status){if(a.gzhead.extra){let e=a.pending,i=(65535&a.gzhead.extra.length)-a.gzindex;for(;a.pending+i>a.pending_buf_size;){let n=a.pending_buf_size-a.pending;if(a.pending_buf.set(a.gzhead.extra.subarray(a.gzindex,a.gzindex+n),a.pending),a.pending=a.pending_buf_size,a.gzhead.hcrc&&a.pending>e&&(t.adler=L(t.adler,a.pending_buf,a.pending-e,e)),a.gzindex+=n,ct(t),0!==a.pending)return a.last_flush=-1,W;e=0,i-=n}let n=new Uint8Array(a.gzhead.extra);a.pending_buf.set(n.subarray(a.gzindex,a.gzindex+i),a.pending),a.pending+=i,a.gzhead.hcrc&&a.pending>e&&(t.adler=L(t.adler,a.pending_buf,a.pending-e,e)),a.gzindex=0}a.status=73}if(73===a.status){if(a.gzhead.name){let e,i=a.pending;do{if(a.pending===a.pending_buf_size){if(a.gzhead.hcrc&&a.pending>i&&(t.adler=L(t.adler,a.pending_buf,a.pending-i,i)),ct(t),0!==a.pending)return a.last_flush=-1,W;i=0}e=a.gzindexi&&(t.adler=L(t.adler,a.pending_buf,a.pending-i,i)),a.gzindex=0}a.status=91}if(91===a.status){if(a.gzhead.comment){let e,i=a.pending;do{if(a.pending===a.pending_buf_size){if(a.gzhead.hcrc&&a.pending>i&&(t.adler=L(t.adler,a.pending_buf,a.pending-i,i)),ct(t),0!==a.pending)return a.last_flush=-1,W;i=0}e=a.gzindexi&&(t.adler=L(t.adler,a.pending_buf,a.pending-i,i))}a.status=103}if(103===a.status){if(a.gzhead.hcrc){if(a.pending+2>a.pending_buf_size&&(ct(t),0!==a.pending))return a.last_flush=-1,W;wt(a,255&t.adler),wt(a,t.adler>>8&255),t.adler=0}if(a.status=113,ct(t),0!==a.pending)return a.last_flush=-1,W}if(0!==t.avail_in||0!==a.lookahead||e!==P&&666!==a.status){let i=0===a.level?kt(a,e):a.strategy===at?((t,e)=>{let a;for(;;){if(0===t.lookahead&&(pt(t),0===t.lookahead)){if(e===P)return 1;break}if(t.match_length=0,a=j(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++,a&&(ut(t,!1),0===t.strm.avail_out))return 1}return t.insert=0,e===X?(ut(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(ut(t,!1),0===t.strm.avail_out)?1:2})(a,e):a.strategy===it?((t,e)=>{let a,i,n,s;const r=t.window;for(;;){if(t.lookahead<=258){if(pt(t),t.lookahead<=258&&e===P)return 1;if(0===t.lookahead)break}if(t.match_length=0,t.lookahead>=3&&t.strstart>0&&(n=t.strstart-1,i=r[n],i===r[++n]&&i===r[++n]&&i===r[++n])){s=t.strstart+258;do{}while(i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&nt.lookahead&&(t.match_length=t.lookahead)}if(t.match_length>=3?(a=j(t,1,t.match_length-3),t.lookahead-=t.match_length,t.strstart+=t.match_length,t.match_length=0):(a=j(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++),a&&(ut(t,!1),0===t.strm.avail_out))return 1}return t.insert=0,e===X?(ut(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(ut(t,!1),0===t.strm.avail_out)?1:2})(a,e):zt[a.level].func(a,e);if(3!==i&&4!==i||(a.status=666),1===i||3===i)return 0===t.avail_out&&(a.last_flush=-1),W;if(2===i&&(e===Y?K(a):e!==J&&(H(a,0,0,!1),e===G&&(dt(a.head),0===a.lookahead&&(a.strstart=0,a.block_start=0,a.insert=0))),ct(t),0===t.avail_out))return a.last_flush=-1,W}return e!==X?W:a.wrap<=0?q:(2===a.wrap?(wt(a,255&t.adler),wt(a,t.adler>>8&255),wt(a,t.adler>>16&255),wt(a,t.adler>>24&255),wt(a,255&t.total_in),wt(a,t.total_in>>8&255),wt(a,t.total_in>>16&255),wt(a,t.total_in>>24&255)):(mt(a,t.adler>>>16),mt(a,65535&t.adler)),ct(t),a.wrap>0&&(a.wrap=-a.wrap),0!==a.pending?W:q)},deflateEnd:t=>{if(Et(t))return Q;const e=t.state.status;return t.state=null,113===e?lt(t,V):W},deflateSetDictionary:(t,e)=>{let a=e.length;if(Et(t))return Q;const i=t.state,n=i.wrap;if(2===n||1===n&&42!==i.status||i.lookahead)return Q;if(1===n&&(t.adler=N(t.adler,e,a,0)),i.wrap=0,a>=i.w_size){0===n&&(dt(i.head),i.strstart=0,i.block_start=0,i.insert=0);let t=new Uint8Array(i.w_size);t.set(e.subarray(a-i.w_size,a),0),e=t,a=i.w_size}const s=t.avail_in,r=t.next_in,o=t.input;for(t.avail_in=a,t.next_in=0,t.input=e,pt(i);i.lookahead>=3;){let t=i.strstart,e=i.lookahead-2;do{i.ins_h=ft(i,i.ins_h,i.window[t+3-1]),i.prev[t&i.w_mask]=i.head[i.ins_h],i.head[i.ins_h]=t,t++}while(--e);i.strstart=t,i.lookahead=2,pt(i)}return i.strstart+=i.lookahead,i.block_start=i.strstart,i.insert=i.lookahead,i.lookahead=0,i.match_length=i.prev_length=2,i.match_available=0,t.next_in=r,t.input=o,t.avail_in=s,i.wrap=n,W},deflateInfo:"pako deflate (from Nodeca project)"};const Dt=(t,e)=>Object.prototype.hasOwnProperty.call(t,e);var Ot=function(t){const e=Array.prototype.slice.call(arguments,1);for(;e.length;){const a=e.shift();if(a){if("object"!=typeof a)throw new TypeError(a+"must be non-object");for(const e in a)Dt(a,e)&&(t[e]=a[e])}}return t},Tt=t=>{let e=0;for(let a=0,i=t.length;a=252?6:t>=248?5:t>=240?4:t>=224?3:t>=192?2:1;Ft[254]=Ft[254]=1;var Lt=t=>{if("function"==typeof TextEncoder&&TextEncoder.prototype.encode)return(new TextEncoder).encode(t);let e,a,i,n,s,r=t.length,o=0;for(n=0;n>>6,e[s++]=128|63&a):a<65536?(e[s++]=224|a>>>12,e[s++]=128|a>>>6&63,e[s++]=128|63&a):(e[s++]=240|a>>>18,e[s++]=128|a>>>12&63,e[s++]=128|a>>>6&63,e[s++]=128|63&a);return e},It=(t,e)=>{const a=e||t.length;if("function"==typeof TextDecoder&&TextDecoder.prototype.decode)return(new TextDecoder).decode(t.subarray(0,e));let i,n;const s=new Array(2*a);for(n=0,i=0;i4)s[n++]=65533,i+=r-1;else{for(e&=2===r?31:3===r?15:7;r>1&&i1?s[n++]=65533:e<65536?s[n++]=e:(e-=65536,s[n++]=55296|e>>10&1023,s[n++]=56320|1023&e)}}return((t,e)=>{if(e<65534&&t.subarray&&Nt)return String.fromCharCode.apply(null,t.length===e?t:t.subarray(0,e));let a="";for(let i=0;i{(e=e||t.length)>t.length&&(e=t.length);let a=e-1;for(;a>=0&&128==(192&t[a]);)a--;return a<0||0===a?e:a+Ft[t[a]]>e?a:e};var Ct=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0};const Ht=Object.prototype.toString,{Z_NO_FLUSH:Mt,Z_SYNC_FLUSH:jt,Z_FULL_FLUSH:Kt,Z_FINISH:Pt,Z_OK:Yt,Z_STREAM_END:Gt,Z_DEFAULT_COMPRESSION:Xt,Z_DEFAULT_STRATEGY:Jt,Z_DEFLATED:Wt}=B;function qt(t){this.options=Ot({level:Xt,method:Wt,chunkSize:16384,windowBits:15,memLevel:8,strategy:Jt},t||{});let e=this.options;e.raw&&e.windowBits>0?e.windowBits=-e.windowBits:e.gzip&&e.windowBits>0&&e.windowBits<16&&(e.windowBits+=16),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new Ct,this.strm.avail_out=0;let a=Ut.deflateInit2(this.strm,e.level,e.method,e.windowBits,e.memLevel,e.strategy);if(a!==Yt)throw new Error(I[a]);if(e.header&&Ut.deflateSetHeader(this.strm,e.header),e.dictionary){let t;if(t="string"==typeof e.dictionary?Lt(e.dictionary):"[object ArrayBuffer]"===Ht.call(e.dictionary)?new Uint8Array(e.dictionary):e.dictionary,a=Ut.deflateSetDictionary(this.strm,t),a!==Yt)throw new Error(I[a]);this._dict_set=!0}}function Qt(t,e){const a=new qt(e);if(a.push(t,!0),a.err)throw a.msg||I[a.err];return a.result}qt.prototype.push=function(t,e){const a=this.strm,i=this.options.chunkSize;let n,s;if(this.ended)return!1;for(s=e===~~e?e:!0===e?Pt:Mt,"string"==typeof t?a.input=Lt(t):"[object ArrayBuffer]"===Ht.call(t)?a.input=new Uint8Array(t):a.input=t,a.next_in=0,a.avail_in=a.input.length;;)if(0===a.avail_out&&(a.output=new Uint8Array(i),a.next_out=0,a.avail_out=i),(s===jt||s===Kt)&&a.avail_out<=6)this.onData(a.output.subarray(0,a.next_out)),a.avail_out=0;else{if(n=Ut.deflate(a,s),n===Gt)return a.next_out>0&&this.onData(a.output.subarray(0,a.next_out)),n=Ut.deflateEnd(this.strm),this.onEnd(n),this.ended=!0,n===Yt;if(0!==a.avail_out){if(s>0&&a.next_out>0)this.onData(a.output.subarray(0,a.next_out)),a.avail_out=0;else if(0===a.avail_in)break}else this.onData(a.output)}return!0},qt.prototype.onData=function(t){this.chunks.push(t)},qt.prototype.onEnd=function(t){t===Yt&&(this.result=Tt(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg};var Vt={Deflate:qt,deflate:Qt,deflateRaw:function(t,e){return(e=e||{}).raw=!0,Qt(t,e)},gzip:function(t,e){return(e=e||{}).gzip=!0,Qt(t,e)},constants:B};var $t=function(t,e){let a,i,n,s,r,o,l,h,d,_,f,c,u,w,m,b,g,p,k,v,y,x,z,A;const E=t.state;a=t.next_in,z=t.input,i=a+(t.avail_in-5),n=t.next_out,A=t.output,s=n-(e-t.avail_out),r=n+(t.avail_out-257),o=E.dmax,l=E.wsize,h=E.whave,d=E.wnext,_=E.window,f=E.hold,c=E.bits,u=E.lencode,w=E.distcode,m=(1<>>24,f>>>=p,c-=p,p=g>>>16&255,0===p)A[n++]=65535&g;else{if(!(16&p)){if(0==(64&p)){g=u[(65535&g)+(f&(1<>>=p,c-=p),c<15&&(f+=z[a++]<>>24,f>>>=p,c-=p,p=g>>>16&255,!(16&p)){if(0==(64&p)){g=w[(65535&g)+(f&(1<o){t.msg="invalid distance too far back",E.mode=16209;break t}if(f>>>=p,c-=p,p=n-s,v>p){if(p=v-p,p>h&&E.sane){t.msg="invalid distance too far back",E.mode=16209;break t}if(y=0,x=_,0===d){if(y+=l-p,p2;)A[n++]=x[y++],A[n++]=x[y++],A[n++]=x[y++],k-=3;k&&(A[n++]=x[y++],k>1&&(A[n++]=x[y++]))}else{y=n-v;do{A[n++]=A[y++],A[n++]=A[y++],A[n++]=A[y++],k-=3}while(k>2);k&&(A[n++]=A[y++],k>1&&(A[n++]=A[y++]))}break}}break}}while(a>3,a-=k,c-=k<<3,f&=(1<{const l=o.bits;let h,d,_,f,c,u,w=0,m=0,b=0,g=0,p=0,k=0,v=0,y=0,x=0,z=0,A=null;const E=new Uint16Array(16),R=new Uint16Array(16);let Z,S,U,D=null;for(w=0;w<=15;w++)E[w]=0;for(m=0;m=1&&0===E[g];g--);if(p>g&&(p=g),0===g)return n[s++]=20971520,n[s++]=20971520,o.bits=1,0;for(b=1;b0&&(0===t||1!==g))return-1;for(R[1]=0,w=1;w<15;w++)R[w+1]=R[w]+E[w];for(m=0;m852||2===t&&x>592)return 1;for(;;){Z=w-v,r[m]+1=u?(S=D[r[m]-u],U=A[r[m]-u]):(S=96,U=0),h=1<>v)+d]=Z<<24|S<<16|U|0}while(0!==d);for(h=1<>=1;if(0!==h?(z&=h-1,z+=h):z=0,m++,0==--E[w]){if(w===g)break;w=e[a+r[m]]}if(w>p&&(z&f)!==_){for(0===v&&(v=p),c+=b,k=w-v,y=1<852||2===t&&x>592)return 1;_=z&f,n[_]=p<<24|k<<16|c-s|0}}return 0!==z&&(n[c+z]=w-v<<24|64<<16|0),o.bits=p,0};const{Z_FINISH:se,Z_BLOCK:re,Z_TREES:oe,Z_OK:le,Z_STREAM_END:he,Z_NEED_DICT:de,Z_STREAM_ERROR:_e,Z_DATA_ERROR:fe,Z_MEM_ERROR:ce,Z_BUF_ERROR:ue,Z_DEFLATED:we}=B,me=16209,be=t=>(t>>>24&255)+(t>>>8&65280)+((65280&t)<<8)+((255&t)<<24);function ge(){this.strm=null,this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new Uint16Array(320),this.work=new Uint16Array(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}const pe=t=>{if(!t)return 1;const e=t.state;return!e||e.strm!==t||e.mode<16180||e.mode>16211?1:0},ke=t=>{if(pe(t))return _e;const e=t.state;return t.total_in=t.total_out=e.total=0,t.msg="",e.wrap&&(t.adler=1&e.wrap),e.mode=16180,e.last=0,e.havedict=0,e.flags=-1,e.dmax=32768,e.head=null,e.hold=0,e.bits=0,e.lencode=e.lendyn=new Int32Array(852),e.distcode=e.distdyn=new Int32Array(592),e.sane=1,e.back=-1,le},ve=t=>{if(pe(t))return _e;const e=t.state;return e.wsize=0,e.whave=0,e.wnext=0,ke(t)},ye=(t,e)=>{let a;if(pe(t))return _e;const i=t.state;return e<0?(a=0,e=-e):(a=5+(e>>4),e<48&&(e&=15)),e&&(e<8||e>15)?_e:(null!==i.window&&i.wbits!==e&&(i.window=null),i.wrap=a,i.wbits=e,ve(t))},xe=(t,e)=>{if(!t)return _e;const a=new ge;t.state=a,a.strm=t,a.window=null,a.mode=16180;const i=ye(t,e);return i!==le&&(t.state=null),i};let ze,Ae,Ee=!0;const Re=t=>{if(Ee){ze=new Int32Array(512),Ae=new Int32Array(32);let e=0;for(;e<144;)t.lens[e++]=8;for(;e<256;)t.lens[e++]=9;for(;e<280;)t.lens[e++]=7;for(;e<288;)t.lens[e++]=8;for(ne(1,t.lens,0,288,ze,0,t.work,{bits:9}),e=0;e<32;)t.lens[e++]=5;ne(2,t.lens,0,32,Ae,0,t.work,{bits:5}),Ee=!1}t.lencode=ze,t.lenbits=9,t.distcode=Ae,t.distbits=5},Ze=(t,e,a,i)=>{let n;const s=t.state;return null===s.window&&(s.wsize=1<=s.wsize?(s.window.set(e.subarray(a-s.wsize,a),0),s.wnext=0,s.whave=s.wsize):(n=s.wsize-s.wnext,n>i&&(n=i),s.window.set(e.subarray(a-i,a-i+n),s.wnext),(i-=n)?(s.window.set(e.subarray(a-i,a),0),s.wnext=i,s.whave=s.wsize):(s.wnext+=n,s.wnext===s.wsize&&(s.wnext=0),s.whavexe(t,15),inflateInit2:xe,inflate:(t,e)=>{let a,i,n,s,r,o,l,h,d,_,f,c,u,w,m,b,g,p,k,v,y,x,z=0;const A=new Uint8Array(4);let E,R;const Z=new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]);if(pe(t)||!t.output||!t.input&&0!==t.avail_in)return _e;a=t.state,16191===a.mode&&(a.mode=16192),r=t.next_out,n=t.output,l=t.avail_out,s=t.next_in,i=t.input,o=t.avail_in,h=a.hold,d=a.bits,_=o,f=l,x=le;t:for(;;)switch(a.mode){case 16180:if(0===a.wrap){a.mode=16192;break}for(;d<16;){if(0===o)break t;o--,h+=i[s++]<>>8&255,a.check=L(a.check,A,2,0),h=0,d=0,a.mode=16181;break}if(a.head&&(a.head.done=!1),!(1&a.wrap)||(((255&h)<<8)+(h>>8))%31){t.msg="incorrect header check",a.mode=me;break}if((15&h)!==we){t.msg="unknown compression method",a.mode=me;break}if(h>>>=4,d-=4,y=8+(15&h),0===a.wbits&&(a.wbits=y),y>15||y>a.wbits){t.msg="invalid window size",a.mode=me;break}a.dmax=1<>8&1),512&a.flags&&4&a.wrap&&(A[0]=255&h,A[1]=h>>>8&255,a.check=L(a.check,A,2,0)),h=0,d=0,a.mode=16182;case 16182:for(;d<32;){if(0===o)break t;o--,h+=i[s++]<>>8&255,A[2]=h>>>16&255,A[3]=h>>>24&255,a.check=L(a.check,A,4,0)),h=0,d=0,a.mode=16183;case 16183:for(;d<16;){if(0===o)break t;o--,h+=i[s++]<>8),512&a.flags&&4&a.wrap&&(A[0]=255&h,A[1]=h>>>8&255,a.check=L(a.check,A,2,0)),h=0,d=0,a.mode=16184;case 16184:if(1024&a.flags){for(;d<16;){if(0===o)break t;o--,h+=i[s++]<>>8&255,a.check=L(a.check,A,2,0)),h=0,d=0}else a.head&&(a.head.extra=null);a.mode=16185;case 16185:if(1024&a.flags&&(c=a.length,c>o&&(c=o),c&&(a.head&&(y=a.head.extra_len-a.length,a.head.extra||(a.head.extra=new Uint8Array(a.head.extra_len)),a.head.extra.set(i.subarray(s,s+c),y)),512&a.flags&&4&a.wrap&&(a.check=L(a.check,i,c,s)),o-=c,s+=c,a.length-=c),a.length))break t;a.length=0,a.mode=16186;case 16186:if(2048&a.flags){if(0===o)break t;c=0;do{y=i[s+c++],a.head&&y&&a.length<65536&&(a.head.name+=String.fromCharCode(y))}while(y&&c>9&1,a.head.done=!0),t.adler=a.check=0,a.mode=16191;break;case 16189:for(;d<32;){if(0===o)break t;o--,h+=i[s++]<>>=7&d,d-=7&d,a.mode=16206;break}for(;d<3;){if(0===o)break t;o--,h+=i[s++]<>>=1,d-=1,3&h){case 0:a.mode=16193;break;case 1:if(Re(a),a.mode=16199,e===oe){h>>>=2,d-=2;break t}break;case 2:a.mode=16196;break;case 3:t.msg="invalid block type",a.mode=me}h>>>=2,d-=2;break;case 16193:for(h>>>=7&d,d-=7&d;d<32;){if(0===o)break t;o--,h+=i[s++]<>>16^65535)){t.msg="invalid stored block lengths",a.mode=me;break}if(a.length=65535&h,h=0,d=0,a.mode=16194,e===oe)break t;case 16194:a.mode=16195;case 16195:if(c=a.length,c){if(c>o&&(c=o),c>l&&(c=l),0===c)break t;n.set(i.subarray(s,s+c),r),o-=c,s+=c,l-=c,r+=c,a.length-=c;break}a.mode=16191;break;case 16196:for(;d<14;){if(0===o)break t;o--,h+=i[s++]<>>=5,d-=5,a.ndist=1+(31&h),h>>>=5,d-=5,a.ncode=4+(15&h),h>>>=4,d-=4,a.nlen>286||a.ndist>30){t.msg="too many length or distance symbols",a.mode=me;break}a.have=0,a.mode=16197;case 16197:for(;a.have>>=3,d-=3}for(;a.have<19;)a.lens[Z[a.have++]]=0;if(a.lencode=a.lendyn,a.lenbits=7,E={bits:a.lenbits},x=ne(0,a.lens,0,19,a.lencode,0,a.work,E),a.lenbits=E.bits,x){t.msg="invalid code lengths set",a.mode=me;break}a.have=0,a.mode=16198;case 16198:for(;a.have>>24,b=z>>>16&255,g=65535&z,!(m<=d);){if(0===o)break t;o--,h+=i[s++]<>>=m,d-=m,a.lens[a.have++]=g;else{if(16===g){for(R=m+2;d>>=m,d-=m,0===a.have){t.msg="invalid bit length repeat",a.mode=me;break}y=a.lens[a.have-1],c=3+(3&h),h>>>=2,d-=2}else if(17===g){for(R=m+3;d>>=m,d-=m,y=0,c=3+(7&h),h>>>=3,d-=3}else{for(R=m+7;d>>=m,d-=m,y=0,c=11+(127&h),h>>>=7,d-=7}if(a.have+c>a.nlen+a.ndist){t.msg="invalid bit length repeat",a.mode=me;break}for(;c--;)a.lens[a.have++]=y}}if(a.mode===me)break;if(0===a.lens[256]){t.msg="invalid code -- missing end-of-block",a.mode=me;break}if(a.lenbits=9,E={bits:a.lenbits},x=ne(1,a.lens,0,a.nlen,a.lencode,0,a.work,E),a.lenbits=E.bits,x){t.msg="invalid literal/lengths set",a.mode=me;break}if(a.distbits=6,a.distcode=a.distdyn,E={bits:a.distbits},x=ne(2,a.lens,a.nlen,a.ndist,a.distcode,0,a.work,E),a.distbits=E.bits,x){t.msg="invalid distances set",a.mode=me;break}if(a.mode=16199,e===oe)break t;case 16199:a.mode=16200;case 16200:if(o>=6&&l>=258){t.next_out=r,t.avail_out=l,t.next_in=s,t.avail_in=o,a.hold=h,a.bits=d,$t(t,f),r=t.next_out,n=t.output,l=t.avail_out,s=t.next_in,i=t.input,o=t.avail_in,h=a.hold,d=a.bits,16191===a.mode&&(a.back=-1);break}for(a.back=0;z=a.lencode[h&(1<>>24,b=z>>>16&255,g=65535&z,!(m<=d);){if(0===o)break t;o--,h+=i[s++]<>p)],m=z>>>24,b=z>>>16&255,g=65535&z,!(p+m<=d);){if(0===o)break t;o--,h+=i[s++]<>>=p,d-=p,a.back+=p}if(h>>>=m,d-=m,a.back+=m,a.length=g,0===b){a.mode=16205;break}if(32&b){a.back=-1,a.mode=16191;break}if(64&b){t.msg="invalid literal/length code",a.mode=me;break}a.extra=15&b,a.mode=16201;case 16201:if(a.extra){for(R=a.extra;d>>=a.extra,d-=a.extra,a.back+=a.extra}a.was=a.length,a.mode=16202;case 16202:for(;z=a.distcode[h&(1<>>24,b=z>>>16&255,g=65535&z,!(m<=d);){if(0===o)break t;o--,h+=i[s++]<>p)],m=z>>>24,b=z>>>16&255,g=65535&z,!(p+m<=d);){if(0===o)break t;o--,h+=i[s++]<>>=p,d-=p,a.back+=p}if(h>>>=m,d-=m,a.back+=m,64&b){t.msg="invalid distance code",a.mode=me;break}a.offset=g,a.extra=15&b,a.mode=16203;case 16203:if(a.extra){for(R=a.extra;d>>=a.extra,d-=a.extra,a.back+=a.extra}if(a.offset>a.dmax){t.msg="invalid distance too far back",a.mode=me;break}a.mode=16204;case 16204:if(0===l)break t;if(c=f-l,a.offset>c){if(c=a.offset-c,c>a.whave&&a.sane){t.msg="invalid distance too far back",a.mode=me;break}c>a.wnext?(c-=a.wnext,u=a.wsize-c):u=a.wnext-c,c>a.length&&(c=a.length),w=a.window}else w=n,u=r-a.offset,c=a.length;c>l&&(c=l),l-=c,a.length-=c;do{n[r++]=w[u++]}while(--c);0===a.length&&(a.mode=16200);break;case 16205:if(0===l)break t;n[r++]=a.length,l--,a.mode=16200;break;case 16206:if(a.wrap){for(;d<32;){if(0===o)break t;o--,h|=i[s++]<{if(pe(t))return _e;let e=t.state;return e.window&&(e.window=null),t.state=null,le},inflateGetHeader:(t,e)=>{if(pe(t))return _e;const a=t.state;return 0==(2&a.wrap)?_e:(a.head=e,e.done=!1,le)},inflateSetDictionary:(t,e)=>{const a=e.length;let i,n,s;return pe(t)?_e:(i=t.state,0!==i.wrap&&16190!==i.mode?_e:16190===i.mode&&(n=1,n=N(n,e,a,0),n!==i.check)?fe:(s=Ze(t,e,a,a),s?(i.mode=16210,ce):(i.havedict=1,le)))},inflateInfo:"pako inflate (from Nodeca project)"};var Ue=function(){this.text=0,this.time=0,this.xflags=0,this.os=0,this.extra=null,this.extra_len=0,this.name="",this.comment="",this.hcrc=0,this.done=!1};const De=Object.prototype.toString,{Z_NO_FLUSH:Oe,Z_FINISH:Te,Z_OK:Ne,Z_STREAM_END:Fe,Z_NEED_DICT:Le,Z_STREAM_ERROR:Ie,Z_DATA_ERROR:Be,Z_MEM_ERROR:Ce}=B;function He(t){this.options=Ot({chunkSize:65536,windowBits:15,to:""},t||{});const e=this.options;e.raw&&e.windowBits>=0&&e.windowBits<16&&(e.windowBits=-e.windowBits,0===e.windowBits&&(e.windowBits=-15)),!(e.windowBits>=0&&e.windowBits<16)||t&&t.windowBits||(e.windowBits+=32),e.windowBits>15&&e.windowBits<48&&0==(15&e.windowBits)&&(e.windowBits|=15),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new Ct,this.strm.avail_out=0;let a=Se.inflateInit2(this.strm,e.windowBits);if(a!==Ne)throw new Error(I[a]);if(this.header=new Ue,Se.inflateGetHeader(this.strm,this.header),e.dictionary&&("string"==typeof e.dictionary?e.dictionary=Lt(e.dictionary):"[object ArrayBuffer]"===De.call(e.dictionary)&&(e.dictionary=new Uint8Array(e.dictionary)),e.raw&&(a=Se.inflateSetDictionary(this.strm,e.dictionary),a!==Ne)))throw new Error(I[a])}He.prototype.push=function(t,e){const a=this.strm,i=this.options.chunkSize,n=this.options.dictionary;let s,r,o;if(this.ended)return!1;for(r=e===~~e?e:!0===e?Te:Oe,"[object ArrayBuffer]"===De.call(t)?a.input=new Uint8Array(t):a.input=t,a.next_in=0,a.avail_in=a.input.length;;){for(0===a.avail_out&&(a.output=new Uint8Array(i),a.next_out=0,a.avail_out=i),s=Se.inflate(a,r),s===Le&&n&&(s=Se.inflateSetDictionary(a,n),s===Ne?s=Se.inflate(a,r):s===Be&&(s=Le));a.avail_in>0&&s===Fe&&a.state.wrap>0&&0!==t[a.next_in];)Se.inflateReset(a),s=Se.inflate(a,r);switch(s){case Ie:case Be:case Le:case Ce:return this.onEnd(s),this.ended=!0,!1}if(o=a.avail_out,a.next_out&&(0===a.avail_out||s===Fe))if("string"===this.options.to){let t=Bt(a.output,a.next_out),e=a.next_out-t,n=It(a.output,t);a.next_out=e,a.avail_out=i-e,e&&a.output.set(a.output.subarray(t,t+e),0),this.onData(n)}else this.onData(a.output.length===a.next_out?a.output:a.output.subarray(0,a.next_out));if(s!==Ne||0!==o){if(s===Fe)return s=Se.inflateEnd(this.strm),this.onEnd(s),this.ended=!0,!0;if(0===a.avail_in)break}}return!0},He.prototype.onData=function(t){this.chunks.push(t)},He.prototype.onEnd=function(t){t===Ne&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=Tt(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg};const{Deflate:Me,deflate:je,deflateRaw:Ke,gzip:Pe}=Vt;var Ye=Me,Ge=B;const Xe=new class{constructor(){this.added=0,this.init()}init(){this.added=0,this.deflate=new Ye,this.deflate.push("[",Ge.Z_NO_FLUSH)}addEvent(t){if(!t)throw new Error("Adding invalid event");const e=this.added>0?",":"";this.deflate.push(e+JSON.stringify(t),Ge.Z_SYNC_FLUSH),this.added++}finish(){if(this.deflate.push("]",Ge.Z_FINISH),this.deflate.err)throw this.deflate.err;const t=this.deflate.result;return this.init(),t}},Je={init:()=>(Xe.init(),""),addEvent:t=>Xe.addEvent(t),finish:()=>Xe.finish()};addEventListener("message",(function(t){const e=t.data.method,a=t.data.id,[i]=t.data.args?JSON.parse(t.data.args):[];if(e in Je&&"function"==typeof Je[e])try{const t=Je[e](i);postMessage({id:a,method:e,success:!0,response:t})}catch(t){postMessage({id:a,method:e,success:!1,response:t.message}),console.error(t)}}));`; diff --git a/packages/replay/test/fixtures/error.ts b/packages/replay/test/fixtures/error.ts index 31827cec3929..6008f81209e1 100644 --- a/packages/replay/test/fixtures/error.ts +++ b/packages/replay/test/fixtures/error.ts @@ -1,5 +1,4 @@ -import { SeverityLevel } from '@sentry/browser'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; export function Error(obj?: Event): any { const timestamp = new Date().getTime() / 1000; @@ -31,7 +30,7 @@ export function Error(obj?: Event): any { }, ], }, - level: 'error' as SeverityLevel, + level: 'error', event_id: 'event_id', platform: 'javascript', timestamp, diff --git a/packages/replay/test/fixtures/transaction.ts b/packages/replay/test/fixtures/transaction.ts index 37ffcc4ac49d..24f89cdc9fb8 100644 --- a/packages/replay/test/fixtures/transaction.ts +++ b/packages/replay/test/fixtures/transaction.ts @@ -1,4 +1,4 @@ -import { Event, SeverityLevel } from '@sentry/types'; +import type { Event, SeverityLevel } from '@sentry/types'; export function Transaction(obj?: Partial): any { const timestamp = new Date().getTime() / 1000; diff --git a/packages/replay/test/integration/autoSaveSession.test.ts b/packages/replay/test/integration/autoSaveSession.test.ts new file mode 100644 index 000000000000..fb047e07bcc8 --- /dev/null +++ b/packages/replay/test/integration/autoSaveSession.test.ts @@ -0,0 +1,57 @@ +import { EventType } from 'rrweb'; + +import type { RecordingEvent } from '../../src/types'; +import { addEvent } from '../../src/util/addEvent'; +import { resetSdkMock } from '../mocks/resetSdkMock'; +import { useFakeTimers } from '../utils/use-fake-timers'; + +useFakeTimers(); + +describe('Integration | autoSaveSession', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + test.each([ + ['with stickySession=true', true, 1], + ['with stickySession=false', false, 0], + ])('%s', async (_: string, stickySession: boolean, addSummand: number) => { + let saveSessionSpy; + + jest.mock('../../src/session/saveSession', () => { + saveSessionSpy = jest.fn(); + + return { + saveSession: saveSessionSpy, + }; + }); + + const { replay } = await resetSdkMock({ + replayOptions: { + stickySession, + }, + }); + + // Initially called up to three times: once for start, then once for replay.updateSessionActivity & once for segmentId increase + expect(saveSessionSpy).toHaveBeenCalledTimes(addSummand * 3); + + replay['_updateSessionActivity'](); + + expect(saveSessionSpy).toHaveBeenCalledTimes(addSummand * 4); + + // In order for runFlush to actually do something, we need to add an event + const event = { + type: EventType.Custom, + data: { + tag: 'test custom', + }, + timestamp: new Date().valueOf(), + } as RecordingEvent; + + addEvent(replay, event); + + await replay['_runFlush'](); + + expect(saveSessionSpy).toHaveBeenCalledTimes(addSummand * 5); + }); +}); diff --git a/packages/replay/test/unit/index-handleGlobalEvent.test.ts b/packages/replay/test/integration/coreHandlers/handleGlobalEvent.test.ts similarity index 92% rename from packages/replay/test/unit/index-handleGlobalEvent.test.ts rename to packages/replay/test/integration/coreHandlers/handleGlobalEvent.test.ts index 0103c1ab2240..e612d1210000 100644 --- a/packages/replay/test/unit/index-handleGlobalEvent.test.ts +++ b/packages/replay/test/integration/coreHandlers/handleGlobalEvent.test.ts @@ -1,19 +1,22 @@ import { getCurrentHub } from '@sentry/core'; -import { Event } from '@sentry/types'; +import type { Event } from '@sentry/types'; -import { REPLAY_EVENT_NAME } from '../../src/constants'; -import { handleGlobalEventListener } from '../../src/coreHandlers/handleGlobalEvent'; -import { overwriteRecordDroppedEvent, restoreRecordDroppedEvent } from '../../src/util/monkeyPatchRecordDroppedEvent'; -import { ReplayContainer } from './../../src/replay'; -import { Error } from './../fixtures/error'; -import { Transaction } from './../fixtures/transaction'; -import { resetSdkMock } from './../mocks/resetSdkMock'; -import { useFakeTimers } from './../utils/use-fake-timers'; +import { REPLAY_EVENT_NAME } from '../../../src/constants'; +import { handleGlobalEventListener } from '../../../src/coreHandlers/handleGlobalEvent'; +import type { ReplayContainer } from '../../../src/replay'; +import { + overwriteRecordDroppedEvent, + restoreRecordDroppedEvent, +} from '../../../src/util/monkeyPatchRecordDroppedEvent'; +import { Error } from '../../fixtures/error'; +import { Transaction } from '../../fixtures/transaction'; +import { resetSdkMock } from '../../mocks/resetSdkMock'; +import { useFakeTimers } from '../../utils/use-fake-timers'; useFakeTimers(); let replay: ReplayContainer; -describe('handleGlobalEvent', () => { +describe('Integration | coreHandlers | handleGlobalEvent', () => { beforeEach(async () => { ({ replay } = await resetSdkMock({ replayOptions: { diff --git a/packages/replay/test/integration/coreHandlers/handleScope.test.ts b/packages/replay/test/integration/coreHandlers/handleScope.test.ts new file mode 100644 index 000000000000..016aba2f9917 --- /dev/null +++ b/packages/replay/test/integration/coreHandlers/handleScope.test.ts @@ -0,0 +1,29 @@ +import { getCurrentHub } from '@sentry/core'; + +import * as HandleScope from '../../../src/coreHandlers/handleScope'; +import { mockSdk } from './../../index'; + +jest.useFakeTimers(); + +describe('Integration | coreHandlers | handleScope', () => { + beforeAll(async function () { + await mockSdk(); + jest.runAllTimers(); + }); + + it('returns a breadcrumb only if last breadcrumb has changed', function () { + const mockHandleScope = jest.spyOn(HandleScope, 'handleScope'); + getCurrentHub().getScope()?.addBreadcrumb({ message: 'testing' }); + + expect(mockHandleScope).toHaveBeenCalledTimes(1); + expect(mockHandleScope).toHaveReturnedWith(expect.objectContaining({ message: 'testing' })); + + mockHandleScope.mockClear(); + + // This will trigger breadcrumb/scope listener, but handleScope should return + // null because breadcrumbs has not changed + getCurrentHub().getScope()?.setUser({ email: 'foo@foo.com' }); + expect(mockHandleScope).toHaveBeenCalledTimes(1); + expect(mockHandleScope).toHaveReturnedWith(null); + }); +}); diff --git a/packages/replay/test/unit/index-errorSampleRate.test.ts b/packages/replay/test/integration/errorSampleRate.test.ts similarity index 91% rename from packages/replay/test/unit/index-errorSampleRate.test.ts rename to packages/replay/test/integration/errorSampleRate.test.ts index d6c848923118..2b52e6c7d96c 100644 --- a/packages/replay/test/unit/index-errorSampleRate.test.ts +++ b/packages/replay/test/integration/errorSampleRate.test.ts @@ -1,13 +1,15 @@ import { captureException } from '@sentry/core'; import { DEFAULT_FLUSH_MIN_DELAY, REPLAY_SESSION_KEY, VISIBILITY_CHANGE_TIMEOUT, WINDOW } from '../../src/constants'; +import type { ReplayContainer } from '../../src/replay'; import { addEvent } from '../../src/util/addEvent'; -import { ReplayContainer } from './../../src/replay'; -import { PerformanceEntryResource } from './../fixtures/performanceEntry/resource'; -import { BASE_TIMESTAMP, RecordMock } from './../index'; -import { resetSdkMock } from './../mocks/resetSdkMock'; -import { DomHandler } from './../types'; -import { useFakeTimers } from './../utils/use-fake-timers'; +import { PerformanceEntryResource } from '../fixtures/performanceEntry/resource'; +import type { RecordMock } from '../index'; +import { BASE_TIMESTAMP } from '../index'; +import { resetSdkMock } from '../mocks/resetSdkMock'; +import type { DomHandler } from '../types'; +import { clearSession } from '../utils/clearSession'; +import { useFakeTimers } from '../utils/use-fake-timers'; useFakeTimers(); @@ -16,7 +18,7 @@ async function advanceTimers(time: number) { await new Promise(process.nextTick); } -describe('Replay (errorSampleRate)', () => { +describe('Integration | errorSampleRate', () => { let replay: ReplayContainer; let mockRecord: RecordMock; let domHandler: DomHandler; @@ -34,7 +36,7 @@ describe('Replay (errorSampleRate)', () => { }); afterEach(async () => { - replay.clearSession(); + clearSession(replay); replay.stop(); }); @@ -60,13 +62,13 @@ describe('Replay (errorSampleRate)', () => { expect(replay).toHaveSentReplay({ recordingPayloadHeader: { segment_id: 0 }, replayEventPayload: expect.objectContaining({ + replay_type: 'error', tags: expect.objectContaining({ errorSampleRate: 1, - replayType: 'error', sessionSampleRate: 0, }), }), - events: JSON.stringify([ + recordingData: JSON.stringify([ { data: { isCheckout: true }, timestamp: BASE_TIMESTAMP, type: 2 }, TEST_EVENT, { @@ -90,13 +92,13 @@ describe('Replay (errorSampleRate)', () => { expect(replay).toHaveLastSentReplay({ recordingPayloadHeader: { segment_id: 1 }, replayEventPayload: expect.objectContaining({ + replay_type: 'error', tags: expect.objectContaining({ errorSampleRate: 1, - replayType: 'error', sessionSampleRate: 0, }), }), - events: JSON.stringify([{ data: { isCheckout: true }, timestamp: BASE_TIMESTAMP + 5020, type: 2 }]), + recordingData: JSON.stringify([{ data: { isCheckout: true }, timestamp: BASE_TIMESTAMP + 5020, type: 2 }]), }); jest.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); @@ -104,7 +106,7 @@ describe('Replay (errorSampleRate)', () => { // New checkout when we call `startRecording` again after uploading segment // after an error occurs expect(replay).toHaveLastSentReplay({ - events: JSON.stringify([ + recordingData: JSON.stringify([ { data: { isCheckout: true }, timestamp: BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY + 20, @@ -122,7 +124,7 @@ describe('Replay (errorSampleRate)', () => { await new Promise(process.nextTick); expect(replay).toHaveLastSentReplay({ - events: JSON.stringify([ + recordingData: JSON.stringify([ { type: 5, timestamp: BASE_TIMESTAMP + 10000 + 40, @@ -302,7 +304,7 @@ describe('Replay (errorSampleRate)', () => { await new Promise(process.nextTick); expect(replay).toHaveSentReplay({ - events: JSON.stringify([{ data: { isCheckout: true }, timestamp: BASE_TIMESTAMP, type: 2 }, TEST_EVENT]), + recordingData: JSON.stringify([{ data: { isCheckout: true }, timestamp: BASE_TIMESTAMP, type: 2 }, TEST_EVENT]), replayEventPayload: expect.objectContaining({ replay_start_timestamp: BASE_TIMESTAMP / 1000, // the exception happens roughly 10 seconds after BASE_TIMESTAMP @@ -358,7 +360,7 @@ describe('Replay (errorSampleRate)', () => { // Make sure the old performance event is thrown out replay_start_timestamp: (BASE_TIMESTAMP + ELAPSED + 20) / 1000, }), - events: JSON.stringify([ + recordingData: JSON.stringify([ { data: { isCheckout: true }, timestamp: BASE_TIMESTAMP + ELAPSED + 20, @@ -404,12 +406,12 @@ it('sends a replay after loading the session multiple times', async () => { await new Promise(process.nextTick); expect(replay).toHaveSentReplay({ - events: JSON.stringify([{ data: { isCheckout: true }, timestamp: BASE_TIMESTAMP, type: 2 }, TEST_EVENT]), + recordingData: JSON.stringify([{ data: { isCheckout: true }, timestamp: BASE_TIMESTAMP, type: 2 }, TEST_EVENT]), }); // Latest checkout when we call `startRecording` again after uploading segment // after an error occurs (e.g. when we switch to session replay recording) expect(replay).toHaveLastSentReplay({ - events: JSON.stringify([{ data: { isCheckout: true }, timestamp: BASE_TIMESTAMP + 5020, type: 2 }]), + recordingData: JSON.stringify([{ data: { isCheckout: true }, timestamp: BASE_TIMESTAMP + 5020, type: 2 }]), }); }); diff --git a/packages/replay/test/integration/eventProcessors.test.ts b/packages/replay/test/integration/eventProcessors.test.ts new file mode 100644 index 000000000000..363c5f2d5a74 --- /dev/null +++ b/packages/replay/test/integration/eventProcessors.test.ts @@ -0,0 +1,82 @@ +import { getCurrentHub } from '@sentry/core'; +import type { Event, Hub, Scope } from '@sentry/types'; + +import { BASE_TIMESTAMP } from '..'; +import { resetSdkMock } from '../mocks/resetSdkMock'; +import { useFakeTimers } from '../utils/use-fake-timers'; + +useFakeTimers(); + +describe('Integration | eventProcessors', () => { + let hub: Hub; + let scope: Scope; + + beforeEach(() => { + hub = getCurrentHub(); + scope = hub.pushScope(); + }); + + afterEach(() => { + hub.popScope(); + jest.resetAllMocks(); + }); + + it('handles event processors properly', async () => { + const MUTATED_TIMESTAMP = BASE_TIMESTAMP + 3000; + + const { mockRecord } = await resetSdkMock({ + replayOptions: { + stickySession: false, + }, + }); + + const client = hub.getClient()!; + + jest.runAllTimers(); + const mockTransportSend = jest.spyOn(client.getTransport()!, 'send'); + mockTransportSend.mockReset(); + + const handler1 = jest.fn((event: Event): Event | null => { + event.timestamp = MUTATED_TIMESTAMP; + + return event; + }); + + const handler2 = jest.fn((): Event | null => { + return null; + }); + + scope.addEventProcessor(handler1); + + const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 }; + + mockRecord._emitter(TEST_EVENT); + jest.runAllTimers(); + jest.advanceTimersByTime(1); + await new Promise(process.nextTick); + + expect(mockTransportSend).toHaveBeenCalledTimes(1); + + scope.addEventProcessor(handler2); + + const TEST_EVENT2 = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 }; + + mockRecord._emitter(TEST_EVENT2); + jest.runAllTimers(); + jest.advanceTimersByTime(1); + await new Promise(process.nextTick); + + expect(mockTransportSend).toHaveBeenCalledTimes(1); + + expect(handler1).toHaveBeenCalledTimes(2); + expect(handler2).toHaveBeenCalledTimes(1); + + // This receives an envelope, which is a deeply nested array + // We only care about the fact that the timestamp was mutated + expect(mockTransportSend).toHaveBeenCalledWith( + expect.arrayContaining([ + expect.arrayContaining([expect.arrayContaining([expect.objectContaining({ timestamp: MUTATED_TIMESTAMP })])]), + ]), + ); + }); +}); diff --git a/packages/replay/test/integration/events.test.ts b/packages/replay/test/integration/events.test.ts new file mode 100644 index 000000000000..5168426dfd79 --- /dev/null +++ b/packages/replay/test/integration/events.test.ts @@ -0,0 +1,199 @@ +import { getCurrentHub } from '@sentry/core'; + +import { WINDOW } from '../../src/constants'; +import type { ReplayContainer } from '../../src/replay'; +import { addEvent } from '../../src/util/addEvent'; +import { PerformanceEntryResource } from '../fixtures/performanceEntry/resource'; +import type { RecordMock } from '../index'; +import { BASE_TIMESTAMP } from '../index'; +import { resetSdkMock } from '../mocks/resetSdkMock'; +import { clearSession } from '../utils/clearSession'; +import { useFakeTimers } from '../utils/use-fake-timers'; + +useFakeTimers(); + +async function advanceTimers(time: number) { + jest.advanceTimersByTime(time); + await new Promise(process.nextTick); +} + +describe('Integration | events', () => { + let replay: ReplayContainer; + let mockRecord: RecordMock; + let mockTransportSend: jest.SpyInstance; + const prevLocation = WINDOW.location; + + beforeAll(async () => { + jest.setSystemTime(new Date(BASE_TIMESTAMP)); + jest.runAllTimers(); + }); + + beforeEach(async () => { + ({ mockRecord, replay } = await resetSdkMock({ + replayOptions: { + stickySession: false, + }, + })); + + mockTransportSend = jest.spyOn(getCurrentHub().getClient()!.getTransport()!, 'send'); + + // Create a new session and clear mocks because a segment (from initial + // checkout) will have already been uploaded by the time the tests run + clearSession(replay); + replay['_loadSession']({ expiry: 0 }); + mockTransportSend.mockClear(); + }); + + afterEach(async () => { + jest.runAllTimers(); + await new Promise(process.nextTick); + Object.defineProperty(WINDOW, 'location', { + value: prevLocation, + writable: true, + }); + clearSession(replay); + jest.clearAllMocks(); + mockRecord.takeFullSnapshot.mockClear(); + replay.stop(); + }); + + it('does not create replay event when there are no events to send', async () => { + Object.defineProperty(document, 'visibilityState', { + configurable: true, + get: function () { + return 'hidden'; + }, + }); + + document.dispatchEvent(new Event('visibilitychange')); + await new Promise(process.nextTick); + expect(replay).not.toHaveLastSentReplay(); + + // Pretend 5 seconds have passed + const ELAPSED = 5000; + await advanceTimers(ELAPSED); + + const TEST_EVENT = { + data: {}, + timestamp: BASE_TIMESTAMP + ELAPSED, + type: 2, + }; + + addEvent(replay, TEST_EVENT); + WINDOW.dispatchEvent(new Event('blur')); + await new Promise(process.nextTick); + + expect(replay).toHaveLastSentReplay({ + replayEventPayload: expect.objectContaining({ + replay_start_timestamp: BASE_TIMESTAMP / 1000, + urls: ['http://localhost/'], // this doesn't truly test if we are capturing the right URL as we don't change URLs, but good enough + }), + }); + }); + + it('has correct timestamps when there are events earlier than initial timestamp', async function () { + clearSession(replay); + replay['_loadSession']({ expiry: 0 }); + mockTransportSend.mockClear(); + Object.defineProperty(document, 'visibilityState', { + configurable: true, + get: function () { + return 'hidden'; + }, + }); + + document.dispatchEvent(new Event('visibilitychange')); + await new Promise(process.nextTick); + expect(replay).not.toHaveLastSentReplay(); + + // Pretend 5 seconds have passed + const ELAPSED = 5000; + await advanceTimers(ELAPSED); + + const TEST_EVENT = { + data: {}, + timestamp: BASE_TIMESTAMP + ELAPSED, + type: 2, + }; + + addEvent(replay, TEST_EVENT); + + // Add a fake event that started BEFORE + addEvent(replay, { + data: {}, + timestamp: (BASE_TIMESTAMP - 10000) / 1000, + type: 5, + }); + + WINDOW.dispatchEvent(new Event('blur')); + await new Promise(process.nextTick); + expect(replay).toHaveLastSentReplay({ + replayEventPayload: expect.objectContaining({ + replay_start_timestamp: (BASE_TIMESTAMP - 10000) / 1000, + urls: ['http://localhost/'], // this doesn't truly test if we are capturing the right URL as we don't change URLs, but good enough + tags: expect.objectContaining({ + errorSampleRate: 0, + sessionSampleRate: 1, + }), + }), + }); + }); + + it('does not have stale `replay_start_timestamp` due to an old time origin', async function () { + const ELAPSED = 86400000 * 2; // 2 days + // Add a mock performance event that happens 2 days ago. This can happen in the browser + // when a tab has sat idle for a long period and user comes back to it. + // + // We pass a negative start time as it's a bit difficult to mock + // `@sentry/utils/browserPerformanceTimeOrigin`. This would not happen in + // real world. + replay.performanceEvents.push( + PerformanceEntryResource({ + startTime: -ELAPSED, + }), + ); + + // This should be null because `addEvent` has not been called yet + expect(replay.getContext().earliestEvent).toBe(null); + expect(mockTransportSend).toHaveBeenCalledTimes(0); + + // A new checkout occurs (i.e. a new session was started) + const TEST_EVENT = { + data: {}, + timestamp: BASE_TIMESTAMP, + type: 2, + }; + + addEvent(replay, TEST_EVENT); + // This event will trigger a flush + WINDOW.dispatchEvent(new Event('blur')); + jest.runAllTimers(); + await new Promise(process.nextTick); + + expect(mockTransportSend).toHaveBeenCalledTimes(1); + expect(replay).toHaveLastSentReplay({ + replayEventPayload: expect.objectContaining({ + // Make sure the old performance event is thrown out + replay_start_timestamp: BASE_TIMESTAMP / 1000, + }), + recordingData: JSON.stringify([ + TEST_EVENT, + { + type: 5, + timestamp: BASE_TIMESTAMP / 1000, + data: { + tag: 'breadcrumb', + payload: { + timestamp: BASE_TIMESTAMP / 1000, + type: 'default', + category: 'ui.blur', + }, + }, + }, + ]), + }); + + // This gets reset after sending replay + expect(replay.getContext().earliestEvent).toBe(null); + }); +}); diff --git a/packages/replay/test/integration/flush.test.ts b/packages/replay/test/integration/flush.test.ts new file mode 100644 index 000000000000..c0c143b255e4 --- /dev/null +++ b/packages/replay/test/integration/flush.test.ts @@ -0,0 +1,263 @@ +import * as SentryUtils from '@sentry/utils'; + +import { DEFAULT_FLUSH_MIN_DELAY, SESSION_IDLE_DURATION, WINDOW } from '../../src/constants'; +import type { ReplayContainer } from '../../src/replay'; +import type { EventBuffer } from '../../src/types'; +import * as AddMemoryEntry from '../../src/util/addMemoryEntry'; +import { createPerformanceEntries } from '../../src/util/createPerformanceEntries'; +import { createPerformanceSpans } from '../../src/util/createPerformanceSpans'; +import * as SendReplay from '../../src/util/sendReplay'; +import { BASE_TIMESTAMP, mockRrweb, mockSdk } from '../index'; +import { clearSession } from '../utils/clearSession'; +import { useFakeTimers } from '../utils/use-fake-timers'; + +useFakeTimers(); + +async function advanceTimers(time: number) { + jest.advanceTimersByTime(time); + await new Promise(process.nextTick); +} + +type MockSendReplay = jest.MockedFunction; +type MockAddPerformanceEntries = jest.MockedFunction; +type MockAddMemoryEntry = jest.SpyInstance; +type MockEventBufferFinish = jest.MockedFunction; +type MockFlush = jest.MockedFunction; +type MockRunFlush = jest.MockedFunction; + +const prevLocation = WINDOW.location; + +describe('Integration | flush', () => { + let domHandler: (args: any) => any; + + const { record: mockRecord } = mockRrweb(); + + let replay: ReplayContainer; + let mockSendReplay: MockSendReplay; + let mockFlush: MockFlush; + let mockRunFlush: MockRunFlush; + let mockEventBufferFinish: MockEventBufferFinish; + let mockAddMemoryEntry: MockAddMemoryEntry; + let mockAddPerformanceEntries: MockAddPerformanceEntries; + + beforeAll(async () => { + jest.spyOn(SentryUtils, 'addInstrumentationHandler').mockImplementation((type, handler: (args: any) => any) => { + if (type === 'dom') { + domHandler = handler; + } + }); + + ({ replay } = await mockSdk()); + + mockSendReplay = jest.spyOn(SendReplay, 'sendReplay'); + mockSendReplay.mockImplementation( + jest.fn(async () => { + return; + }), + ); + + // @ts-ignore private API + mockFlush = jest.spyOn(replay, '_flush'); + + // @ts-ignore private API + mockRunFlush = jest.spyOn(replay, '_runFlush'); + + // @ts-ignore private API + mockAddPerformanceEntries = jest.spyOn(replay, '_addPerformanceEntries'); + + mockAddPerformanceEntries.mockImplementation(async () => { + return []; + }); + + mockAddMemoryEntry = jest.spyOn(AddMemoryEntry, 'addMemoryEntry'); + }); + + beforeEach(() => { + jest.runAllTimers(); + jest.setSystemTime(new Date(BASE_TIMESTAMP)); + mockSendReplay.mockClear(); + replay.eventBuffer?.destroy(); + mockAddPerformanceEntries.mockClear(); + mockFlush.mockClear(); + mockRunFlush.mockClear(); + mockAddMemoryEntry.mockClear(); + + if (replay.eventBuffer) { + jest.spyOn(replay.eventBuffer, 'finish'); + } + mockEventBufferFinish = replay.eventBuffer?.finish as MockEventBufferFinish; + mockEventBufferFinish.mockClear(); + }); + + afterEach(async () => { + jest.runAllTimers(); + await new Promise(process.nextTick); + jest.setSystemTime(new Date(BASE_TIMESTAMP)); + sessionStorage.clear(); + clearSession(replay); + replay['_loadSession']({ expiry: SESSION_IDLE_DURATION }); + mockRecord.takeFullSnapshot.mockClear(); + Object.defineProperty(WINDOW, 'location', { + value: prevLocation, + writable: true, + }); + }); + + afterAll(() => { + replay && replay.stop(); + }); + + it('flushes twice after multiple flush() calls)', async () => { + // blur events cause an immediate flush (as well as a flush due to adding a + // breadcrumb) -- this means that the first blur event will be flushed and + // the following blur events will all call a debounced flush function, which + // should end up queueing a second flush + + WINDOW.dispatchEvent(new Event('blur')); + WINDOW.dispatchEvent(new Event('blur')); + WINDOW.dispatchEvent(new Event('blur')); + WINDOW.dispatchEvent(new Event('blur')); + + expect(mockFlush).toHaveBeenCalledTimes(4); + + jest.runAllTimers(); + await new Promise(process.nextTick); + expect(mockRunFlush).toHaveBeenCalledTimes(1); + + jest.runAllTimers(); + await new Promise(process.nextTick); + expect(mockRunFlush).toHaveBeenCalledTimes(2); + + jest.runAllTimers(); + await new Promise(process.nextTick); + expect(mockRunFlush).toHaveBeenCalledTimes(2); + }); + + it('long first flush enqueues following events', async () => { + // Mock this to resolve after 20 seconds so that we can queue up following flushes + mockAddPerformanceEntries.mockImplementationOnce(async () => { + return await new Promise(resolve => setTimeout(resolve, 20000)); + }); + + expect(mockAddPerformanceEntries).not.toHaveBeenCalled(); + + // flush #1 @ t=0s - due to blur + WINDOW.dispatchEvent(new Event('blur')); + expect(mockFlush).toHaveBeenCalledTimes(1); + expect(mockRunFlush).toHaveBeenCalledTimes(1); + + // This will attempt to flush in 5 seconds (flushMinDelay) + domHandler({ + name: 'click', + }); + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + // flush #2 @ t=5s - due to click + expect(mockFlush).toHaveBeenCalledTimes(2); + + await advanceTimers(1000); + // flush #3 @ t=6s - due to blur + WINDOW.dispatchEvent(new Event('blur')); + expect(mockFlush).toHaveBeenCalledTimes(3); + + // NOTE: Blur also adds a breadcrumb which calls `addUpdate`, meaning it will + // flush after `flushMinDelay`, but this gets cancelled by the blur + await advanceTimers(8000); + expect(mockFlush).toHaveBeenCalledTimes(3); + + // flush #4 @ t=14s - due to blur + WINDOW.dispatchEvent(new Event('blur')); + expect(mockFlush).toHaveBeenCalledTimes(4); + + expect(mockRunFlush).toHaveBeenCalledTimes(1); + await advanceTimers(6000); + // t=20s + // addPerformanceEntries is finished, `flushLock` promise is resolved, calls + // debouncedFlush, which will call `flush` in 1 second + expect(mockFlush).toHaveBeenCalledTimes(4); + // sendReplay is called with replayId, events, segment + + expect(mockSendReplay).toHaveBeenLastCalledWith({ + recordingData: expect.any(String), + replayId: expect.any(String), + includeReplayStartTimestamp: true, + segmentId: 0, + eventContext: expect.anything(), + session: expect.any(Object), + options: expect.any(Object), + timestamp: expect.any(Number), + }); + + // Add this to test that segment ID increases + mockAddPerformanceEntries.mockImplementationOnce(() => + Promise.all( + createPerformanceSpans( + replay, + createPerformanceEntries([ + { + name: 'https://sentry.io/foo.js', + entryType: 'resource', + startTime: 176.59999990463257, + duration: 5.600000023841858, + initiatorType: 'link', + nextHopProtocol: 'h2', + workerStart: 177.5, + redirectStart: 0, + redirectEnd: 0, + fetchStart: 177.69999992847443, + domainLookupStart: 177.69999992847443, + domainLookupEnd: 177.69999992847443, + connectStart: 177.69999992847443, + connectEnd: 177.69999992847443, + secureConnectionStart: 177.69999992847443, + requestStart: 177.5, + responseStart: 181, + responseEnd: 182.19999992847443, + transferSize: 0, + encodedBodySize: 0, + decodedBodySize: 0, + serverTiming: [], + } as unknown as PerformanceResourceTiming, + ]), + ), + ), + ); + // flush #5 @ t=25s - debounced flush calls `flush` + // 20s + `flushMinDelay` which is 5 seconds + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + expect(mockFlush).toHaveBeenCalledTimes(5); + expect(mockRunFlush).toHaveBeenCalledTimes(2); + expect(mockSendReplay).toHaveBeenLastCalledWith({ + recordingData: expect.any(String), + replayId: expect.any(String), + includeReplayStartTimestamp: false, + segmentId: 1, + eventContext: expect.anything(), + session: expect.any(Object), + options: expect.any(Object), + timestamp: expect.any(Number), + }); + + // Make sure there's no other calls + jest.runAllTimers(); + await new Promise(process.nextTick); + expect(mockSendReplay).toHaveBeenCalledTimes(2); + }); + + it('has single flush when checkout flush and debounce flush happen near simultaneously', async () => { + // click happens first + domHandler({ + name: 'click', + }); + + // checkout + const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 2 }; + mockRecord._emitter(TEST_EVENT); + + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + expect(mockFlush).toHaveBeenCalledTimes(1); + + // Make sure there's nothing queued up after + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + expect(mockFlush).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/replay/test/unit/index-integrationSettings.test.ts b/packages/replay/test/integration/integrationSettings.test.ts similarity index 98% rename from packages/replay/test/unit/index-integrationSettings.test.ts rename to packages/replay/test/integration/integrationSettings.test.ts index c78214b8c2ea..6c63839b1c06 100644 --- a/packages/replay/test/unit/index-integrationSettings.test.ts +++ b/packages/replay/test/integration/integrationSettings.test.ts @@ -1,7 +1,7 @@ import { MASK_ALL_TEXT_SELECTOR } from '../../src/constants'; -import { mockSdk } from './../index'; +import { mockSdk } from '../index'; -describe('integration settings', () => { +describe('Integration | integrationSettings', () => { beforeEach(() => { jest.resetModules(); }); diff --git a/packages/replay/test/integration/rateLimiting.test.ts b/packages/replay/test/integration/rateLimiting.test.ts new file mode 100644 index 000000000000..31e15638c314 --- /dev/null +++ b/packages/replay/test/integration/rateLimiting.test.ts @@ -0,0 +1,292 @@ +import { getCurrentHub } from '@sentry/core'; +import type { Transport, TransportMakeRequestResponse } from '@sentry/types'; + +import { DEFAULT_FLUSH_MIN_DELAY, SESSION_IDLE_DURATION } from '../../src/constants'; +import type { ReplayContainer } from '../../src/replay'; +import * as SendReplayRequest from '../../src/util/sendReplayRequest'; +import { BASE_TIMESTAMP, mockSdk } from '../index'; +import { mockRrweb } from '../mocks/mockRrweb'; +import { clearSession } from '../utils/clearSession'; +import { useFakeTimers } from '../utils/use-fake-timers'; + +useFakeTimers(); + +async function advanceTimers(time: number) { + jest.advanceTimersByTime(time); + await new Promise(process.nextTick); +} + +type MockTransportSend = jest.MockedFunction; + +describe('Integration | rate-limiting behaviour', () => { + let replay: ReplayContainer; + let mockTransportSend: MockTransportSend; + let mockSendReplayRequest: jest.MockedFunction; + const { record: mockRecord } = mockRrweb(); + + beforeAll(async () => { + jest.setSystemTime(new Date(BASE_TIMESTAMP)); + + ({ replay } = await mockSdk({ + replayOptions: { + stickySession: false, + }, + })); + + jest.runAllTimers(); + mockTransportSend = getCurrentHub()?.getClient()?.getTransport()?.send as MockTransportSend; + mockSendReplayRequest = jest.spyOn(SendReplayRequest, 'sendReplayRequest'); + }); + + beforeEach(() => { + jest.setSystemTime(new Date(BASE_TIMESTAMP)); + mockRecord.takeFullSnapshot.mockClear(); + mockTransportSend.mockClear(); + + // Create a new session and clear mocks because a segment (from initial + // checkout) will have already been uploaded by the time the tests run + clearSession(replay); + replay['_loadSession']({ expiry: 0 }); + + mockSendReplayRequest.mockClear(); + }); + + afterEach(async () => { + jest.runAllTimers(); + await new Promise(process.nextTick); + jest.setSystemTime(new Date(BASE_TIMESTAMP)); + clearSession(replay); + jest.clearAllMocks(); + replay['_loadSession']({ expiry: SESSION_IDLE_DURATION }); + }); + + afterAll(() => { + replay && replay.stop(); + }); + + it.each([ + { + statusCode: 429, + headers: { + 'x-sentry-rate-limits': '30', + 'retry-after': null, + }, + }, + { + statusCode: 429, + headers: { + 'x-sentry-rate-limits': '30:replay', + 'retry-after': null, + }, + }, + { + statusCode: 429, + headers: { + 'x-sentry-rate-limits': null, + 'retry-after': '30', + }, + }, + ] as TransportMakeRequestResponse[])( + 'pauses recording and flushing a rate limit is hit and resumes both after the rate limit duration is over %j', + async rateLimitResponse => { + expect(replay.session?.segmentId).toBe(0); + jest.spyOn(replay, 'pause'); + jest.spyOn(replay, 'resume'); + // @ts-ignore private API + jest.spyOn(replay, '_handleRateLimit'); + + const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 2 }; + + mockTransportSend.mockImplementationOnce(() => { + return Promise.resolve(rateLimitResponse); + }); + + mockRecord._emitter(TEST_EVENT); + + // T = base + 5 + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + + expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); + expect(mockTransportSend).toHaveBeenCalledTimes(1); + expect(replay).toHaveLastSentReplay({ recordingData: JSON.stringify([TEST_EVENT]) }); + + expect(replay['_handleRateLimit']).toHaveBeenCalledTimes(1); + // resume() was called once before we even started + expect(replay.resume).not.toHaveBeenCalled(); + expect(replay.pause).toHaveBeenCalledTimes(1); + + // No user activity to trigger an update + expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); + expect(replay.session?.segmentId).toBe(1); + + // let's simulate the rate-limit time of inactivity (30secs) and check that we don't do anything in the meantime + const TEST_EVENT2 = { data: {}, timestamp: BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY, type: 3 }; + for (let i = 0; i < 5; i++) { + const ev = { + ...TEST_EVENT2, + timestamp: BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY * (i + 1), + }; + mockRecord._emitter(ev); + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + expect(replay.isPaused()).toBe(true); + expect(mockSendReplayRequest).toHaveBeenCalledTimes(1); + expect(mockTransportSend).toHaveBeenCalledTimes(1); + } + + // T = base + 35 + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + + // now, recording should resume and first, we expect a checkout event to be sent, as resume() + // should trigger a full snapshot + expect(replay.resume).toHaveBeenCalledTimes(1); + expect(replay.isPaused()).toBe(false); + + expect(mockSendReplayRequest).toHaveBeenCalledTimes(2); + expect(replay).toHaveLastSentReplay({ + recordingData: JSON.stringify([ + { data: { isCheckout: true }, timestamp: BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY * 7, type: 2 }, + ]), + }); + + // and let's also emit a new event and check that it is recorded + const TEST_EVENT3 = { + data: {}, + timestamp: BASE_TIMESTAMP + 7 * DEFAULT_FLUSH_MIN_DELAY, + type: 3, + }; + mockRecord._emitter(TEST_EVENT3); + + // T = base + 40 + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + expect(mockSendReplayRequest).toHaveBeenCalledTimes(3); + expect(replay).toHaveLastSentReplay({ recordingData: JSON.stringify([TEST_EVENT3]) }); + + // nothing should happen afterwards + // T = base + 60 + await advanceTimers(20_000); + expect(mockSendReplayRequest).toHaveBeenCalledTimes(3); + expect(replay).toHaveLastSentReplay({ recordingData: JSON.stringify([TEST_EVENT3]) }); + + // events array should be empty + expect(replay.eventBuffer?.pendingLength).toBe(0); + }, + ); + + it('handles rate-limits from a plain 429 response without any retry time', async () => { + expect(replay.session?.segmentId).toBe(0); + jest.spyOn(replay, 'pause'); + jest.spyOn(replay, 'resume'); + // @ts-ignore private API + jest.spyOn(replay, '_handleRateLimit'); + + const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 2 }; + + mockTransportSend.mockImplementationOnce(() => { + return Promise.resolve({ statusCode: 429 }); + }); + + mockRecord._emitter(TEST_EVENT); + + // T = base + 5 + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + + expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); + expect(mockTransportSend).toHaveBeenCalledTimes(1); + expect(replay).toHaveLastSentReplay({ recordingData: JSON.stringify([TEST_EVENT]) }); + + expect(replay['_handleRateLimit']).toHaveBeenCalledTimes(1); + // resume() was called once before we even started + expect(replay.resume).not.toHaveBeenCalled(); + expect(replay.pause).toHaveBeenCalledTimes(1); + + // No user activity to trigger an update + expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); + expect(replay.session?.segmentId).toBe(1); + + // let's simulate the rate-limit time of inactivity (60secs) and check that we don't do anything in the meantime + // 60secs are the default we fall back to in the plain 429 case in updateRateLimits() + const TEST_EVENT2 = { data: {}, timestamp: BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY, type: 3 }; + for (let i = 0; i < 11; i++) { + const ev = { + ...TEST_EVENT2, + timestamp: BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY * (i + 1), + }; + mockRecord._emitter(ev); + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + expect(replay.isPaused()).toBe(true); + expect(mockSendReplayRequest).toHaveBeenCalledTimes(1); + expect(mockTransportSend).toHaveBeenCalledTimes(1); + } + + // T = base + 60 + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + + // now, recording should resume and first, we expect a checkout event to be sent, as resume() + // should trigger a full snapshot + expect(replay.resume).toHaveBeenCalledTimes(1); + expect(replay.isPaused()).toBe(false); + + expect(mockSendReplayRequest).toHaveBeenCalledTimes(2); + expect(replay).toHaveLastSentReplay({ + recordingData: JSON.stringify([ + { data: { isCheckout: true }, timestamp: BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY * 13, type: 2 }, + ]), + }); + + // and let's also emit a new event and check that it is recorded + const TEST_EVENT3 = { + data: {}, + timestamp: BASE_TIMESTAMP + 7 * DEFAULT_FLUSH_MIN_DELAY, + type: 3, + }; + mockRecord._emitter(TEST_EVENT3); + + // T = base + 65 + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + expect(mockSendReplayRequest).toHaveBeenCalledTimes(3); + expect(replay).toHaveLastSentReplay({ recordingData: JSON.stringify([TEST_EVENT3]) }); + + // nothing should happen afterwards + // T = base + 85 + await advanceTimers(20_000); + expect(mockSendReplayRequest).toHaveBeenCalledTimes(3); + expect(replay).toHaveLastSentReplay({ recordingData: JSON.stringify([TEST_EVENT3]) }); + + // events array should be empty + expect(replay.eventBuffer?.pendingLength).toBe(0); + }); + + it("doesn't do anything, if a rate limit is hit and recording is already paused", async () => { + let paused = false; + expect(replay.session?.segmentId).toBe(0); + jest.spyOn(replay, 'isPaused').mockImplementation(() => { + return paused; + }); + jest.spyOn(replay, 'pause'); + jest.spyOn(replay, 'resume'); + // @ts-ignore private API + jest.spyOn(replay, '_handleRateLimit'); + + const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 2 }; + + mockTransportSend.mockImplementationOnce(() => { + return Promise.resolve({ statusCode: 429 }); + }); + + mockRecord._emitter(TEST_EVENT); + paused = true; + + // T = base + 5 + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + + expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); + expect(mockTransportSend).toHaveBeenCalledTimes(1); + + expect(replay).toHaveLastSentReplay({ recordingData: JSON.stringify([TEST_EVENT]) }); + + expect(replay['_handleRateLimit']).toHaveBeenCalledTimes(1); + expect(replay.resume).not.toHaveBeenCalled(); + expect(replay.isPaused).toHaveBeenCalledTimes(2); + expect(replay.pause).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/replay/test/integration/rrweb.test.ts b/packages/replay/test/integration/rrweb.test.ts new file mode 100644 index 000000000000..15c8cdba432b --- /dev/null +++ b/packages/replay/test/integration/rrweb.test.ts @@ -0,0 +1,31 @@ +import { MASK_ALL_TEXT_SELECTOR } from '../../src/constants'; +import { resetSdkMock } from '../mocks/resetSdkMock'; +import { useFakeTimers } from '../utils/use-fake-timers'; + +useFakeTimers(); + +describe('Integration | rrweb', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('calls rrweb.record with custom options', async () => { + const { mockRecord } = await resetSdkMock({ + replayOptions: { + ignoreClass: 'sentry-test-ignore', + stickySession: false, + }, + }); + expect(mockRecord.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "blockClass": "sentry-block", + "blockSelector": "[data-sentry-block],img,image,svg,path,rect,area,video,object,picture,embed,map,audio", + "emit": [Function], + "ignoreClass": "sentry-test-ignore", + "maskAllInputs": true, + "maskTextClass": "sentry-mask", + "maskTextSelector": "${MASK_ALL_TEXT_SELECTOR}", + } + `); + }); +}); diff --git a/packages/replay/test/unit/index-sampling.test.ts b/packages/replay/test/integration/sampling.test.ts similarity index 56% rename from packages/replay/test/unit/index-sampling.test.ts rename to packages/replay/test/integration/sampling.test.ts index 1ce841128895..049329ebda3e 100644 --- a/packages/replay/test/unit/index-sampling.test.ts +++ b/packages/replay/test/integration/sampling.test.ts @@ -1,10 +1,9 @@ -// mock functions need to be imported first -import { mockRrweb, mockSdk } from './../index'; -import { useFakeTimers } from './../utils/use-fake-timers'; +import { mockRrweb, mockSdk } from '../index'; +import { useFakeTimers } from '../utils/use-fake-timers'; useFakeTimers(); -describe('Replay (sampling)', () => { +describe('Integration | sampling', () => { it('does nothing if not sampled', async () => { const { record: mockRecord } = mockRrweb(); const { replay } = await mockSdk({ @@ -17,21 +16,18 @@ describe('Replay (sampling)', () => { }, }); - jest.spyOn(replay, 'loadSession'); - jest.spyOn(replay, 'addListeners'); - // @ts-ignore private - expect(replay.initialState).toEqual(undefined); + // @ts-ignore private API + const spyAddListeners = jest.spyOn(replay, '_addListeners'); jest.runAllTimers(); expect(replay.session?.sampled).toBe(false); - // @ts-ignore private - expect(replay._context).toEqual( + expect(replay.getContext()).toEqual( expect.objectContaining({ initialTimestamp: expect.any(Number), initialUrl: 'http://localhost/', }), ); expect(mockRecord).not.toHaveBeenCalled(); - expect(replay.addListeners).not.toHaveBeenCalled(); + expect(spyAddListeners).not.toHaveBeenCalled(); }); }); diff --git a/packages/replay/test/integration/sendReplayEvent.test.ts b/packages/replay/test/integration/sendReplayEvent.test.ts new file mode 100644 index 000000000000..9a4c62fa8ede --- /dev/null +++ b/packages/replay/test/integration/sendReplayEvent.test.ts @@ -0,0 +1,432 @@ +import * as SentryCore from '@sentry/core'; +import type { Transport } from '@sentry/types'; +import * as SentryUtils from '@sentry/utils'; + +import { DEFAULT_FLUSH_MIN_DELAY, SESSION_IDLE_DURATION, WINDOW } from '../../src/constants'; +import type { ReplayContainer } from '../../src/replay'; +import { addEvent } from '../../src/util/addEvent'; +import * as SendReplayRequest from '../../src/util/sendReplayRequest'; +import { BASE_TIMESTAMP, mockRrweb, mockSdk } from '../index'; +import { clearSession } from '../utils/clearSession'; +import { useFakeTimers } from '../utils/use-fake-timers'; + +useFakeTimers(); + +async function advanceTimers(time: number) { + jest.advanceTimersByTime(time); + await new Promise(process.nextTick); +} + +type MockTransportSend = jest.MockedFunction; + +describe('Integration | sendReplayEvent', () => { + let replay: ReplayContainer; + let mockTransportSend: MockTransportSend; + let mockSendReplayRequest: jest.SpyInstance; + let domHandler: (args: any) => any; + const { record: mockRecord } = mockRrweb(); + + beforeAll(async () => { + jest.setSystemTime(new Date(BASE_TIMESTAMP)); + jest.spyOn(SentryUtils, 'addInstrumentationHandler').mockImplementation((type, handler: (args: any) => any) => { + if (type === 'dom') { + domHandler = handler; + } + }); + + ({ replay } = await mockSdk({ + replayOptions: { + stickySession: false, + _experiments: { + captureExceptions: true, + }, + }, + })); + + mockSendReplayRequest = jest.spyOn(SendReplayRequest, 'sendReplayRequest'); + + jest.runAllTimers(); + mockTransportSend = SentryCore.getCurrentHub()?.getClient()?.getTransport()?.send as MockTransportSend; + }); + + beforeEach(() => { + jest.setSystemTime(new Date(BASE_TIMESTAMP)); + mockRecord.takeFullSnapshot.mockClear(); + mockTransportSend.mockClear(); + + // Create a new session and clear mocks because a segment (from initial + // checkout) will have already been uploaded by the time the tests run + clearSession(replay); + replay['_loadSession']({ expiry: 0 }); + + mockSendReplayRequest.mockClear(); + }); + + afterEach(async () => { + jest.runAllTimers(); + await new Promise(process.nextTick); + jest.setSystemTime(new Date(BASE_TIMESTAMP)); + clearSession(replay); + replay['_loadSession']({ expiry: SESSION_IDLE_DURATION }); + }); + + afterAll(() => { + replay && replay.stop(); + }); + + it('uploads a replay event when document becomes hidden', async () => { + mockRecord.takeFullSnapshot.mockClear(); + Object.defineProperty(document, 'visibilityState', { + configurable: true, + get: function () { + return 'hidden'; + }, + }); + + // Pretend 5 seconds have passed + const ELAPSED = 5000; + jest.advanceTimersByTime(ELAPSED); + + const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 2 }; + addEvent(replay, TEST_EVENT); + + document.dispatchEvent(new Event('visibilitychange')); + + await new Promise(process.nextTick); + + expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); + + expect(replay).toHaveLastSentReplay({ recordingData: JSON.stringify([TEST_EVENT]) }); + + // Session's last activity is not updated because we do not consider + // visibilitystate as user being active + expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); + expect(replay.session?.segmentId).toBe(1); + + // events array should be empty + expect(replay.eventBuffer?.pendingLength).toBe(0); + }); + + it('update last activity when user clicks mouse', async () => { + expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); + + domHandler({ + name: 'click', + }); + + expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); + + // Pretend 5 seconds have passed + const ELAPSED = 5000; + jest.advanceTimersByTime(ELAPSED); + + domHandler({ + name: 'click', + }); + + expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP + ELAPSED); + }); + + it('uploads a replay event if 5 seconds have elapsed since the last replay event occurred', async () => { + const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 }; + mockRecord._emitter(TEST_EVENT); + // Pretend 5 seconds have passed + const ELAPSED = 5000; + await advanceTimers(ELAPSED); + + expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); + + expect(replay).toHaveLastSentReplay({ recordingData: JSON.stringify([TEST_EVENT]) }); + + // No user activity to trigger an update + expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); + expect(replay.session?.segmentId).toBe(1); + + // events array should be empty + expect(replay.eventBuffer?.pendingLength).toBe(0); + }); + + it('uploads a replay event if 15 seconds have elapsed since the last replay upload', async () => { + const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 }; + // Fire a new event every 4 seconds, 4 times + [...Array(4)].forEach(() => { + mockRecord._emitter(TEST_EVENT); + jest.advanceTimersByTime(4000); + }); + + // We are at time = +16seconds now (relative to BASE_TIMESTAMP) + // The next event should cause an upload immediately + mockRecord._emitter(TEST_EVENT); + await new Promise(process.nextTick); + + expect(replay).toHaveLastSentReplay({ + recordingData: JSON.stringify([...Array(5)].map(() => TEST_EVENT)), + }); + + // There should also not be another attempt at an upload 5 seconds after the last replay event + mockTransportSend.mockClear(); + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + expect(replay).not.toHaveLastSentReplay(); + + expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); + expect(replay.session?.segmentId).toBe(1); + // events array should be empty + expect(replay.eventBuffer?.pendingLength).toBe(0); + + // Let's make sure it continues to work + mockTransportSend.mockClear(); + mockRecord._emitter(TEST_EVENT); + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + expect(replay).toHaveLastSentReplay({ recordingData: JSON.stringify([TEST_EVENT]) }); + }); + + it('uploads a replay event when WINDOW is blurred', async () => { + Object.defineProperty(document, 'visibilityState', { + configurable: true, + get: function () { + return 'hidden'; + }, + }); + + // Pretend 5 seconds have passed + const ELAPSED = 5000; + jest.advanceTimersByTime(ELAPSED); + + const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 2 }; + const hiddenBreadcrumb = { + type: 5, + timestamp: +new Date(BASE_TIMESTAMP + ELAPSED) / 1000, + data: { + tag: 'breadcrumb', + payload: { + timestamp: +new Date(BASE_TIMESTAMP + ELAPSED) / 1000, + type: 'default', + category: 'ui.blur', + }, + }, + }; + + addEvent(replay, TEST_EVENT); + WINDOW.dispatchEvent(new Event('blur')); + await new Promise(process.nextTick); + expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); + expect(replay).toHaveLastSentReplay({ + recordingData: JSON.stringify([TEST_EVENT, hiddenBreadcrumb]), + }); + // Session's last activity should not be updated + expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); + // events array should be empty + expect(replay.eventBuffer?.pendingLength).toBe(0); + }); + + it('uploads a replay event when document becomes hidden', async () => { + Object.defineProperty(document, 'visibilityState', { + configurable: true, + get: function () { + return 'hidden'; + }, + }); + // Pretend 5 seconds have passed + const ELAPSED = 5000; + jest.advanceTimersByTime(ELAPSED); + + const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 2 }; + + addEvent(replay, TEST_EVENT); + document.dispatchEvent(new Event('visibilitychange')); + jest.runAllTimers(); + await new Promise(process.nextTick); + + expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); + expect(replay).toHaveLastSentReplay({ recordingData: JSON.stringify([TEST_EVENT]) }); + + // Session's last activity is not updated because we do not consider + // visibilitystate as user being active + expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); + // events array should be empty + expect(replay.eventBuffer?.pendingLength).toBe(0); + }); + + it('uploads a replay event if 5 seconds have elapsed since the last replay event occurred', async () => { + const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 }; + mockRecord._emitter(TEST_EVENT); + // Pretend 5 seconds have passed + const ELAPSED = 5000; + await advanceTimers(ELAPSED); + + expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); + expect(mockTransportSend).toHaveBeenCalledTimes(1); + expect(replay).toHaveLastSentReplay({ recordingData: JSON.stringify([TEST_EVENT]) }); + + // No user activity to trigger an update + expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); + expect(replay.session?.segmentId).toBe(1); + + // events array should be empty + expect(replay.eventBuffer?.pendingLength).toBe(0); + }); + + it('uploads a replay event if 15 seconds have elapsed since the last replay upload', async () => { + const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 }; + // Fire a new event every 4 seconds, 4 times + [...Array(4)].forEach(() => { + mockRecord._emitter(TEST_EVENT); + jest.advanceTimersByTime(4000); + }); + + // We are at time = +16seconds now (relative to BASE_TIMESTAMP) + // The next event should cause an upload immediately + mockRecord._emitter(TEST_EVENT); + await new Promise(process.nextTick); + + expect(replay).toHaveLastSentReplay({ + recordingData: JSON.stringify([...Array(5)].map(() => TEST_EVENT)), + }); + + // There should also not be another attempt at an upload 5 seconds after the last replay event + mockTransportSend.mockClear(); + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + + expect(replay).not.toHaveLastSentReplay(); + + expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); + expect(replay.session?.segmentId).toBe(1); + // events array should be empty + expect(replay.eventBuffer?.pendingLength).toBe(0); + + // Let's make sure it continues to work + mockTransportSend.mockClear(); + mockRecord._emitter(TEST_EVENT); + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + expect(replay).toHaveLastSentReplay({ recordingData: JSON.stringify([TEST_EVENT]) }); + }); + + it('uploads a dom breadcrumb 5 seconds after listener receives an event', async () => { + domHandler({ + name: 'click', + }); + + // Pretend 5 seconds have passed + const ELAPSED = 5000; + await advanceTimers(ELAPSED); + + expect(replay).toHaveLastSentReplay({ + recordingData: JSON.stringify([ + { + type: 5, + timestamp: BASE_TIMESTAMP, + data: { + tag: 'breadcrumb', + payload: { + timestamp: BASE_TIMESTAMP / 1000, + type: 'default', + category: 'ui.click', + message: '', + data: {}, + }, + }, + }, + ]), + }); + + expect(replay.session?.segmentId).toBe(1); + }); + + it('fails to upload data on first two calls and succeeds on the third', async () => { + expect(replay.session?.segmentId).toBe(0); + const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 }; + + // Suppress console.errors + const mockConsole = jest.spyOn(console, 'error').mockImplementation(jest.fn()); + + // fail the first and second requests and pass the third one + mockTransportSend.mockImplementationOnce(() => { + throw new Error('Something bad happened'); + }); + mockRecord._emitter(TEST_EVENT); + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + + expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); + mockTransportSend.mockImplementationOnce(() => { + throw new Error('Something bad happened'); + }); + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + + // next tick should retry and succeed + mockConsole.mockRestore(); + + await advanceTimers(8000); + await advanceTimers(2000); + + expect(replay).toHaveLastSentReplay({ + replayEventPayload: expect.objectContaining({ + error_ids: [], + replay_id: expect.any(String), + replay_start_timestamp: BASE_TIMESTAMP / 1000, + // timestamp is set on first try, after 5s flush + timestamp: (BASE_TIMESTAMP + 5000) / 1000, + trace_ids: [], + urls: ['http://localhost/'], + }), + recordingPayloadHeader: { segment_id: 0 }, + recordingData: JSON.stringify([TEST_EVENT]), + }); + + mockTransportSend.mockClear(); + // No activity has occurred, session's last activity should remain the same + expect(replay.session?.lastActivity).toBeGreaterThanOrEqual(BASE_TIMESTAMP); + expect(replay.session?.segmentId).toBe(1); + + // next tick should do nothing + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + expect(replay).not.toHaveLastSentReplay(); + }); + + it('fails to upload data and hits retry max and stops', async () => { + const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 }; + + const spyHandleException = jest.spyOn(SentryCore, 'captureException'); + + // Suppress console.errors + const mockConsole = jest.spyOn(console, 'error').mockImplementation(jest.fn()); + + expect(replay.session?.segmentId).toBe(0); + + // fail all requests + mockSendReplayRequest.mockImplementation(async () => { + throw new Error('Something bad happened'); + }); + mockRecord._emitter(TEST_EVENT); + + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + + expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); + expect(mockSendReplayRequest).toHaveBeenCalledTimes(1); + + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + expect(mockSendReplayRequest).toHaveBeenCalledTimes(2); + + await advanceTimers(10000); + expect(mockSendReplayRequest).toHaveBeenCalledTimes(3); + + await advanceTimers(30000); + expect(mockSendReplayRequest).toHaveBeenCalledTimes(4); + + mockConsole.mockReset(); + + // Make sure it doesn't retry again + jest.runAllTimers(); + expect(mockSendReplayRequest).toHaveBeenCalledTimes(4); + + // Retries = 3 (total tries = 4 including initial attempt) + // + last exception is max retries exceeded + expect(spyHandleException).toHaveBeenCalledTimes(5); + expect(spyHandleException).toHaveBeenLastCalledWith(new Error('Unable to send Replay - max retries exceeded')); + + // No activity has occurred, session's last activity should remain the same + expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); + + // segmentId increases despite error + expect(replay.session?.segmentId).toBe(1); + }); +}); diff --git a/packages/replay/test/integration/session.test.ts b/packages/replay/test/integration/session.test.ts new file mode 100644 index 000000000000..0a8a79dc5467 --- /dev/null +++ b/packages/replay/test/integration/session.test.ts @@ -0,0 +1,486 @@ +import { getCurrentHub } from '@sentry/core'; +import type { Transport } from '@sentry/types'; + +import { + DEFAULT_FLUSH_MIN_DELAY, + MAX_SESSION_LIFE, + REPLAY_SESSION_KEY, + VISIBILITY_CHANGE_TIMEOUT, + WINDOW, +} from '../../src/constants'; +import type { ReplayContainer } from '../../src/replay'; +import { addEvent } from '../../src/util/addEvent'; +import { createPerformanceSpans } from '../../src/util/createPerformanceSpans'; +import { BASE_TIMESTAMP } from '../index'; +import type { RecordMock } from '../mocks/mockRrweb'; +import { resetSdkMock } from '../mocks/resetSdkMock'; +import { clearSession } from '../utils/clearSession'; +import { useFakeTimers } from '../utils/use-fake-timers'; + +useFakeTimers(); + +async function advanceTimers(time: number) { + jest.advanceTimersByTime(time); + await new Promise(process.nextTick); +} + +const prevLocation = WINDOW.location; + +describe('Integration | session', () => { + let replay: ReplayContainer; + let domHandler: (args: any) => any; + let mockRecord: RecordMock; + + beforeEach(async () => { + ({ mockRecord, domHandler, replay } = await resetSdkMock({ + replayOptions: { + stickySession: false, + }, + })); + + const mockTransport = getCurrentHub()?.getClient()?.getTransport()?.send as jest.MockedFunction; + mockTransport?.mockClear(); + }); + + afterEach(async () => { + replay.stop(); + + jest.runAllTimers(); + await new Promise(process.nextTick); + jest.setSystemTime(new Date(BASE_TIMESTAMP)); + + Object.defineProperty(WINDOW, 'location', { + value: prevLocation, + writable: true, + }); + }); + + it('creates a new session and triggers a full dom snapshot when document becomes visible after [VISIBILITY_CHANGE_TIMEOUT]ms', () => { + Object.defineProperty(document, 'visibilityState', { + configurable: true, + get: function () { + return 'visible'; + }, + }); + + const initialSession = replay.session; + + jest.advanceTimersByTime(VISIBILITY_CHANGE_TIMEOUT + 1); + + document.dispatchEvent(new Event('visibilitychange')); + + expect(mockRecord.takeFullSnapshot).toHaveBeenLastCalledWith(true); + + // Should have created a new session + expect(replay).not.toHaveSameSession(initialSession); + }); + + it('does not create a new session if user hides the tab and comes back within [VISIBILITY_CHANGE_TIMEOUT] seconds', () => { + const initialSession = replay.session; + + Object.defineProperty(document, 'visibilityState', { + configurable: true, + get: function () { + return 'hidden'; + }, + }); + document.dispatchEvent(new Event('visibilitychange')); + expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); + expect(replay).toHaveSameSession(initialSession); + + // User comes back before `VISIBILITY_CHANGE_TIMEOUT` elapses + jest.advanceTimersByTime(VISIBILITY_CHANGE_TIMEOUT - 1); + Object.defineProperty(document, 'visibilityState', { + configurable: true, + get: function () { + return 'visible'; + }, + }); + document.dispatchEvent(new Event('visibilitychange')); + + expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); + // Should NOT have created a new session + expect(replay).toHaveSameSession(initialSession); + }); + + it('creates a new session if user has been idle for more than 15 minutes and comes back to move their mouse', async () => { + const initialSession = replay.session; + + expect(initialSession?.id).toBeDefined(); + + // Idle for 15 minutes + const FIFTEEN_MINUTES = 15 * 60000; + jest.advanceTimersByTime(FIFTEEN_MINUTES); + + // TBD: We are currently deciding that this event will get dropped, but + // this could/should change in the future. + const TEST_EVENT = { + data: { name: 'lost event' }, + timestamp: BASE_TIMESTAMP, + type: 3, + }; + mockRecord._emitter(TEST_EVENT); + expect(replay).not.toHaveLastSentReplay(); + + await new Promise(process.nextTick); + + // Instead of recording the above event, a full snapshot will occur. + // + // TODO: We could potentially figure out a way to save the last session, + // and produce a checkout based on a previous checkout + updates, and then + // replay the event on top. Or maybe replay the event on top of a refresh + // snapshot. + expect(mockRecord.takeFullSnapshot).toHaveBeenCalledWith(true); + + // Should be a new session + expect(replay).not.toHaveSameSession(initialSession); + + // Replay does not send immediately because checkout was due to expired session + expect(replay).not.toHaveLastSentReplay(); + + // Now do a click + domHandler({ + name: 'click', + }); + + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + + const newTimestamp = BASE_TIMESTAMP + FIFTEEN_MINUTES; + const breadcrumbTimestamp = newTimestamp + 20; // I don't know where this 20ms comes from + + expect(replay).toHaveLastSentReplay({ + recordingData: JSON.stringify([ + { data: { isCheckout: true }, timestamp: newTimestamp, type: 2 }, + { + type: 5, + timestamp: breadcrumbTimestamp, + data: { + tag: 'breadcrumb', + payload: { + timestamp: breadcrumbTimestamp / 1000, + type: 'default', + category: 'ui.click', + message: '', + data: {}, + }, + }, + }, + ]), + }); + }); + + it('should have a session after setup', () => { + expect(replay.session).toMatchObject({ + lastActivity: BASE_TIMESTAMP, + started: BASE_TIMESTAMP, + }); + expect(replay.session?.id).toBeDefined(); + expect(replay.session?.segmentId).toBeDefined(); + }); + + it('clears session', () => { + clearSession(replay); + expect(WINDOW.sessionStorage.getItem(REPLAY_SESSION_KEY)).toBe(null); + expect(replay.session).toBe(undefined); + }); + + it('creates a new session and triggers a full dom snapshot when document becomes visible after [VISIBILITY_CHANGE_TIMEOUT]ms', () => { + Object.defineProperty(document, 'visibilityState', { + configurable: true, + get: function () { + return 'visible'; + }, + }); + + const initialSession = replay.session; + + jest.advanceTimersByTime(VISIBILITY_CHANGE_TIMEOUT + 1); + + document.dispatchEvent(new Event('visibilitychange')); + + expect(mockRecord.takeFullSnapshot).toHaveBeenLastCalledWith(true); + + // Should have created a new session + expect(replay).not.toHaveSameSession(initialSession); + }); + + it('creates a new session and triggers a full dom snapshot when document becomes focused after [VISIBILITY_CHANGE_TIMEOUT]ms', () => { + Object.defineProperty(document, 'visibilityState', { + configurable: true, + get: function () { + return 'visible'; + }, + }); + + const initialSession = replay.session; + + jest.advanceTimersByTime(VISIBILITY_CHANGE_TIMEOUT + 1); + + WINDOW.dispatchEvent(new Event('focus')); + + expect(mockRecord.takeFullSnapshot).toHaveBeenLastCalledWith(true); + + // Should have created a new session + expect(replay).not.toHaveSameSession(initialSession); + }); + + it('does not create a new session if user hides the tab and comes back within [VISIBILITY_CHANGE_TIMEOUT] seconds', () => { + const initialSession = replay.session; + + Object.defineProperty(document, 'visibilityState', { + configurable: true, + get: function () { + return 'hidden'; + }, + }); + document.dispatchEvent(new Event('visibilitychange')); + expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); + expect(replay).toHaveSameSession(initialSession); + + // User comes back before `VISIBILITY_CHANGE_TIMEOUT` elapses + jest.advanceTimersByTime(VISIBILITY_CHANGE_TIMEOUT - 1); + Object.defineProperty(document, 'visibilityState', { + configurable: true, + get: function () { + return 'visible'; + }, + }); + document.dispatchEvent(new Event('visibilitychange')); + + expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); + // Should NOT have created a new session + expect(replay).toHaveSameSession(initialSession); + }); + + it('creates a new session if user has been idle for 15 minutes and comes back to click their mouse', async () => { + const initialSession = replay.session; + + expect(initialSession?.id).toBeDefined(); + expect(replay.getContext()).toEqual( + expect.objectContaining({ + initialUrl: 'http://localhost/', + initialTimestamp: BASE_TIMESTAMP, + }), + ); + + const url = 'http://dummy/'; + Object.defineProperty(WINDOW, 'location', { + value: new URL(url), + }); + + // Idle for 15 minutes + const FIFTEEN_MINUTES = 15 * 60000; + jest.advanceTimersByTime(FIFTEEN_MINUTES); + + // TBD: We are currently deciding that this event will get dropped, but + // this could/should change in the future. + const TEST_EVENT = { + data: { name: 'lost event' }, + timestamp: BASE_TIMESTAMP, + type: 3, + }; + mockRecord._emitter(TEST_EVENT); + expect(replay).not.toHaveLastSentReplay(); + + await new Promise(process.nextTick); + + // Instead of recording the above event, a full snapshot will occur. + // + // TODO: We could potentially figure out a way to save the last session, + // and produce a checkout based on a previous checkout + updates, and then + // replay the event on top. Or maybe replay the event on top of a refresh + // snapshot. + expect(mockRecord.takeFullSnapshot).toHaveBeenCalledWith(true); + + expect(replay).not.toHaveLastSentReplay(); + + // Should be a new session + expect(replay).not.toHaveSameSession(initialSession); + + // Now do a click + domHandler({ + name: 'click', + }); + + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + + const newTimestamp = BASE_TIMESTAMP + FIFTEEN_MINUTES; + const breadcrumbTimestamp = newTimestamp + 20; // I don't know where this 20ms comes from + + expect(replay).toHaveLastSentReplay({ + recordingPayloadHeader: { segment_id: 0 }, + recordingData: JSON.stringify([ + { data: { isCheckout: true }, timestamp: newTimestamp, type: 2 }, + { + type: 5, + timestamp: breadcrumbTimestamp, + data: { + tag: 'breadcrumb', + payload: { + timestamp: breadcrumbTimestamp / 1000, + type: 'default', + category: 'ui.click', + message: '', + data: {}, + }, + }, + }, + ]), + }); + + // `_context` should be reset when a new session is created + expect(replay.getContext()).toEqual( + expect.objectContaining({ + initialUrl: 'http://dummy/', + initialTimestamp: newTimestamp, + }), + ); + }); + + it('does not record if user has been idle for more than MAX_SESSION_LIFE and only starts a new session after a user action', async () => { + jest.clearAllMocks(); + + const initialSession = replay.session; + + expect(initialSession?.id).toBeDefined(); + expect(replay.getContext()).toEqual( + expect.objectContaining({ + initialUrl: 'http://localhost/', + initialTimestamp: BASE_TIMESTAMP, + }), + ); + + const url = 'http://dummy/'; + Object.defineProperty(WINDOW, 'location', { + value: new URL(url), + }); + + // Idle for MAX_SESSION_LIFE + jest.advanceTimersByTime(MAX_SESSION_LIFE); + + // These events will not get flushed and will eventually be dropped because user is idle and session is expired + const TEST_EVENT = { + data: { name: 'lost event' }, + timestamp: MAX_SESSION_LIFE, + type: 3, + }; + mockRecord._emitter(TEST_EVENT); + // performance events can still be collected while recording is stopped + // TODO: we may want to prevent `addEvent` from adding to buffer when user is inactive + replay.addUpdate(() => { + createPerformanceSpans(replay, [ + { + type: 'navigation.navigate', + name: 'foo', + start: BASE_TIMESTAMP + MAX_SESSION_LIFE, + end: BASE_TIMESTAMP + MAX_SESSION_LIFE + 100, + }, + ]); + return true; + }); + + WINDOW.dispatchEvent(new Event('blur')); + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + + expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); + expect(replay).not.toHaveLastSentReplay(); + // Should be the same session because user has been idle and no events have caused a new session to be created + expect(replay).toHaveSameSession(initialSession); + + // @ts-ignore private + expect(replay._stopRecording).toBeUndefined(); + + // Now do a click + domHandler({ + name: 'click', + }); + // This should still be thrown away + mockRecord._emitter(TEST_EVENT); + + const NEW_TEST_EVENT = { + data: { name: 'test' }, + timestamp: BASE_TIMESTAMP + MAX_SESSION_LIFE + DEFAULT_FLUSH_MIN_DELAY + 20, + type: 3, + }; + + mockRecord._emitter(NEW_TEST_EVENT); + + // new session is created + jest.runAllTimers(); + await new Promise(process.nextTick); + + expect(replay).not.toHaveSameSession(initialSession); + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + + const newTimestamp = BASE_TIMESTAMP + MAX_SESSION_LIFE + DEFAULT_FLUSH_MIN_DELAY + 20; // I don't know where this 20ms comes from + const breadcrumbTimestamp = newTimestamp; + + jest.runAllTimers(); + await new Promise(process.nextTick); + + expect(replay).toHaveLastSentReplay({ + recordingPayloadHeader: { segment_id: 0 }, + recordingData: JSON.stringify([ + { data: { isCheckout: true }, timestamp: newTimestamp, type: 2 }, + { + type: 5, + timestamp: breadcrumbTimestamp, + data: { + tag: 'breadcrumb', + payload: { + timestamp: breadcrumbTimestamp / 1000, + type: 'default', + category: 'ui.click', + message: '', + data: {}, + }, + }, + }, + NEW_TEST_EVENT, + ]), + }); + + // `_context` should be reset when a new session is created + expect(replay.getContext()).toEqual( + expect.objectContaining({ + initialUrl: 'http://dummy/', + initialTimestamp: newTimestamp, + }), + ); + }); + + it('increases segment id after each event', async () => { + clearSession(replay); + replay['_loadSession']({ expiry: 0 }); + + Object.defineProperty(document, 'visibilityState', { + configurable: true, + get: function () { + return 'hidden'; + }, + }); + + // Pretend 5 seconds have passed + const ELAPSED = 5000; + await advanceTimers(ELAPSED); + + const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 2 }; + + addEvent(replay, TEST_EVENT); + WINDOW.dispatchEvent(new Event('blur')); + await new Promise(process.nextTick); + expect(replay).toHaveLastSentReplay({ + recordingPayloadHeader: { segment_id: 0 }, + }); + expect(replay.session?.segmentId).toBe(1); + + addEvent(replay, TEST_EVENT); + WINDOW.dispatchEvent(new Event('blur')); + jest.runAllTimers(); + await new Promise(process.nextTick); + expect(replay.session?.segmentId).toBe(2); + expect(replay).toHaveLastSentReplay({ + recordingPayloadHeader: { segment_id: 1 }, + }); + }); +}); diff --git a/packages/replay/test/unit/stop.test.ts b/packages/replay/test/integration/stop.test.ts similarity index 86% rename from packages/replay/test/unit/stop.test.ts rename to packages/replay/test/integration/stop.test.ts index 1adbde5a11d3..55f8dafd9289 100644 --- a/packages/replay/test/unit/stop.test.ts +++ b/packages/replay/test/integration/stop.test.ts @@ -1,16 +1,17 @@ import * as SentryUtils from '@sentry/utils'; +import type { Replay } from '../../src'; import { SESSION_IDLE_DURATION, WINDOW } from '../../src/constants'; -import { ReplayContainer } from '../../src/replay'; +import type { ReplayContainer } from '../../src/replay'; import { addEvent } from '../../src/util/addEvent'; -import { Replay } from './../../src'; // mock functions need to be imported first -import { BASE_TIMESTAMP, mockRrweb, mockSdk } from './../index'; -import { useFakeTimers } from './../utils/use-fake-timers'; +import { BASE_TIMESTAMP, mockRrweb, mockSdk } from '../index'; +import { clearSession } from '../utils/clearSession'; +import { useFakeTimers } from '../utils/use-fake-timers'; useFakeTimers(); -describe('Replay - stop', () => { +describe('Integration | stop', () => { let replay: ReplayContainer; let integration: Replay; const prevLocation = WINDOW.location; @@ -42,8 +43,8 @@ describe('Replay - stop', () => { await new Promise(process.nextTick); jest.setSystemTime(new Date(BASE_TIMESTAMP)); sessionStorage.clear(); - replay.clearSession(); - replay.loadSession({ expiry: SESSION_IDLE_DURATION }); + clearSession(replay); + replay['_loadSession']({ expiry: SESSION_IDLE_DURATION }); mockRecord.takeFullSnapshot.mockClear(); mockAddInstrumentationHandler.mockClear(); Object.defineProperty(WINDOW, 'location', { @@ -109,7 +110,7 @@ describe('Replay - stop', () => { jest.runAllTimers(); await new Promise(process.nextTick); expect(replay).toHaveLastSentReplay({ - events: JSON.stringify([ + recordingData: JSON.stringify([ // This event happens when we call `replay.start` { data: { isCheckout: true }, @@ -127,17 +128,17 @@ describe('Replay - stop', () => { it('does not buffer events when stopped', async function () { WINDOW.dispatchEvent(new Event('blur')); - expect(replay.eventBuffer?.length).toBe(1); + expect(replay.eventBuffer?.pendingLength).toBe(1); // stop replays integration.stop(); - expect(replay.eventBuffer?.length).toBe(undefined); + expect(replay.eventBuffer?.pendingLength).toBe(undefined); WINDOW.dispatchEvent(new Event('blur')); await new Promise(process.nextTick); - expect(replay.eventBuffer?.length).toBe(undefined); + expect(replay.eventBuffer?.pendingLength).toBe(undefined); expect(replay).not.toHaveLastSentReplay(); }); diff --git a/packages/replay/test/mocks/mockSdk.ts b/packages/replay/test/mocks/mockSdk.ts index 159627e656a2..9da56061071f 100644 --- a/packages/replay/test/mocks/mockSdk.ts +++ b/packages/replay/test/mocks/mockSdk.ts @@ -1,13 +1,14 @@ -import { BrowserOptions, init } from '@sentry/browser'; -import { Envelope, Transport } from '@sentry/types'; +import type { Envelope, Transport } from '@sentry/types'; -import { Replay as ReplayIntegration } from '../../src'; -import { ReplayContainer } from '../../src/replay'; +import type { Replay as ReplayIntegration } from '../../src'; +import type { ReplayContainer } from '../../src/replay'; import type { ReplayConfiguration } from '../../src/types'; +import type { TestClientOptions } from '../utils/TestClient'; +import { getDefaultClientOptions, init } from '../utils/TestClient'; export interface MockSdkParams { replayOptions?: ReplayConfiguration; - sentryOptions?: BrowserOptions; + sentryOptions?: Partial; autoStart?: boolean; } @@ -63,13 +64,13 @@ export async function mockSdk({ replayOptions, sentryOptions, autoStart = true } }); init({ + ...getDefaultClientOptions(), dsn: 'https://dsn@ingest.f00.f00/1', autoSessionTracking: false, sendClientReports: false, transport: () => new MockTransport(), replaysSessionSampleRate: 1.0, replaysOnErrorSampleRate: 0.0, - defaultIntegrations: false, ...sentryOptions, integrations: [replayIntegration], }); diff --git a/packages/replay/test/mocks/resetSdkMock.ts b/packages/replay/test/mocks/resetSdkMock.ts index f2104975045c..24d90678f400 100644 --- a/packages/replay/test/mocks/resetSdkMock.ts +++ b/packages/replay/test/mocks/resetSdkMock.ts @@ -1,7 +1,9 @@ import type { ReplayContainer } from '../../src/replay'; -import { BASE_TIMESTAMP, RecordMock } from './../index'; +import type { RecordMock } from './../index'; +import { BASE_TIMESTAMP } from './../index'; import type { DomHandler } from './../types'; -import { mockSdk, MockSdkParams } from './mockSdk'; +import type { MockSdkParams } from './mockSdk'; +import { mockSdk } from './mockSdk'; export async function resetSdkMock({ replayOptions, sentryOptions }: MockSdkParams): Promise<{ domHandler: DomHandler; diff --git a/packages/replay/test/unit/coreHandlers/handleFetch.test.ts b/packages/replay/test/unit/coreHandlers/handleFetch.test.ts index 0d3386aee15a..e936b693af56 100644 --- a/packages/replay/test/unit/coreHandlers/handleFetch.test.ts +++ b/packages/replay/test/unit/coreHandlers/handleFetch.test.ts @@ -1,9 +1,4 @@ import { handleFetch } from '../../../src/coreHandlers/handleFetch'; -import { mockSdk } from './../../index'; - -beforeAll(function () { - mockSdk(); -}); const DEFAULT_DATA = { args: ['/api/0/organizations/sentry/', { method: 'GET', headers: {}, credentials: 'include' }] as Parameters< @@ -24,28 +19,30 @@ const DEFAULT_DATA = { startTimestamp: 10000, }; -it('formats fetch calls from core SDK to replay breadcrumbs', function () { - expect(handleFetch(DEFAULT_DATA)).toEqual({ - type: 'resource.fetch', - name: '/api/0/organizations/sentry/', - start: 10, - end: 15, - data: { - method: 'GET', - statusCode: 200, - }, +describe('Unit | coreHandlers | handleFetch', () => { + it('formats fetch calls from core SDK to replay breadcrumbs', function () { + expect(handleFetch(DEFAULT_DATA)).toEqual({ + type: 'resource.fetch', + name: '/api/0/organizations/sentry/', + start: 10, + end: 15, + data: { + method: 'GET', + statusCode: 200, + }, + }); }); -}); -it('ignores fetches that have not completed yet', function () { - const data = { - ...DEFAULT_DATA, - }; + it('ignores fetches that have not completed yet', function () { + const data = { + ...DEFAULT_DATA, + }; - // @ts-ignore: The operand of a 'delete' operator must be optional.ts(2790) - delete data.endTimestamp; - // @ts-ignore: The operand of a 'delete' operator must be optional.ts(2790) - delete data.response; + // @ts-ignore: The operand of a 'delete' operator must be optional.ts(2790) + delete data.endTimestamp; + // @ts-ignore: The operand of a 'delete' operator must be optional.ts(2790) + delete data.response; - expect(handleFetch(data)).toEqual(null); + expect(handleFetch(data)).toEqual(null); + }); }); diff --git a/packages/replay/test/unit/coreHandlers/handleScope-unit.test.ts b/packages/replay/test/unit/coreHandlers/handleScope-unit.test.ts deleted file mode 100644 index cec834472029..000000000000 --- a/packages/replay/test/unit/coreHandlers/handleScope-unit.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { getCurrentHub } from '@sentry/core'; - -import * as HandleScope from '../../../src/coreHandlers/handleScope'; -import { mockSdk } from './../../index'; - -let mockHandleScope: jest.MockedFunction; - -jest.useFakeTimers(); - -beforeAll(async function () { - await mockSdk(); - jest.spyOn(HandleScope, 'handleScope'); - mockHandleScope = HandleScope.handleScope as jest.MockedFunction; - - jest.runAllTimers(); -}); - -it('returns a breadcrumb only if last breadcrumb has changed (integration)', function () { - getCurrentHub().getScope()?.addBreadcrumb({ message: 'testing' }); - - expect(mockHandleScope).toHaveBeenCalledTimes(1); - expect(mockHandleScope).toHaveReturnedWith(expect.objectContaining({ message: 'testing' })); - - mockHandleScope.mockClear(); - - // This will trigger breadcrumb/scope listener, but handleScope should return - // null because breadcrumbs has not changed - getCurrentHub().getScope()?.setUser({ email: 'foo@foo.com' }); - expect(mockHandleScope).toHaveBeenCalledTimes(1); - expect(mockHandleScope).toHaveReturnedWith(null); -}); diff --git a/packages/replay/test/unit/coreHandlers/handleScope.test.ts b/packages/replay/test/unit/coreHandlers/handleScope.test.ts index dd650a685293..db562a7b98aa 100644 --- a/packages/replay/test/unit/coreHandlers/handleScope.test.ts +++ b/packages/replay/test/unit/coreHandlers/handleScope.test.ts @@ -2,48 +2,49 @@ import type { Breadcrumb, Scope } from '@sentry/types'; import * as HandleScope from '../../../src/coreHandlers/handleScope'; -jest.spyOn(HandleScope, 'handleScope'); -const mockHandleScope = HandleScope.handleScope as jest.MockedFunction; - -it('returns a breadcrumb only if last breadcrumb has changed (unit)', function () { - const scope = { - _breadcrumbs: [], - getLastBreadcrumb() { - return this._breadcrumbs[this._breadcrumbs.length - 1]; - }, - } as unknown as Scope; - - function addBreadcrumb(breadcrumb: Breadcrumb) { - // @ts-ignore using private member - scope._breadcrumbs.push(breadcrumb); - } - - const testMsg = { - timestamp: new Date().getTime() / 1000, - message: 'testing', - category: 'console', - }; - - addBreadcrumb(testMsg); - // integration testing here is a bit tricky, because the core SDK can - // interfere with console output from test runner - HandleScope.handleScope(scope); - expect(mockHandleScope).toHaveBeenCalledTimes(1); - expect(mockHandleScope).toHaveReturnedWith(expect.objectContaining({ message: 'testing', category: 'console' })); - - // This will trigger breadcrumb/scope listener, but handleScope should return - // null because breadcrumbs has not changed - mockHandleScope.mockClear(); - HandleScope.handleScope(scope); - expect(mockHandleScope).toHaveBeenCalledTimes(1); - expect(mockHandleScope).toHaveReturnedWith(null); - - mockHandleScope.mockClear(); - addBreadcrumb({ - message: 'f00', - category: 'console', +describe('Unit | coreHandlers | handleScope', () => { + const mockHandleScope = jest.spyOn(HandleScope, 'handleScope'); + + it('returns a breadcrumb only if last breadcrumb has changed (unit)', function () { + const scope = { + _breadcrumbs: [], + getLastBreadcrumb() { + return this._breadcrumbs[this._breadcrumbs.length - 1]; + }, + } as unknown as Scope; + + function addBreadcrumb(breadcrumb: Breadcrumb) { + // @ts-ignore using private member + scope._breadcrumbs.push(breadcrumb); + } + + const testMsg = { + timestamp: new Date().getTime() / 1000, + message: 'testing', + category: 'console', + }; + + addBreadcrumb(testMsg); + // integration testing here is a bit tricky, because the core SDK can + // interfere with console output from test runner + HandleScope.handleScope(scope); + expect(mockHandleScope).toHaveBeenCalledTimes(1); + expect(mockHandleScope).toHaveReturnedWith(expect.objectContaining({ message: 'testing', category: 'console' })); + + // This will trigger breadcrumb/scope listener, but handleScope should return + // null because breadcrumbs has not changed + mockHandleScope.mockClear(); + HandleScope.handleScope(scope); + expect(mockHandleScope).toHaveBeenCalledTimes(1); + expect(mockHandleScope).toHaveReturnedWith(null); + + mockHandleScope.mockClear(); + addBreadcrumb({ + message: 'f00', + category: 'console', + }); + HandleScope.handleScope(scope); + expect(mockHandleScope).toHaveBeenCalledTimes(1); + expect(mockHandleScope).toHaveReturnedWith(expect.objectContaining({ message: 'f00', category: 'console' })); }); - HandleScope.handleScope(scope); - expect(mockHandleScope).toHaveBeenCalledTimes(1); - expect(mockHandleScope).toHaveReturnedWith(expect.objectContaining({ message: 'f00', category: 'console' })); }); diff --git a/packages/replay/test/unit/createPerformanceEntry.test.ts b/packages/replay/test/unit/createPerformanceEntry.test.ts deleted file mode 100644 index ecd0d746806e..000000000000 --- a/packages/replay/test/unit/createPerformanceEntry.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { createPerformanceEntries } from '../../src/createPerformanceEntry'; -import { mockSdk } from './../index'; - -beforeAll(function () { - mockSdk(); -}); - -it('ignores sdks own requests', function () { - const data = { - name: 'https://ingest.f00.f00/api/1/envelope/?sentry_key=dsn&sentry_version=7', - entryType: 'resource', - startTime: 234462.69999998808, - duration: 55.70000001788139, - initiatorType: 'fetch', - nextHopProtocol: '', - workerStart: 0, - redirectStart: 0, - redirectEnd: 0, - fetchStart: 234462.69999998808, - domainLookupStart: 0, - domainLookupEnd: 0, - connectStart: 0, - connectEnd: 0, - secureConnectionStart: 0, - requestStart: 0, - responseStart: 0, - responseEnd: 234518.40000000596, - transferSize: 0, - encodedBodySize: 0, - decodedBodySize: 0, - serverTiming: [], - workerTiming: [], - } as const; - - // @ts-ignore Needs a PerformanceEntry mock - expect(createPerformanceEntries([data])).toEqual([]); -}); diff --git a/packages/replay/test/unit/eventBuffer.test.ts b/packages/replay/test/unit/eventBuffer.test.ts index a34230138fea..395af37b7f05 100644 --- a/packages/replay/test/unit/eventBuffer.test.ts +++ b/packages/replay/test/unit/eventBuffer.test.ts @@ -2,118 +2,122 @@ import 'jsdom-worker'; import pako from 'pako'; -import { createEventBuffer, EventBufferCompressionWorker } from './../../src/eventBuffer'; +import type { EventBufferCompressionWorker } from './../../src/eventBuffer'; +import { createEventBuffer } from './../../src/eventBuffer'; import { BASE_TIMESTAMP } from './../index'; const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 }; +describe('Unit | eventBuffer', () => { + it('adds events to normal event buffer', async function () { + const buffer = createEventBuffer({ useCompression: false }); -it('adds events to normal event buffer', async function () { - const buffer = createEventBuffer({ useCompression: false }); + buffer.addEvent(TEST_EVENT); + buffer.addEvent(TEST_EVENT); - buffer.addEvent(TEST_EVENT); - buffer.addEvent(TEST_EVENT); + const result = await buffer.finish(); - const result = await buffer.finish(); + expect(result).toEqual(JSON.stringify([TEST_EVENT, TEST_EVENT])); + }); - expect(result).toEqual(JSON.stringify([TEST_EVENT, TEST_EVENT])); -}); + it('adds checkout event to normal event buffer', async function () { + const buffer = createEventBuffer({ useCompression: false }); -it('adds checkout event to normal event buffer', async function () { - const buffer = createEventBuffer({ useCompression: false }); + buffer.addEvent(TEST_EVENT); + buffer.addEvent(TEST_EVENT); + buffer.addEvent(TEST_EVENT); - buffer.addEvent(TEST_EVENT); - buffer.addEvent(TEST_EVENT); - buffer.addEvent(TEST_EVENT); + buffer.addEvent(TEST_EVENT, true); + const result = await buffer.finish(); - buffer.addEvent(TEST_EVENT, true); - const result = await buffer.finish(); + expect(result).toEqual(JSON.stringify([TEST_EVENT])); + }); - expect(result).toEqual(JSON.stringify([TEST_EVENT])); -}); + it('calling `finish()` multiple times does not result in duplicated events', async function () { + const buffer = createEventBuffer({ useCompression: false }); -it('calling `finish()` multiple times does not result in duplicated events', async function () { - const buffer = createEventBuffer({ useCompression: false }); + buffer.addEvent(TEST_EVENT); - buffer.addEvent(TEST_EVENT); + const promise1 = buffer.finish(); + const promise2 = buffer.finish(); - const promise1 = buffer.finish(); - const promise2 = buffer.finish(); + const result1 = (await promise1) as Uint8Array; + const result2 = (await promise2) as Uint8Array; - const result1 = (await promise1) as Uint8Array; - const result2 = (await promise2) as Uint8Array; + expect(result1).toEqual(JSON.stringify([TEST_EVENT])); + expect(result2).toEqual(JSON.stringify([])); + }); - expect(result1).toEqual(JSON.stringify([TEST_EVENT])); - expect(result2).toEqual(JSON.stringify([])); -}); + it('adds events to event buffer with compression worker', async function () { + const buffer = createEventBuffer({ + useCompression: true, + }) as EventBufferCompressionWorker; -it('adds events to event buffer with compression worker', async function () { - const buffer = createEventBuffer({ - useCompression: true, - }) as EventBufferCompressionWorker; + buffer.addEvent(TEST_EVENT); + buffer.addEvent(TEST_EVENT); - buffer.addEvent(TEST_EVENT); - buffer.addEvent(TEST_EVENT); + expect(buffer.pendingEvents).toEqual([TEST_EVENT, TEST_EVENT]); - const result = await buffer.finish(); - const restored = pako.inflate(result, { to: 'string' }); + const result = await buffer.finish(); + const restored = pako.inflate(result, { to: 'string' }); - expect(restored).toEqual(JSON.stringify([TEST_EVENT, TEST_EVENT])); -}); + expect(restored).toEqual(JSON.stringify([TEST_EVENT, TEST_EVENT])); + }); -it('adds checkout events to event buffer with compression worker', async function () { - const buffer = createEventBuffer({ - useCompression: true, - }) as EventBufferCompressionWorker; + it('adds checkout events to event buffer with compression worker', async function () { + const buffer = createEventBuffer({ + useCompression: true, + }) as EventBufferCompressionWorker; - await buffer.addEvent(TEST_EVENT); - await buffer.addEvent(TEST_EVENT); + await buffer.addEvent(TEST_EVENT); + await buffer.addEvent(TEST_EVENT); - // This should clear previous buffer - await buffer.addEvent({ ...TEST_EVENT, type: 2 }, true); + // This should clear previous buffer + await buffer.addEvent({ ...TEST_EVENT, type: 2 }, true); - const result = await buffer.finish(); - const restored = pako.inflate(result, { to: 'string' }); + const result = await buffer.finish(); + const restored = pako.inflate(result, { to: 'string' }); - expect(restored).toEqual(JSON.stringify([{ ...TEST_EVENT, type: 2 }])); -}); + expect(restored).toEqual(JSON.stringify([{ ...TEST_EVENT, type: 2 }])); + }); -it('calling `finish()` multiple times does not result in duplicated events', async function () { - const buffer = createEventBuffer({ - useCompression: true, - }) as EventBufferCompressionWorker; + it('calling `finish()` multiple times does not result in duplicated events', async function () { + const buffer = createEventBuffer({ + useCompression: true, + }) as EventBufferCompressionWorker; - buffer.addEvent(TEST_EVENT); + buffer.addEvent(TEST_EVENT); - const promise1 = buffer.finish(); - const promise2 = buffer.finish(); + const promise1 = buffer.finish(); + const promise2 = buffer.finish(); - const result1 = (await promise1) as Uint8Array; - const result2 = (await promise2) as Uint8Array; - const restored1 = pako.inflate(result1, { to: 'string' }); - const restored2 = pako.inflate(result2, { to: 'string' }); + const result1 = (await promise1) as Uint8Array; + const result2 = (await promise2) as Uint8Array; + const restored1 = pako.inflate(result1, { to: 'string' }); + const restored2 = pako.inflate(result2, { to: 'string' }); - expect(restored1).toEqual(JSON.stringify([TEST_EVENT])); - expect(restored2).toEqual(JSON.stringify([])); -}); + expect(restored1).toEqual(JSON.stringify([TEST_EVENT])); + expect(restored2).toEqual(JSON.stringify([])); + }); -it('calling `finish()` multiple times, with events in between, does not result in duplicated or dropped events', async function () { - const buffer = createEventBuffer({ - useCompression: true, - }) as EventBufferCompressionWorker; + it('calling `finish()` multiple times, with events in between, does not result in duplicated or dropped events', async function () { + const buffer = createEventBuffer({ + useCompression: true, + }) as EventBufferCompressionWorker; - buffer.addEvent(TEST_EVENT); + buffer.addEvent(TEST_EVENT); - const promise1 = buffer.finish(); + const promise1 = buffer.finish(); - buffer.addEvent({ ...TEST_EVENT, type: 5 }); - const promise2 = buffer.finish(); + buffer.addEvent({ ...TEST_EVENT, type: 5 }); + const promise2 = buffer.finish(); - const result1 = (await promise1) as Uint8Array; - const result2 = (await promise2) as Uint8Array; + const result1 = (await promise1) as Uint8Array; + const result2 = (await promise2) as Uint8Array; - const restored1 = pako.inflate(result1, { to: 'string' }); - const restored2 = pako.inflate(result2, { to: 'string' }); + const restored1 = pako.inflate(result1, { to: 'string' }); + const restored2 = pako.inflate(result2, { to: 'string' }); - expect(restored1).toEqual(JSON.stringify([TEST_EVENT])); - expect(restored2).toEqual(JSON.stringify([{ ...TEST_EVENT, type: 5 }])); + expect(restored1).toEqual(JSON.stringify([TEST_EVENT])); + expect(restored2).toEqual(JSON.stringify([{ ...TEST_EVENT, type: 5 }])); + }); }); diff --git a/packages/replay/test/unit/flush.test.ts b/packages/replay/test/unit/flush.test.ts deleted file mode 100644 index 3c481c486c73..000000000000 --- a/packages/replay/test/unit/flush.test.ts +++ /dev/null @@ -1,230 +0,0 @@ -import * as SentryUtils from '@sentry/utils'; - -import { DEFAULT_FLUSH_MIN_DELAY, SESSION_IDLE_DURATION, WINDOW } from '../../src/constants'; -import * as AddMemoryEntry from '../../src/util/addMemoryEntry'; -import { createPerformanceSpans } from '../../src/util/createPerformanceSpans'; -import { createPerformanceEntries } from './../../src/createPerformanceEntry'; -import { ReplayContainer } from './../../src/replay'; -import { useFakeTimers } from './../../test/utils/use-fake-timers'; -import { BASE_TIMESTAMP, mockRrweb, mockSdk } from './../index'; - -useFakeTimers(); - -async function advanceTimers(time: number) { - jest.advanceTimersByTime(time); - await new Promise(process.nextTick); -} - -type MockSendReplay = jest.MockedFunction; -type MockAddPerformanceEntries = jest.MockedFunction; -type MockAddMemoryEntry = jest.SpyInstance; -type MockEventBufferFinish = jest.MockedFunction['finish']>; -type MockFlush = jest.MockedFunction; -type MockRunFlush = jest.MockedFunction; - -const prevLocation = WINDOW.location; -let domHandler: (args: any) => any; - -const { record: mockRecord } = mockRrweb(); - -let replay: ReplayContainer; -let mockSendReplay: MockSendReplay; -let mockFlush: MockFlush; -let mockRunFlush: MockRunFlush; -let mockEventBufferFinish: MockEventBufferFinish; -let mockAddMemoryEntry: MockAddMemoryEntry; -let mockAddPerformanceEntries: MockAddPerformanceEntries; - -beforeAll(async () => { - jest.spyOn(SentryUtils, 'addInstrumentationHandler').mockImplementation((type, handler: (args: any) => any) => { - if (type === 'dom') { - domHandler = handler; - } - }); - - ({ replay } = await mockSdk()); - jest.spyOn(replay, 'sendReplay'); - mockSendReplay = replay.sendReplay as MockSendReplay; - mockSendReplay.mockImplementation( - jest.fn(async () => { - return; - }), - ); - - jest.spyOn(replay, 'flush'); - mockFlush = replay.flush as MockFlush; - - jest.spyOn(replay, 'runFlush'); - mockRunFlush = replay.runFlush as MockRunFlush; - - jest.spyOn(replay, 'addPerformanceEntries'); - mockAddPerformanceEntries = replay.addPerformanceEntries as MockAddPerformanceEntries; - - mockAddPerformanceEntries.mockImplementation(async () => { - return []; - }); - - mockAddMemoryEntry = jest.spyOn(AddMemoryEntry, 'addMemoryEntry'); -}); - -beforeEach(() => { - jest.runAllTimers(); - jest.setSystemTime(new Date(BASE_TIMESTAMP)); - mockSendReplay.mockClear(); - replay.eventBuffer?.destroy(); - mockAddPerformanceEntries.mockClear(); - mockFlush.mockClear(); - mockRunFlush.mockClear(); - mockAddMemoryEntry.mockClear(); - - if (replay.eventBuffer) { - jest.spyOn(replay.eventBuffer, 'finish'); - } - mockEventBufferFinish = replay.eventBuffer?.finish as MockEventBufferFinish; - mockEventBufferFinish.mockClear(); -}); - -afterEach(async () => { - jest.runAllTimers(); - await new Promise(process.nextTick); - jest.setSystemTime(new Date(BASE_TIMESTAMP)); - sessionStorage.clear(); - replay.clearSession(); - replay.loadSession({ expiry: SESSION_IDLE_DURATION }); - mockRecord.takeFullSnapshot.mockClear(); - Object.defineProperty(WINDOW, 'location', { - value: prevLocation, - writable: true, - }); -}); - -afterAll(() => { - replay && replay.stop(); -}); - -it('flushes twice after multiple flush() calls)', async () => { - // blur events cause an immediate flush (as well as a flush due to adding a - // breadcrumb) -- this means that the first blur event will be flushed and - // the following blur events will all call a debounced flush function, which - // should end up queueing a second flush - - WINDOW.dispatchEvent(new Event('blur')); - WINDOW.dispatchEvent(new Event('blur')); - WINDOW.dispatchEvent(new Event('blur')); - WINDOW.dispatchEvent(new Event('blur')); - - expect(replay.flush).toHaveBeenCalledTimes(4); - - jest.runAllTimers(); - await new Promise(process.nextTick); - expect(replay.runFlush).toHaveBeenCalledTimes(1); - - jest.runAllTimers(); - await new Promise(process.nextTick); - expect(replay.runFlush).toHaveBeenCalledTimes(2); - - jest.runAllTimers(); - await new Promise(process.nextTick); - expect(replay.runFlush).toHaveBeenCalledTimes(2); -}); - -it('long first flush enqueues following events', async () => { - // Mock this to resolve after 20 seconds so that we can queue up following flushes - mockAddPerformanceEntries.mockImplementationOnce(async () => { - return await new Promise(resolve => setTimeout(resolve, 20000)); - }); - - expect(mockAddPerformanceEntries).not.toHaveBeenCalled(); - - // flush #1 @ t=0s - due to blur - WINDOW.dispatchEvent(new Event('blur')); - expect(replay.flush).toHaveBeenCalledTimes(1); - expect(replay.runFlush).toHaveBeenCalledTimes(1); - - // This will attempt to flush in 5 seconds (flushMinDelay) - domHandler({ - name: 'click', - }); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); - // flush #2 @ t=5s - due to click - expect(replay.flush).toHaveBeenCalledTimes(2); - - await advanceTimers(1000); - // flush #3 @ t=6s - due to blur - WINDOW.dispatchEvent(new Event('blur')); - expect(replay.flush).toHaveBeenCalledTimes(3); - - // NOTE: Blur also adds a breadcrumb which calls `addUpdate`, meaning it will - // flush after `flushMinDelay`, but this gets cancelled by the blur - await advanceTimers(8000); - expect(replay.flush).toHaveBeenCalledTimes(3); - - // flush #4 @ t=14s - due to blur - WINDOW.dispatchEvent(new Event('blur')); - expect(replay.flush).toHaveBeenCalledTimes(4); - - expect(replay.runFlush).toHaveBeenCalledTimes(1); - await advanceTimers(6000); - // t=20s - // addPerformanceEntries is finished, `flushLock` promise is resolved, calls - // debouncedFlush, which will call `flush` in 1 second - expect(replay.flush).toHaveBeenCalledTimes(4); - // sendReplay is called with replayId, events, segment - expect(mockSendReplay).toHaveBeenLastCalledWith({ - events: expect.any(String), - replayId: expect.any(String), - includeReplayStartTimestamp: true, - segmentId: 0, - eventContext: expect.anything(), - }); - - // Add this to test that segment ID increases - mockAddPerformanceEntries.mockImplementationOnce(() => { - createPerformanceSpans( - replay, - createPerformanceEntries([ - { - name: 'https://sentry.io/foo.js', - entryType: 'resource', - startTime: 176.59999990463257, - duration: 5.600000023841858, - initiatorType: 'link', - nextHopProtocol: 'h2', - workerStart: 177.5, - redirectStart: 0, - redirectEnd: 0, - fetchStart: 177.69999992847443, - domainLookupStart: 177.69999992847443, - domainLookupEnd: 177.69999992847443, - connectStart: 177.69999992847443, - connectEnd: 177.69999992847443, - secureConnectionStart: 177.69999992847443, - requestStart: 177.5, - responseStart: 181, - responseEnd: 182.19999992847443, - transferSize: 0, - encodedBodySize: 0, - decodedBodySize: 0, - serverTiming: [], - } as unknown as PerformanceResourceTiming, - ]), - ); - }); - // flush #5 @ t=25s - debounced flush calls `flush` - // 20s + `flushMinDelay` which is 5 seconds - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); - expect(replay.flush).toHaveBeenCalledTimes(5); - expect(replay.runFlush).toHaveBeenCalledTimes(2); - expect(mockSendReplay).toHaveBeenLastCalledWith({ - events: expect.any(String), - replayId: expect.any(String), - includeReplayStartTimestamp: false, - segmentId: 1, - eventContext: expect.anything(), - }); - - // Make sure there's no other calls - jest.runAllTimers(); - await new Promise(process.nextTick); - expect(mockSendReplay).toHaveBeenCalledTimes(2); -}); diff --git a/packages/replay/test/unit/index-noSticky.test.ts b/packages/replay/test/unit/index-noSticky.test.ts deleted file mode 100644 index f854258a1ec7..000000000000 --- a/packages/replay/test/unit/index-noSticky.test.ts +++ /dev/null @@ -1,280 +0,0 @@ -import { getCurrentHub } from '@sentry/core'; -import { Transport } from '@sentry/types'; -import * as SentryUtils from '@sentry/utils'; - -import { DEFAULT_FLUSH_MIN_DELAY, SESSION_IDLE_DURATION, VISIBILITY_CHANGE_TIMEOUT } from '../../src/constants'; -import { addEvent } from '../../src/util/addEvent'; -import { ReplayContainer } from './../../src/replay'; -import { BASE_TIMESTAMP, mockRrweb, mockSdk } from './../index'; -import { useFakeTimers } from './../utils/use-fake-timers'; - -useFakeTimers(); - -async function advanceTimers(time: number) { - jest.advanceTimersByTime(time); - await new Promise(process.nextTick); -} - -type MockTransport = jest.MockedFunction; - -describe('Replay (no sticky)', () => { - let replay: ReplayContainer; - let mockTransport: MockTransport; - let domHandler: (args: any) => any; - const { record: mockRecord } = mockRrweb(); - - beforeAll(async () => { - jest.setSystemTime(new Date(BASE_TIMESTAMP)); - jest.spyOn(SentryUtils, 'addInstrumentationHandler').mockImplementation((type, handler: (args: any) => any) => { - if (type === 'dom') { - domHandler = handler; - } - }); - - ({ replay } = await mockSdk({ - replayOptions: { - stickySession: false, - }, - })); - jest.runAllTimers(); - mockTransport = getCurrentHub()?.getClient()?.getTransport()?.send as MockTransport; - }); - - beforeEach(() => { - jest.setSystemTime(new Date(BASE_TIMESTAMP)); - mockRecord.takeFullSnapshot.mockClear(); - mockTransport.mockClear(); - }); - - afterEach(async () => { - jest.runAllTimers(); - await new Promise(process.nextTick); - jest.setSystemTime(new Date(BASE_TIMESTAMP)); - replay.clearSession(); - replay.loadSession({ expiry: SESSION_IDLE_DURATION }); - }); - - afterAll(() => { - replay && replay.stop(); - }); - - it('creates a new session and triggers a full dom snapshot when document becomes visible after [VISIBILITY_CHANGE_TIMEOUT]ms', () => { - Object.defineProperty(document, 'visibilityState', { - configurable: true, - get: function () { - return 'visible'; - }, - }); - - const initialSession = replay.session; - - jest.advanceTimersByTime(VISIBILITY_CHANGE_TIMEOUT + 1); - - document.dispatchEvent(new Event('visibilitychange')); - - expect(mockRecord.takeFullSnapshot).toHaveBeenLastCalledWith(true); - - // Should have created a new session - expect(replay).not.toHaveSameSession(initialSession); - }); - - it('does not create a new session if user hides the tab and comes back within [VISIBILITY_CHANGE_TIMEOUT] seconds', () => { - const initialSession = replay.session; - - Object.defineProperty(document, 'visibilityState', { - configurable: true, - get: function () { - return 'hidden'; - }, - }); - document.dispatchEvent(new Event('visibilitychange')); - expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); - expect(replay).toHaveSameSession(initialSession); - - // User comes back before `VISIBILITY_CHANGE_TIMEOUT` elapses - jest.advanceTimersByTime(VISIBILITY_CHANGE_TIMEOUT - 1); - Object.defineProperty(document, 'visibilityState', { - configurable: true, - get: function () { - return 'visible'; - }, - }); - document.dispatchEvent(new Event('visibilitychange')); - - expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); - // Should NOT have created a new session - expect(replay).toHaveSameSession(initialSession); - }); - - it('uploads a replay event when document becomes hidden', async () => { - mockRecord.takeFullSnapshot.mockClear(); - Object.defineProperty(document, 'visibilityState', { - configurable: true, - get: function () { - return 'hidden'; - }, - }); - - // Pretend 5 seconds have passed - const ELAPSED = 5000; - jest.advanceTimersByTime(ELAPSED); - - const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 2 }; - addEvent(replay, TEST_EVENT); - - document.dispatchEvent(new Event('visibilitychange')); - - await new Promise(process.nextTick); - - expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); - - expect(replay).toHaveLastSentReplay({ events: JSON.stringify([TEST_EVENT]) }); - - // Session's last activity is not updated because we do not consider - // visibilitystate as user being active - expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); - expect(replay.session?.segmentId).toBe(1); - - // events array should be empty - expect(replay.eventBuffer?.length).toBe(0); - }); - - it('update last activity when user clicks mouse', async () => { - expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); - - domHandler({ - name: 'click', - }); - - expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); - - // Pretend 5 seconds have passed - const ELAPSED = 5000; - jest.advanceTimersByTime(ELAPSED); - - domHandler({ - name: 'click', - }); - - expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP + ELAPSED); - }); - - it('uploads a replay event if 5 seconds have elapsed since the last replay event occurred', async () => { - const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 }; - mockRecord._emitter(TEST_EVENT); - // Pretend 5 seconds have passed - const ELAPSED = 5000; - await advanceTimers(ELAPSED); - - expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); - - expect(replay).toHaveLastSentReplay({ events: JSON.stringify([TEST_EVENT]) }); - - // No user activity to trigger an update - expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); - expect(replay.session?.segmentId).toBe(1); - - // events array should be empty - expect(replay.eventBuffer?.length).toBe(0); - }); - - it('uploads a replay event if 15 seconds have elapsed since the last replay upload', async () => { - const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 }; - // Fire a new event every 4 seconds, 4 times - [...Array(4)].forEach(() => { - mockRecord._emitter(TEST_EVENT); - jest.advanceTimersByTime(4000); - }); - - // We are at time = +16seconds now (relative to BASE_TIMESTAMP) - // The next event should cause an upload immediately - mockRecord._emitter(TEST_EVENT); - await new Promise(process.nextTick); - - expect(replay).toHaveLastSentReplay({ - events: JSON.stringify([...Array(5)].map(() => TEST_EVENT)), - }); - - // There should also not be another attempt at an upload 5 seconds after the last replay event - mockTransport.mockClear(); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); - expect(replay).not.toHaveLastSentReplay(); - - expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); - expect(replay.session?.segmentId).toBe(1); - // events array should be empty - expect(replay.eventBuffer?.length).toBe(0); - - // Let's make sure it continues to work - mockTransport.mockClear(); - mockRecord._emitter(TEST_EVENT); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); - expect(replay).toHaveLastSentReplay({ events: JSON.stringify([TEST_EVENT]) }); - }); - - it('creates a new session if user has been idle for more than 15 minutes and comes back to move their mouse', async () => { - const initialSession = replay.session; - - expect(initialSession?.id).toBeDefined(); - - // Idle for 15 minutes - const FIFTEEN_MINUTES = 15 * 60000; - jest.advanceTimersByTime(FIFTEEN_MINUTES); - - // TBD: We are currently deciding that this event will get dropped, but - // this could/should change in the future. - const TEST_EVENT = { - data: { name: 'lost event' }, - timestamp: BASE_TIMESTAMP, - type: 3, - }; - mockRecord._emitter(TEST_EVENT); - expect(replay).not.toHaveLastSentReplay(); - - await new Promise(process.nextTick); - - // Instead of recording the above event, a full snapshot will occur. - // - // TODO: We could potentially figure out a way to save the last session, - // and produce a checkout based on a previous checkout + updates, and then - // replay the event on top. Or maybe replay the event on top of a refresh - // snapshot. - expect(mockRecord.takeFullSnapshot).toHaveBeenCalledWith(true); - - // Should be a new session - expect(replay).not.toHaveSameSession(initialSession); - - // Replay does not send immediately because checkout was due to expired session - expect(replay).not.toHaveLastSentReplay(); - - // Now do a click - domHandler({ - name: 'click', - }); - - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); - - const newTimestamp = BASE_TIMESTAMP + FIFTEEN_MINUTES; - const breadcrumbTimestamp = newTimestamp + 20; // I don't know where this 20ms comes from - - expect(replay).toHaveLastSentReplay({ - events: JSON.stringify([ - { data: { isCheckout: true }, timestamp: newTimestamp, type: 2 }, - { - type: 5, - timestamp: breadcrumbTimestamp, - data: { - tag: 'breadcrumb', - payload: { - timestamp: breadcrumbTimestamp / 1000, - type: 'default', - category: 'ui.click', - message: '', - data: {}, - }, - }, - }, - ]), - }); - }); -}); diff --git a/packages/replay/test/unit/index.test.ts b/packages/replay/test/unit/index.test.ts deleted file mode 100644 index b7f26d2d3a83..000000000000 --- a/packages/replay/test/unit/index.test.ts +++ /dev/null @@ -1,950 +0,0 @@ -import { getCurrentHub, Hub } from '@sentry/core'; -import { Event, Scope } from '@sentry/types'; -import { EventType } from 'rrweb'; - -import { - DEFAULT_FLUSH_MIN_DELAY, - MASK_ALL_TEXT_SELECTOR, - MAX_SESSION_LIFE, - REPLAY_SESSION_KEY, - VISIBILITY_CHANGE_TIMEOUT, - WINDOW, -} from '../../src/constants'; -import { ReplayContainer } from '../../src/replay'; -import type { RecordingEvent } from '../../src/types'; -import { addEvent } from '../../src/util/addEvent'; -import { createPerformanceSpans } from '../../src/util/createPerformanceSpans'; -import { useFakeTimers } from '../utils/use-fake-timers'; -import { PerformanceEntryResource } from './../fixtures/performanceEntry/resource'; -import { BASE_TIMESTAMP, RecordMock } from './../index'; -import { resetSdkMock } from './../mocks/resetSdkMock'; -import { DomHandler } from './../types'; - -useFakeTimers(); - -async function advanceTimers(time: number) { - jest.advanceTimersByTime(time); - await new Promise(process.nextTick); -} - -describe('Replay with custom mock', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - it('calls rrweb.record with custom options', async () => { - const { mockRecord } = await resetSdkMock({ - replayOptions: { - ignoreClass: 'sentry-test-ignore', - stickySession: false, - }, - }); - expect(mockRecord.mock.calls[0][0]).toMatchInlineSnapshot(` - Object { - "blockClass": "sentry-block", - "blockSelector": "[data-sentry-block],img,image,svg,path,rect,area,video,object,picture,embed,map,audio", - "emit": [Function], - "ignoreClass": "sentry-test-ignore", - "maskAllInputs": true, - "maskTextClass": "sentry-mask", - "maskTextSelector": "${MASK_ALL_TEXT_SELECTOR}", - } - `); - }); - - describe('auto save session', () => { - test.each([ - ['with stickySession=true', true, 1], - ['with stickySession=false', false, 0], - ])('%s', async (_: string, stickySession: boolean, addSummand: number) => { - let saveSessionSpy; - - jest.mock('../../src/session/saveSession', () => { - saveSessionSpy = jest.fn(); - - return { - saveSession: saveSessionSpy, - }; - }); - - const { replay } = await resetSdkMock({ - replayOptions: { - stickySession, - }, - }); - - // Initially called up to three times: once for start, then once for replay.updateSessionActivity & once for segmentId increase - expect(saveSessionSpy).toHaveBeenCalledTimes(addSummand * 3); - - replay.updateSessionActivity(); - - expect(saveSessionSpy).toHaveBeenCalledTimes(addSummand * 4); - - // In order for runFlush to actually do something, we need to add an event - const event = { - type: EventType.Custom, - data: { - tag: 'test custom', - }, - timestamp: new Date().valueOf(), - } as RecordingEvent; - - addEvent(replay, event); - - await replay.runFlush(); - - expect(saveSessionSpy).toHaveBeenCalledTimes(addSummand * 5); - }); - }); -}); - -describe('Replay', () => { - let replay: ReplayContainer; - let mockRecord: RecordMock; - let mockTransportSend: jest.SpyInstance; - let domHandler: DomHandler; - const prevLocation = WINDOW.location; - - type MockSendReplayRequest = jest.MockedFunction; - let mockSendReplayRequest: MockSendReplayRequest; - - beforeAll(async () => { - jest.setSystemTime(new Date(BASE_TIMESTAMP)); - jest.runAllTimers(); - }); - - beforeEach(async () => { - ({ mockRecord, domHandler, replay } = await resetSdkMock({ - replayOptions: { - stickySession: false, - }, - })); - - mockTransportSend = jest.spyOn(getCurrentHub().getClient()!.getTransport()!, 'send'); - - jest.spyOn(replay, 'flush'); - jest.spyOn(replay, 'runFlush'); - jest.spyOn(replay, 'sendReplayRequest'); - - // Create a new session and clear mocks because a segment (from initial - // checkout) will have already been uploaded by the time the tests run - replay.clearSession(); - replay.loadSession({ expiry: 0 }); - mockTransportSend.mockClear(); - mockSendReplayRequest = replay.sendReplayRequest as MockSendReplayRequest; - mockSendReplayRequest.mockClear(); - }); - - afterEach(async () => { - jest.runAllTimers(); - await new Promise(process.nextTick); - Object.defineProperty(WINDOW, 'location', { - value: prevLocation, - writable: true, - }); - replay.clearSession(); - jest.clearAllMocks(); - mockSendReplayRequest.mockRestore(); - mockRecord.takeFullSnapshot.mockClear(); - replay.stop(); - }); - - it('should have a session after setup', () => { - expect(replay.session).toMatchObject({ - lastActivity: BASE_TIMESTAMP, - started: BASE_TIMESTAMP, - }); - expect(replay.session?.id).toBeDefined(); - expect(replay.session?.segmentId).toBeDefined(); - }); - - it('clears session', () => { - replay.clearSession(); - expect(WINDOW.sessionStorage.getItem(REPLAY_SESSION_KEY)).toBe(null); - expect(replay.session).toBe(undefined); - }); - - it('creates a new session and triggers a full dom snapshot when document becomes visible after [VISIBILITY_CHANGE_TIMEOUT]ms', () => { - Object.defineProperty(document, 'visibilityState', { - configurable: true, - get: function () { - return 'visible'; - }, - }); - - const initialSession = replay.session; - - jest.advanceTimersByTime(VISIBILITY_CHANGE_TIMEOUT + 1); - - document.dispatchEvent(new Event('visibilitychange')); - - expect(mockRecord.takeFullSnapshot).toHaveBeenLastCalledWith(true); - - // Should have created a new session - expect(replay).not.toHaveSameSession(initialSession); - }); - - it('creates a new session and triggers a full dom snapshot when document becomes focused after [VISIBILITY_CHANGE_TIMEOUT]ms', () => { - Object.defineProperty(document, 'visibilityState', { - configurable: true, - get: function () { - return 'visible'; - }, - }); - - const initialSession = replay.session; - - jest.advanceTimersByTime(VISIBILITY_CHANGE_TIMEOUT + 1); - - WINDOW.dispatchEvent(new Event('focus')); - - expect(mockRecord.takeFullSnapshot).toHaveBeenLastCalledWith(true); - - // Should have created a new session - expect(replay).not.toHaveSameSession(initialSession); - }); - - it('does not create a new session if user hides the tab and comes back within [VISIBILITY_CHANGE_TIMEOUT] seconds', () => { - const initialSession = replay.session; - - Object.defineProperty(document, 'visibilityState', { - configurable: true, - get: function () { - return 'hidden'; - }, - }); - document.dispatchEvent(new Event('visibilitychange')); - expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); - expect(replay).toHaveSameSession(initialSession); - - // User comes back before `VISIBILITY_CHANGE_TIMEOUT` elapses - jest.advanceTimersByTime(VISIBILITY_CHANGE_TIMEOUT - 1); - Object.defineProperty(document, 'visibilityState', { - configurable: true, - get: function () { - return 'visible'; - }, - }); - document.dispatchEvent(new Event('visibilitychange')); - - expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); - // Should NOT have created a new session - expect(replay).toHaveSameSession(initialSession); - }); - - it('uploads a replay event when WINDOW is blurred', async () => { - Object.defineProperty(document, 'visibilityState', { - configurable: true, - get: function () { - return 'hidden'; - }, - }); - - // Pretend 5 seconds have passed - const ELAPSED = 5000; - jest.advanceTimersByTime(ELAPSED); - - const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 2 }; - const hiddenBreadcrumb = { - type: 5, - timestamp: +new Date(BASE_TIMESTAMP + ELAPSED) / 1000, - data: { - tag: 'breadcrumb', - payload: { - timestamp: +new Date(BASE_TIMESTAMP + ELAPSED) / 1000, - type: 'default', - category: 'ui.blur', - }, - }, - }; - - addEvent(replay, TEST_EVENT); - WINDOW.dispatchEvent(new Event('blur')); - await new Promise(process.nextTick); - expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); - expect(replay).toHaveLastSentReplay({ - events: JSON.stringify([TEST_EVENT, hiddenBreadcrumb]), - }); - // Session's last activity should not be updated - expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); - // events array should be empty - expect(replay.eventBuffer?.length).toBe(0); - }); - - it('uploads a replay event when document becomes hidden', async () => { - Object.defineProperty(document, 'visibilityState', { - configurable: true, - get: function () { - return 'hidden'; - }, - }); - // Pretend 5 seconds have passed - const ELAPSED = 5000; - jest.advanceTimersByTime(ELAPSED); - - const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 2 }; - - addEvent(replay, TEST_EVENT); - document.dispatchEvent(new Event('visibilitychange')); - jest.runAllTimers(); - await new Promise(process.nextTick); - - expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); - expect(replay).toHaveLastSentReplay({ events: JSON.stringify([TEST_EVENT]) }); - - // Session's last activity is not updated because we do not consider - // visibilitystate as user being active - expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); - // events array should be empty - expect(replay.eventBuffer?.length).toBe(0); - }); - - it('uploads a replay event if 5 seconds have elapsed since the last replay event occurred', async () => { - const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 }; - mockRecord._emitter(TEST_EVENT); - // Pretend 5 seconds have passed - const ELAPSED = 5000; - await advanceTimers(ELAPSED); - - expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); - expect(mockTransportSend).toHaveBeenCalledTimes(1); - expect(replay).toHaveLastSentReplay({ events: JSON.stringify([TEST_EVENT]) }); - - // No user activity to trigger an update - expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); - expect(replay.session?.segmentId).toBe(1); - - // events array should be empty - expect(replay.eventBuffer?.length).toBe(0); - }); - - it('uploads a replay event if 15 seconds have elapsed since the last replay upload', async () => { - const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 }; - // Fire a new event every 4 seconds, 4 times - [...Array(4)].forEach(() => { - mockRecord._emitter(TEST_EVENT); - jest.advanceTimersByTime(4000); - }); - - // We are at time = +16seconds now (relative to BASE_TIMESTAMP) - // The next event should cause an upload immediately - mockRecord._emitter(TEST_EVENT); - await new Promise(process.nextTick); - - expect(replay).toHaveLastSentReplay({ - events: JSON.stringify([...Array(5)].map(() => TEST_EVENT)), - }); - - // There should also not be another attempt at an upload 5 seconds after the last replay event - mockTransportSend.mockClear(); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); - - expect(replay).not.toHaveLastSentReplay(); - - expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); - expect(replay.session?.segmentId).toBe(1); - // events array should be empty - expect(replay.eventBuffer?.length).toBe(0); - - // Let's make sure it continues to work - mockTransportSend.mockClear(); - mockRecord._emitter(TEST_EVENT); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); - expect(replay).toHaveLastSentReplay({ events: JSON.stringify([TEST_EVENT]) }); - }); - - it('creates a new session if user has been idle for 15 minutes and comes back to click their mouse', async () => { - const initialSession = replay.session; - - expect(initialSession?.id).toBeDefined(); - expect(replay.getContext()).toEqual( - expect.objectContaining({ - initialUrl: 'http://localhost/', - initialTimestamp: BASE_TIMESTAMP, - }), - ); - - const url = 'http://dummy/'; - Object.defineProperty(WINDOW, 'location', { - value: new URL(url), - }); - - // Idle for 15 minutes - const FIFTEEN_MINUTES = 15 * 60000; - jest.advanceTimersByTime(FIFTEEN_MINUTES); - - // TBD: We are currently deciding that this event will get dropped, but - // this could/should change in the future. - const TEST_EVENT = { - data: { name: 'lost event' }, - timestamp: BASE_TIMESTAMP, - type: 3, - }; - mockRecord._emitter(TEST_EVENT); - expect(replay).not.toHaveLastSentReplay(); - - await new Promise(process.nextTick); - - // Instead of recording the above event, a full snapshot will occur. - // - // TODO: We could potentially figure out a way to save the last session, - // and produce a checkout based on a previous checkout + updates, and then - // replay the event on top. Or maybe replay the event on top of a refresh - // snapshot. - expect(mockRecord.takeFullSnapshot).toHaveBeenCalledWith(true); - - expect(replay).not.toHaveLastSentReplay(); - - // Should be a new session - expect(replay).not.toHaveSameSession(initialSession); - - // Now do a click - domHandler({ - name: 'click', - }); - - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); - - const newTimestamp = BASE_TIMESTAMP + FIFTEEN_MINUTES; - const breadcrumbTimestamp = newTimestamp + 20; // I don't know where this 20ms comes from - - expect(replay).toHaveLastSentReplay({ - recordingPayloadHeader: { segment_id: 0 }, - events: JSON.stringify([ - { data: { isCheckout: true }, timestamp: newTimestamp, type: 2 }, - { - type: 5, - timestamp: breadcrumbTimestamp, - data: { - tag: 'breadcrumb', - payload: { - timestamp: breadcrumbTimestamp / 1000, - type: 'default', - category: 'ui.click', - message: '', - data: {}, - }, - }, - }, - ]), - }); - - // `_context` should be reset when a new session is created - expect(replay.getContext()).toEqual( - expect.objectContaining({ - initialUrl: 'http://dummy/', - initialTimestamp: newTimestamp, - }), - ); - }); - - it('does not record if user has been idle for more than MAX_SESSION_LIFE and only starts a new session after a user action', async () => { - jest.clearAllMocks(); - const initialSession = replay.session; - - expect(initialSession?.id).toBeDefined(); - expect(replay.getContext()).toEqual( - expect.objectContaining({ - initialUrl: 'http://localhost/', - initialTimestamp: BASE_TIMESTAMP, - }), - ); - - const url = 'http://dummy/'; - Object.defineProperty(WINDOW, 'location', { - value: new URL(url), - }); - - // Idle for MAX_SESSION_LIFE - jest.advanceTimersByTime(MAX_SESSION_LIFE); - - // These events will not get flushed and will eventually be dropped because user is idle and session is expired - const TEST_EVENT = { - data: { name: 'lost event' }, - timestamp: MAX_SESSION_LIFE, - type: 3, - }; - mockRecord._emitter(TEST_EVENT); - // performance events can still be collected while recording is stopped - // TODO: we may want to prevent `addEvent` from adding to buffer when user is inactive - replay.addUpdate(() => { - createPerformanceSpans(replay, [ - { - type: 'navigation.navigate', - name: 'foo', - start: BASE_TIMESTAMP + MAX_SESSION_LIFE, - end: BASE_TIMESTAMP + MAX_SESSION_LIFE + 100, - }, - ]); - return true; - }); - - WINDOW.dispatchEvent(new Event('blur')); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); - - expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); - expect(replay).not.toHaveLastSentReplay(); - // Should be the same session because user has been idle and no events have caused a new session to be created - expect(replay).toHaveSameSession(initialSession); - - // @ts-ignore private - expect(replay._stopRecording).toBeUndefined(); - - // Now do a click - domHandler({ - name: 'click', - }); - // This should still be thrown away - mockRecord._emitter(TEST_EVENT); - - const NEW_TEST_EVENT = { - data: { name: 'test' }, - timestamp: BASE_TIMESTAMP + MAX_SESSION_LIFE + DEFAULT_FLUSH_MIN_DELAY + 20, - type: 3, - }; - - mockRecord._emitter(NEW_TEST_EVENT); - - // new session is created - jest.runAllTimers(); - await new Promise(process.nextTick); - - expect(replay).not.toHaveSameSession(initialSession); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); - - const newTimestamp = BASE_TIMESTAMP + MAX_SESSION_LIFE + DEFAULT_FLUSH_MIN_DELAY + 20; // I don't know where this 20ms comes from - const breadcrumbTimestamp = newTimestamp; - - jest.runAllTimers(); - await new Promise(process.nextTick); - - expect(replay).toHaveLastSentReplay({ - recordingPayloadHeader: { segment_id: 0 }, - events: JSON.stringify([ - { data: { isCheckout: true }, timestamp: newTimestamp, type: 2 }, - { - type: 5, - timestamp: breadcrumbTimestamp, - data: { - tag: 'breadcrumb', - payload: { - timestamp: breadcrumbTimestamp / 1000, - type: 'default', - category: 'ui.click', - message: '', - data: {}, - }, - }, - }, - NEW_TEST_EVENT, - ]), - }); - - // `_context` should be reset when a new session is created - expect(replay.getContext()).toEqual( - expect.objectContaining({ - initialUrl: 'http://dummy/', - initialTimestamp: newTimestamp, - }), - ); - }); - - it('uploads a dom breadcrumb 5 seconds after listener receives an event', async () => { - domHandler({ - name: 'click', - }); - - // Pretend 5 seconds have passed - const ELAPSED = 5000; - await advanceTimers(ELAPSED); - - expect(replay).toHaveLastSentReplay({ - events: JSON.stringify([ - { - type: 5, - timestamp: BASE_TIMESTAMP, - data: { - tag: 'breadcrumb', - payload: { - timestamp: BASE_TIMESTAMP / 1000, - type: 'default', - category: 'ui.click', - message: '', - data: {}, - }, - }, - }, - ]), - }); - - expect(replay.session?.segmentId).toBe(1); - }); - - it('fails to upload data on first two calls and succeeds on the third', async () => { - expect(replay.session?.segmentId).toBe(0); - const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 }; - - // Suppress console.errors - const mockConsole = jest.spyOn(console, 'error').mockImplementation(jest.fn()); - - // fail the first and second requests and pass the third one - mockTransportSend.mockImplementationOnce(() => { - throw new Error('Something bad happened'); - }); - mockRecord._emitter(TEST_EVENT); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); - - expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); - mockTransportSend.mockImplementationOnce(() => { - throw new Error('Something bad happened'); - }); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); - - // next tick should retry and succeed - mockConsole.mockRestore(); - - await advanceTimers(8000); - await advanceTimers(2000); - - expect(replay).toHaveLastSentReplay({ - replayEventPayload: expect.objectContaining({ - error_ids: [], - replay_id: expect.any(String), - replay_start_timestamp: BASE_TIMESTAMP / 1000, - // 20seconds = Add up all of the previous `advanceTimers()` - timestamp: (BASE_TIMESTAMP + 20000) / 1000 + 0.02, - trace_ids: [], - urls: ['http://localhost/'], - }), - recordingPayloadHeader: { segment_id: 0 }, - events: JSON.stringify([TEST_EVENT]), - }); - - mockTransportSend.mockClear(); - // No activity has occurred, session's last activity should remain the same - expect(replay.session?.lastActivity).toBeGreaterThanOrEqual(BASE_TIMESTAMP); - expect(replay.session?.segmentId).toBe(1); - - // next tick should do nothing - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); - expect(replay).not.toHaveLastSentReplay(); - }); - - it('fails to upload data and hits retry max and stops', async () => { - const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 }; - jest.spyOn(replay, 'sendReplay'); - - // Suppress console.errors - const mockConsole = jest.spyOn(console, 'error').mockImplementation(jest.fn()); - - // Check errors - const spyHandleException = jest.spyOn(replay, 'handleException'); - - expect(replay.session?.segmentId).toBe(0); - - // fail the first and second requests and pass the third one - mockSendReplayRequest.mockReset(); - mockSendReplayRequest.mockImplementation(() => { - throw new Error('Something bad happened'); - }); - mockRecord._emitter(TEST_EVENT); - - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); - - expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); - expect(replay.sendReplayRequest).toHaveBeenCalledTimes(1); - - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); - expect(replay.sendReplayRequest).toHaveBeenCalledTimes(2); - - await advanceTimers(10000); - expect(replay.sendReplayRequest).toHaveBeenCalledTimes(3); - - await advanceTimers(30000); - expect(replay.sendReplayRequest).toHaveBeenCalledTimes(4); - expect(replay.sendReplay).toHaveBeenCalledTimes(4); - - mockConsole.mockReset(); - - // Make sure it doesn't retry again - jest.runAllTimers(); - expect(replay.sendReplayRequest).toHaveBeenCalledTimes(4); - expect(replay.sendReplay).toHaveBeenCalledTimes(4); - - // Retries = 3 (total tries = 4 including initial attempt) - // + last exception is max retries exceeded - expect(spyHandleException).toHaveBeenCalledTimes(5); - expect(spyHandleException).toHaveBeenLastCalledWith(new Error('Unable to send Replay - max retries exceeded')); - - // No activity has occurred, session's last activity should remain the same - expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); - - // segmentId increases despite error - expect(replay.session?.segmentId).toBe(1); - }); - - it('increases segment id after each event', async () => { - Object.defineProperty(document, 'visibilityState', { - configurable: true, - get: function () { - return 'hidden'; - }, - }); - - // Pretend 5 seconds have passed - const ELAPSED = 5000; - await advanceTimers(ELAPSED); - - const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 2 }; - - addEvent(replay, TEST_EVENT); - WINDOW.dispatchEvent(new Event('blur')); - await new Promise(process.nextTick); - expect(replay).toHaveLastSentReplay({ - recordingPayloadHeader: { segment_id: 0 }, - }); - expect(replay.session?.segmentId).toBe(1); - - addEvent(replay, TEST_EVENT); - WINDOW.dispatchEvent(new Event('blur')); - jest.runAllTimers(); - await new Promise(process.nextTick); - expect(replay.session?.segmentId).toBe(2); - expect(replay).toHaveLastSentReplay({ - recordingPayloadHeader: { segment_id: 1 }, - }); - }); - - it('does not create replay event when there are no events to send', async () => { - Object.defineProperty(document, 'visibilityState', { - configurable: true, - get: function () { - return 'hidden'; - }, - }); - - document.dispatchEvent(new Event('visibilitychange')); - await new Promise(process.nextTick); - expect(replay).not.toHaveLastSentReplay(); - - // Pretend 5 seconds have passed - const ELAPSED = 5000; - await advanceTimers(ELAPSED); - - const TEST_EVENT = { - data: {}, - timestamp: BASE_TIMESTAMP + ELAPSED, - type: 2, - }; - - addEvent(replay, TEST_EVENT); - WINDOW.dispatchEvent(new Event('blur')); - await new Promise(process.nextTick); - - expect(replay).toHaveLastSentReplay({ - replayEventPayload: expect.objectContaining({ - replay_start_timestamp: BASE_TIMESTAMP / 1000, - urls: ['http://localhost/'], // this doesn't truly test if we are capturing the right URL as we don't change URLs, but good enough - }), - }); - }); - - it('has correct timestamps when there events earlier than initial timestamp', async function () { - replay.clearSession(); - replay.loadSession({ expiry: 0 }); - mockTransportSend.mockClear(); - Object.defineProperty(document, 'visibilityState', { - configurable: true, - get: function () { - return 'hidden'; - }, - }); - - document.dispatchEvent(new Event('visibilitychange')); - await new Promise(process.nextTick); - expect(replay).not.toHaveLastSentReplay(); - - // Pretend 5 seconds have passed - const ELAPSED = 5000; - await advanceTimers(ELAPSED); - - const TEST_EVENT = { - data: {}, - timestamp: BASE_TIMESTAMP + ELAPSED, - type: 2, - }; - - addEvent(replay, TEST_EVENT); - - // Add a fake event that started BEFORE - addEvent(replay, { - data: {}, - timestamp: (BASE_TIMESTAMP - 10000) / 1000, - type: 5, - }); - - WINDOW.dispatchEvent(new Event('blur')); - await new Promise(process.nextTick); - expect(replay).toHaveLastSentReplay({ - replayEventPayload: expect.objectContaining({ - replay_start_timestamp: (BASE_TIMESTAMP - 10000) / 1000, - urls: ['http://localhost/'], // this doesn't truly test if we are capturing the right URL as we don't change URLs, but good enough - tags: expect.objectContaining({ - errorSampleRate: 0, - replayType: 'session', - sessionSampleRate: 1, - }), - }), - }); - }); - - it('does not have stale `replay_start_timestamp` due to an old time origin', async function () { - const ELAPSED = 86400000 * 2; // 2 days - // Add a mock performance event that happens 2 days ago. This can happen in the browser - // when a tab has sat idle for a long period and user comes back to it. - // - // We pass a negative start time as it's a bit difficult to mock - // `@sentry/utils/browserPerformanceTimeOrigin`. This would not happen in - // real world. - replay.performanceEvents.push( - PerformanceEntryResource({ - startTime: -ELAPSED, - }), - ); - - // This should be null because `addEvent` has not been called yet - expect(replay.getContext().earliestEvent).toBe(null); - expect(mockTransportSend).toHaveBeenCalledTimes(0); - - // A new checkout occurs (i.e. a new session was started) - const TEST_EVENT = { - data: {}, - timestamp: BASE_TIMESTAMP, - type: 2, - }; - - addEvent(replay, TEST_EVENT); - // This event will trigger a flush - WINDOW.dispatchEvent(new Event('blur')); - jest.runAllTimers(); - await new Promise(process.nextTick); - - expect(mockTransportSend).toHaveBeenCalledTimes(1); - expect(replay).toHaveLastSentReplay({ - replayEventPayload: expect.objectContaining({ - // Make sure the old performance event is thrown out - replay_start_timestamp: BASE_TIMESTAMP / 1000, - }), - events: JSON.stringify([ - TEST_EVENT, - { - type: 5, - timestamp: BASE_TIMESTAMP / 1000, - data: { - tag: 'breadcrumb', - payload: { - timestamp: BASE_TIMESTAMP / 1000, - type: 'default', - category: 'ui.blur', - }, - }, - }, - ]), - }); - - // This gets reset after sending replay - expect(replay.getContext().earliestEvent).toBe(null); - }); - - it('has single flush when checkout flush and debounce flush happen near simultaneously', async () => { - // click happens first - domHandler({ - name: 'click', - }); - - // checkout - const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 2 }; - mockRecord._emitter(TEST_EVENT); - - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); - expect(replay.flush).toHaveBeenCalledTimes(1); - - // Make sure there's nothing queued up after - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); - expect(replay.flush).toHaveBeenCalledTimes(1); - }); -}); - -describe('eventProcessors', () => { - let hub: Hub; - let scope: Scope; - - beforeEach(() => { - hub = getCurrentHub(); - scope = hub.pushScope(); - }); - - afterEach(() => { - hub.popScope(); - jest.resetAllMocks(); - }); - - it('handles event processors properly', async () => { - const MUTATED_TIMESTAMP = BASE_TIMESTAMP + 3000; - - const { mockRecord } = await resetSdkMock({ - replayOptions: { - stickySession: false, - }, - }); - - const client = hub.getClient()!; - - jest.runAllTimers(); - const mockTransportSend = jest.spyOn(client.getTransport()!, 'send'); - mockTransportSend.mockReset(); - - const handler1 = jest.fn((event: Event): Event | null => { - event.timestamp = MUTATED_TIMESTAMP; - - return event; - }); - - const handler2 = jest.fn((): Event | null => { - return null; - }); - - scope.addEventProcessor(handler1); - - const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 }; - - mockRecord._emitter(TEST_EVENT); - jest.runAllTimers(); - jest.advanceTimersByTime(1); - await new Promise(process.nextTick); - - expect(mockTransportSend).toHaveBeenCalledTimes(1); - - scope.addEventProcessor(handler2); - - const TEST_EVENT2 = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 }; - - mockRecord._emitter(TEST_EVENT2); - jest.runAllTimers(); - jest.advanceTimersByTime(1); - await new Promise(process.nextTick); - - expect(mockTransportSend).toHaveBeenCalledTimes(1); - - expect(handler1).toHaveBeenCalledTimes(2); - expect(handler2).toHaveBeenCalledTimes(1); - - // This receives an envelope, which is a deeply nested array - // We only care about the fact that the timestamp was mutated - expect(mockTransportSend).toHaveBeenCalledWith( - expect.arrayContaining([ - expect.arrayContaining([expect.arrayContaining([expect.objectContaining({ timestamp: MUTATED_TIMESTAMP })])]), - ]), - ); - }); -}); diff --git a/packages/replay/test/unit/multiple-instances.test.ts b/packages/replay/test/unit/multiple-instances.test.ts deleted file mode 100644 index 9ae622605590..000000000000 --- a/packages/replay/test/unit/multiple-instances.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Replay } from './../../src'; - -it('throws on creating multiple instances', function () { - expect(() => { - new Replay(); - new Replay(); - }).toThrow(); -}); diff --git a/packages/replay/test/unit/multipleInstances.test.ts b/packages/replay/test/unit/multipleInstances.test.ts new file mode 100644 index 000000000000..a271801936ff --- /dev/null +++ b/packages/replay/test/unit/multipleInstances.test.ts @@ -0,0 +1,10 @@ +import { Replay } from '../../src'; + +describe('Unit | multipleInstances', () => { + it('throws on creating multiple instances', function () { + expect(() => { + new Replay(); + new Replay(); + }).toThrow(); + }); +}); diff --git a/packages/replay/test/unit/session/Session.test.ts b/packages/replay/test/unit/session/Session.test.ts deleted file mode 100644 index ca34e72b4172..000000000000 --- a/packages/replay/test/unit/session/Session.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -jest.mock('./../../../src/session/saveSession'); - -jest.mock('@sentry/browser', () => { - const originalModule = jest.requireActual('@sentry/browser'); - - return { - ...originalModule, - getCurrentHub: jest.fn(() => { - return { - captureEvent: jest.fn(), - getClient: jest.fn(() => ({ getDsn: jest.fn() })), - }; - }), - }; -}); - -jest.mock('@sentry/utils', () => { - const originalModule = jest.requireActual('@sentry/utils'); - - return { - ...originalModule, - uuid4: jest.fn(() => 'test_session_id'), - }; -}); - -import * as Sentry from '@sentry/browser'; - -import { WINDOW } from '../../../src/constants'; -import { getSessionSampleType, makeSession } from '../../../src/session/Session'; - -type CaptureEventMockType = jest.MockedFunction; - -beforeEach(() => { - WINDOW.sessionStorage.clear(); -}); - -afterEach(() => { - (Sentry.getCurrentHub().captureEvent as CaptureEventMockType).mockReset(); -}); - -it('does not sample', function () { - const newSession = makeSession({ - sampled: getSessionSampleType(0, 0), - }); - - expect(newSession.sampled).toBe(false); -}); - -it('samples using `sessionSampleRate`', function () { - const newSession = makeSession({ - sampled: getSessionSampleType(1.0, 0), - }); - - expect(newSession.sampled).toBe('session'); -}); - -it('samples using `errorSampleRate`', function () { - const newSession = makeSession({ - sampled: getSessionSampleType(0, 1), - }); - - expect(newSession.sampled).toBe('error'); -}); - -it('does not run sampling function if existing session was sampled', function () { - const newSession = makeSession({ - sampled: 'session', - }); - - expect(newSession.sampled).toBe('session'); -}); diff --git a/packages/replay/test/unit/session/createSession.test.ts b/packages/replay/test/unit/session/createSession.test.ts index f6942f1f6252..afe2ce3df374 100644 --- a/packages/replay/test/unit/session/createSession.test.ts +++ b/packages/replay/test/unit/session/createSession.test.ts @@ -15,53 +15,55 @@ jest.mock('@sentry/utils', () => { type CaptureEventMockType = jest.MockedFunction; -const captureEventMock: CaptureEventMockType = jest.fn(); +describe('Unit | session | createSession', () => { + const captureEventMock: CaptureEventMockType = jest.fn(); -beforeAll(() => { - WINDOW.sessionStorage.clear(); - jest.spyOn(Sentry, 'getCurrentHub'); - (Sentry.getCurrentHub as jest.Mock).mockImplementation(() => ({ - captureEvent: captureEventMock, - })); -}); - -afterEach(() => { - captureEventMock.mockReset(); -}); + beforeAll(() => { + WINDOW.sessionStorage.clear(); + jest.spyOn(Sentry, 'getCurrentHub'); + (Sentry.getCurrentHub as jest.Mock).mockImplementation(() => ({ + captureEvent: captureEventMock, + })); + }); -it('creates a new session with no sticky sessions', function () { - const newSession = createSession({ - stickySession: false, - sessionSampleRate: 1.0, - errorSampleRate: 0, + afterEach(() => { + captureEventMock.mockReset(); }); - expect(captureEventMock).not.toHaveBeenCalled(); - expect(saveSession).not.toHaveBeenCalled(); + it('creates a new session with no sticky sessions', function () { + const newSession = createSession({ + stickySession: false, + sessionSampleRate: 1.0, + errorSampleRate: 0, + }); + expect(captureEventMock).not.toHaveBeenCalled(); - expect(newSession.id).toBe('test_session_id'); - expect(newSession.started).toBeGreaterThan(0); - expect(newSession.lastActivity).toEqual(newSession.started); -}); + expect(saveSession).not.toHaveBeenCalled(); -it('creates a new session with sticky sessions', function () { - const newSession = createSession({ - stickySession: true, - sessionSampleRate: 1.0, - errorSampleRate: 0, + expect(newSession.id).toBe('test_session_id'); + expect(newSession.started).toBeGreaterThan(0); + expect(newSession.lastActivity).toEqual(newSession.started); }); - expect(captureEventMock).not.toHaveBeenCalled(); - expect(saveSession).toHaveBeenCalledWith( - expect.objectContaining({ - id: 'test_session_id', - segmentId: 0, - started: expect.any(Number), - lastActivity: expect.any(Number), - }), - ); + it('creates a new session with sticky sessions', function () { + const newSession = createSession({ + stickySession: true, + sessionSampleRate: 1.0, + errorSampleRate: 0, + }); + expect(captureEventMock).not.toHaveBeenCalled(); + + expect(saveSession).toHaveBeenCalledWith( + expect.objectContaining({ + id: 'test_session_id', + segmentId: 0, + started: expect.any(Number), + lastActivity: expect.any(Number), + }), + ); - expect(newSession.id).toBe('test_session_id'); - expect(newSession.started).toBeGreaterThan(0); - expect(newSession.lastActivity).toEqual(newSession.started); + expect(newSession.id).toBe('test_session_id'); + expect(newSession.started).toBeGreaterThan(0); + expect(newSession.lastActivity).toEqual(newSession.started); + }); }); diff --git a/packages/replay/test/unit/session/deleteSession.test.ts b/packages/replay/test/unit/session/deleteSession.test.ts deleted file mode 100644 index 3e8354d0c8d4..000000000000 --- a/packages/replay/test/unit/session/deleteSession.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { REPLAY_SESSION_KEY, WINDOW } from '../../../src/constants'; -import { deleteSession } from '../../../src/session/deleteSession'; - -const storageEngine = WINDOW.sessionStorage; - -it('deletes a session', function () { - storageEngine.setItem( - REPLAY_SESSION_KEY, - '{"id":"fd09adfc4117477abc8de643e5a5798a","started":1648827162630,"lastActivity":1648827162658}', - ); - - deleteSession(); - - expect(storageEngine.getItem(REPLAY_SESSION_KEY)).toBe(null); -}); - -it('deletes an empty session', function () { - expect(() => deleteSession()).not.toThrow(); -}); diff --git a/packages/replay/test/unit/session/fetchSession.test.ts b/packages/replay/test/unit/session/fetchSession.test.ts index 9f7e7694a5e0..526c9c7969d1 100644 --- a/packages/replay/test/unit/session/fetchSession.test.ts +++ b/packages/replay/test/unit/session/fetchSession.test.ts @@ -3,67 +3,69 @@ import { fetchSession } from '../../../src/session/fetchSession'; const oldSessionStorage = WINDOW.sessionStorage; -beforeAll(() => { - WINDOW.sessionStorage.clear(); -}); +describe('Unit | session | fetchSession', () => { + beforeAll(() => { + WINDOW.sessionStorage.clear(); + }); -afterEach(() => { - Object.defineProperty(WINDOW, 'sessionStorage', { - writable: true, - value: oldSessionStorage, + afterEach(() => { + Object.defineProperty(WINDOW, 'sessionStorage', { + writable: true, + value: oldSessionStorage, + }); + WINDOW.sessionStorage.clear(); }); - WINDOW.sessionStorage.clear(); -}); -it('fetches a valid and sampled session', function () { - WINDOW.sessionStorage.setItem( - REPLAY_SESSION_KEY, - '{"id":"fd09adfc4117477abc8de643e5a5798a","sampled": "session","started":1648827162630,"lastActivity":1648827162658}', - ); + it('fetches a valid and sampled session', function () { + WINDOW.sessionStorage.setItem( + REPLAY_SESSION_KEY, + '{"id":"fd09adfc4117477abc8de643e5a5798a","sampled": "session","started":1648827162630,"lastActivity":1648827162658}', + ); - expect(fetchSession()).toEqual({ - id: 'fd09adfc4117477abc8de643e5a5798a', - lastActivity: 1648827162658, - segmentId: 0, - sampled: 'session', - started: 1648827162630, + expect(fetchSession()).toEqual({ + id: 'fd09adfc4117477abc8de643e5a5798a', + lastActivity: 1648827162658, + segmentId: 0, + sampled: 'session', + started: 1648827162630, + }); }); -}); -it('fetches an unsampled session', function () { - WINDOW.sessionStorage.setItem( - REPLAY_SESSION_KEY, - '{"id":"fd09adfc4117477abc8de643e5a5798a","sampled": false,"started":1648827162630,"lastActivity":1648827162658}', - ); + it('fetches an unsampled session', function () { + WINDOW.sessionStorage.setItem( + REPLAY_SESSION_KEY, + '{"id":"fd09adfc4117477abc8de643e5a5798a","sampled": false,"started":1648827162630,"lastActivity":1648827162658}', + ); - expect(fetchSession()).toEqual({ - id: 'fd09adfc4117477abc8de643e5a5798a', - lastActivity: 1648827162658, - segmentId: 0, - sampled: false, - started: 1648827162630, + expect(fetchSession()).toEqual({ + id: 'fd09adfc4117477abc8de643e5a5798a', + lastActivity: 1648827162658, + segmentId: 0, + sampled: false, + started: 1648827162630, + }); }); -}); -it('fetches a session that does not exist', function () { - expect(fetchSession()).toBe(null); -}); + it('fetches a session that does not exist', function () { + expect(fetchSession()).toBe(null); + }); -it('fetches an invalid session', function () { - WINDOW.sessionStorage.setItem(REPLAY_SESSION_KEY, '{"id":"fd09adfc4117477abc8de643e5a5798a",'); + it('fetches an invalid session', function () { + WINDOW.sessionStorage.setItem(REPLAY_SESSION_KEY, '{"id":"fd09adfc4117477abc8de643e5a5798a",'); - expect(fetchSession()).toBe(null); -}); + expect(fetchSession()).toBe(null); + }); -it('safely attempts to fetch session when Session Storage is disabled', function () { - Object.defineProperty(WINDOW, 'sessionStorage', { - writable: true, - value: { - getItem: () => { - throw new Error('No Session Storage for you'); + it('safely attempts to fetch session when Session Storage is disabled', function () { + Object.defineProperty(WINDOW, 'sessionStorage', { + writable: true, + value: { + getItem: () => { + throw new Error('No Session Storage for you'); + }, }, - }, - }); + }); - expect(fetchSession()).toEqual(null); + expect(fetchSession()).toEqual(null); + }); }); diff --git a/packages/replay/test/unit/session/getSession.test.ts b/packages/replay/test/unit/session/getSession.test.ts index fd1606820c99..54c259fb7772 100644 --- a/packages/replay/test/unit/session/getSession.test.ts +++ b/packages/replay/test/unit/session/getSession.test.ts @@ -8,7 +8,7 @@ import { makeSession } from '../../../src/session/Session'; jest.mock('@sentry/utils', () => { return { ...(jest.requireActual('@sentry/utils') as { string: unknown }), - uuid4: jest.fn(() => 'test_session_id'), + uuid4: jest.fn(() => 'test_session_uuid'), }; }); @@ -27,166 +27,168 @@ function createMockSession(when: number = new Date().getTime()) { }); } -beforeAll(() => { - jest.spyOn(CreateSession, 'createSession'); - jest.spyOn(FetchSession, 'fetchSession'); - WINDOW.sessionStorage.clear(); -}); - -afterEach(() => { - WINDOW.sessionStorage.clear(); - (CreateSession.createSession as jest.MockedFunction).mockClear(); - (FetchSession.fetchSession as jest.MockedFunction).mockClear(); -}); - -it('creates a non-sticky session when one does not exist', function () { - const { session } = getSession({ - expiry: 900000, - stickySession: false, - ...SAMPLE_RATES, +describe('Unit | session | getSession', () => { + beforeAll(() => { + jest.spyOn(CreateSession, 'createSession'); + jest.spyOn(FetchSession, 'fetchSession'); + WINDOW.sessionStorage.clear(); }); - expect(FetchSession.fetchSession).not.toHaveBeenCalled(); - expect(CreateSession.createSession).toHaveBeenCalled(); - - expect(session).toEqual({ - id: 'test_session_id', - segmentId: 0, - lastActivity: expect.any(Number), - sampled: 'session', - started: expect.any(Number), + afterEach(() => { + WINDOW.sessionStorage.clear(); + (CreateSession.createSession as jest.MockedFunction).mockClear(); + (FetchSession.fetchSession as jest.MockedFunction).mockClear(); }); - // Should not have anything in storage - expect(FetchSession.fetchSession()).toBe(null); -}); + it('creates a non-sticky session when one does not exist', function () { + const { session } = getSession({ + expiry: 900000, + stickySession: false, + ...SAMPLE_RATES, + }); -it('creates a non-sticky session, regardless of session existing in sessionStorage', function () { - saveSession(createMockSession(new Date().getTime() - 10000)); - - const { session } = getSession({ - expiry: 1000, - stickySession: false, - ...SAMPLE_RATES, - }); + expect(FetchSession.fetchSession).not.toHaveBeenCalled(); + expect(CreateSession.createSession).toHaveBeenCalled(); - expect(FetchSession.fetchSession).not.toHaveBeenCalled(); - expect(CreateSession.createSession).toHaveBeenCalled(); - - expect(session).toBeDefined(); -}); - -it('creates a non-sticky session, when one is expired', function () { - const { session } = getSession({ - expiry: 1000, - stickySession: false, - ...SAMPLE_RATES, - currentSession: makeSession({ - id: 'old_session_id', - lastActivity: new Date().getTime() - 1001, - started: new Date().getTime() - 1001, + expect(session).toEqual({ + id: 'test_session_uuid', segmentId: 0, + lastActivity: expect.any(Number), sampled: 'session', - }), + started: expect.any(Number), + }); + + // Should not have anything in storage + expect(FetchSession.fetchSession()).toBe(null); }); - expect(FetchSession.fetchSession).not.toHaveBeenCalled(); - expect(CreateSession.createSession).toHaveBeenCalled(); + it('creates a non-sticky session, regardless of session existing in sessionStorage', function () { + saveSession(createMockSession(new Date().getTime() - 10000)); - expect(session).toBeDefined(); - expect(session.id).not.toBe('old_session_id'); -}); + const { session } = getSession({ + expiry: 1000, + stickySession: false, + ...SAMPLE_RATES, + }); -it('creates a sticky session when one does not exist', function () { - expect(FetchSession.fetchSession()).toBe(null); + expect(FetchSession.fetchSession).not.toHaveBeenCalled(); + expect(CreateSession.createSession).toHaveBeenCalled(); - const { session } = getSession({ - expiry: 900000, - stickySession: true, - sessionSampleRate: 1.0, - errorSampleRate: 0.0, + expect(session).toBeDefined(); }); - expect(FetchSession.fetchSession).toHaveBeenCalled(); - expect(CreateSession.createSession).toHaveBeenCalled(); - - expect(session).toEqual({ - id: 'test_session_id', - segmentId: 0, - lastActivity: expect.any(Number), - sampled: 'session', - started: expect.any(Number), + it('creates a non-sticky session, when one is expired', function () { + const { session } = getSession({ + expiry: 1000, + stickySession: false, + ...SAMPLE_RATES, + currentSession: makeSession({ + id: 'old_session_id', + lastActivity: new Date().getTime() - 1001, + started: new Date().getTime() - 1001, + segmentId: 0, + sampled: 'session', + }), + }); + + expect(FetchSession.fetchSession).not.toHaveBeenCalled(); + expect(CreateSession.createSession).toHaveBeenCalled(); + + expect(session).toBeDefined(); + expect(session.id).not.toBe('old_session_id'); }); - // Should not have anything in storage - expect(FetchSession.fetchSession()).toEqual({ - id: 'test_session_id', - segmentId: 0, - lastActivity: expect.any(Number), - sampled: 'session', - started: expect.any(Number), - }); -}); + it('creates a sticky session when one does not exist', function () { + expect(FetchSession.fetchSession()).toBe(null); -it('fetches an existing sticky session', function () { - const now = new Date().getTime(); - saveSession(createMockSession(now)); + const { session } = getSession({ + expiry: 900000, + stickySession: true, + sessionSampleRate: 1.0, + errorSampleRate: 0.0, + }); - const { session } = getSession({ - expiry: 1000, - stickySession: true, - sessionSampleRate: 1.0, - errorSampleRate: 0.0, - }); + expect(FetchSession.fetchSession).toHaveBeenCalled(); + expect(CreateSession.createSession).toHaveBeenCalled(); - expect(FetchSession.fetchSession).toHaveBeenCalled(); - expect(CreateSession.createSession).not.toHaveBeenCalled(); + expect(session).toEqual({ + id: 'test_session_uuid', + segmentId: 0, + lastActivity: expect.any(Number), + sampled: 'session', + started: expect.any(Number), + }); - expect(session).toEqual({ - id: 'test_session_id', - segmentId: 0, - lastActivity: now, - sampled: 'session', - started: now, + // Should not have anything in storage + expect(FetchSession.fetchSession()).toEqual({ + id: 'test_session_uuid', + segmentId: 0, + lastActivity: expect.any(Number), + sampled: 'session', + started: expect.any(Number), + }); }); -}); -it('fetches an expired sticky session', function () { - const now = new Date().getTime(); - saveSession(createMockSession(new Date().getTime() - 2000)); + it('fetches an existing sticky session', function () { + const now = new Date().getTime(); + saveSession(createMockSession(now)); - const { session } = getSession({ - expiry: 1000, - stickySession: true, - ...SAMPLE_RATES, - }); + const { session } = getSession({ + expiry: 1000, + stickySession: true, + sessionSampleRate: 1.0, + errorSampleRate: 0.0, + }); - expect(FetchSession.fetchSession).toHaveBeenCalled(); - expect(CreateSession.createSession).toHaveBeenCalled(); - - expect(session.id).toBe('test_session_id'); - expect(session.lastActivity).toBeGreaterThanOrEqual(now); - expect(session.started).toBeGreaterThanOrEqual(now); - expect(session.segmentId).toBe(0); -}); + expect(FetchSession.fetchSession).toHaveBeenCalled(); + expect(CreateSession.createSession).not.toHaveBeenCalled(); -it('fetches a non-expired non-sticky session', function () { - const { session } = getSession({ - expiry: 1000, - stickySession: false, - ...SAMPLE_RATES, - currentSession: makeSession({ - id: 'test_session_id_2', - lastActivity: +new Date() - 500, - started: +new Date() - 500, + expect(session).toEqual({ + id: 'test_session_id', segmentId: 0, + lastActivity: now, sampled: 'session', - }), + started: now, + }); }); - expect(FetchSession.fetchSession).not.toHaveBeenCalled(); - expect(CreateSession.createSession).not.toHaveBeenCalled(); + it('fetches an expired sticky session', function () { + const now = new Date().getTime(); + saveSession(createMockSession(new Date().getTime() - 2000)); - expect(session.id).toBe('test_session_id_2'); - expect(session.segmentId).toBe(0); + const { session } = getSession({ + expiry: 1000, + stickySession: true, + ...SAMPLE_RATES, + }); + + expect(FetchSession.fetchSession).toHaveBeenCalled(); + expect(CreateSession.createSession).toHaveBeenCalled(); + + expect(session.id).toBe('test_session_uuid'); + expect(session.lastActivity).toBeGreaterThanOrEqual(now); + expect(session.started).toBeGreaterThanOrEqual(now); + expect(session.segmentId).toBe(0); + }); + + it('fetches a non-expired non-sticky session', function () { + const { session } = getSession({ + expiry: 1000, + stickySession: false, + ...SAMPLE_RATES, + currentSession: makeSession({ + id: 'test_session_uuid_2', + lastActivity: +new Date() - 500, + started: +new Date() - 500, + segmentId: 0, + sampled: 'session', + }), + }); + + expect(FetchSession.fetchSession).not.toHaveBeenCalled(); + expect(CreateSession.createSession).not.toHaveBeenCalled(); + + expect(session.id).toBe('test_session_uuid_2'); + expect(session.segmentId).toBe(0); + }); }); diff --git a/packages/replay/test/unit/session/saveSession.test.ts b/packages/replay/test/unit/session/saveSession.test.ts index c1a884bca84f..ff8b8e15f204 100644 --- a/packages/replay/test/unit/session/saveSession.test.ts +++ b/packages/replay/test/unit/session/saveSession.test.ts @@ -2,23 +2,25 @@ import { REPLAY_SESSION_KEY, WINDOW } from '../../../src/constants'; import { saveSession } from '../../../src/session/saveSession'; import { makeSession } from '../../../src/session/Session'; -beforeAll(() => { - WINDOW.sessionStorage.clear(); -}); - -afterEach(() => { - WINDOW.sessionStorage.clear(); -}); +describe('Unit | session | saveSession', () => { + beforeAll(() => { + WINDOW.sessionStorage.clear(); + }); -it('saves a valid session', function () { - const session = makeSession({ - id: 'fd09adfc4117477abc8de643e5a5798a', - segmentId: 0, - started: 1648827162630, - lastActivity: 1648827162658, - sampled: 'session', + afterEach(() => { + WINDOW.sessionStorage.clear(); }); - saveSession(session); - expect(WINDOW.sessionStorage.getItem(REPLAY_SESSION_KEY)).toEqual(JSON.stringify(session)); + it('saves a valid session', function () { + const session = makeSession({ + id: 'fd09adfc4117477abc8de643e5a5798a', + segmentId: 0, + started: 1648827162630, + lastActivity: 1648827162658, + sampled: 'session', + }); + saveSession(session); + + expect(WINDOW.sessionStorage.getItem(REPLAY_SESSION_KEY)).toEqual(JSON.stringify(session)); + }); }); diff --git a/packages/replay/test/unit/session/sessionSampling.test.ts b/packages/replay/test/unit/session/sessionSampling.test.ts new file mode 100644 index 000000000000..cb730006d572 --- /dev/null +++ b/packages/replay/test/unit/session/sessionSampling.test.ts @@ -0,0 +1,35 @@ +import { getSessionSampleType, makeSession } from '../../../src/session/Session'; + +describe('Unit | session | sessionSampling', () => { + it('does not sample', function () { + const newSession = makeSession({ + sampled: getSessionSampleType(0, 0), + }); + + expect(newSession.sampled).toBe(false); + }); + + it('samples using `sessionSampleRate`', function () { + const newSession = makeSession({ + sampled: getSessionSampleType(1.0, 0), + }); + + expect(newSession.sampled).toBe('session'); + }); + + it('samples using `errorSampleRate`', function () { + const newSession = makeSession({ + sampled: getSessionSampleType(0, 1), + }); + + expect(newSession.sampled).toBe('error'); + }); + + it('does not run sampling function if existing session was sampled', function () { + const newSession = makeSession({ + sampled: 'session', + }); + + expect(newSession.sampled).toBe('session'); + }); +}); diff --git a/packages/replay/test/unit/util/createPerformanceEntry.test.ts b/packages/replay/test/unit/util/createPerformanceEntry.test.ts new file mode 100644 index 000000000000..cd250f7db64c --- /dev/null +++ b/packages/replay/test/unit/util/createPerformanceEntry.test.ts @@ -0,0 +1,34 @@ +import { createPerformanceEntries } from '../../../src/util/createPerformanceEntries'; + +describe('Unit | util | createPerformanceEntries', () => { + it('ignores sdks own requests', function () { + const data = { + name: 'https://ingest.f00.f00/api/1/envelope/?sentry_key=dsn&sentry_version=7', + entryType: 'resource', + startTime: 234462.69999998808, + duration: 55.70000001788139, + initiatorType: 'fetch', + nextHopProtocol: '', + workerStart: 0, + redirectStart: 0, + redirectEnd: 0, + fetchStart: 234462.69999998808, + domainLookupStart: 0, + domainLookupEnd: 0, + connectStart: 0, + connectEnd: 0, + secureConnectionStart: 0, + requestStart: 0, + responseStart: 0, + responseEnd: 234518.40000000596, + transferSize: 0, + encodedBodySize: 0, + decodedBodySize: 0, + serverTiming: [], + workerTiming: [], + } as const; + + // @ts-ignore Needs a PerformanceEntry mock + expect(createPerformanceEntries([data])).toEqual([]); + }); +}); diff --git a/packages/replay/test/unit/util/createReplayEnvelope.test.ts b/packages/replay/test/unit/util/createReplayEnvelope.test.ts index 456c0863e3fc..d0245d0d2f5f 100644 --- a/packages/replay/test/unit/util/createReplayEnvelope.test.ts +++ b/packages/replay/test/unit/util/createReplayEnvelope.test.ts @@ -1,9 +1,9 @@ -import { ReplayEvent } from '@sentry/types'; +import type { ReplayEvent } from '@sentry/types'; import { makeDsn } from '@sentry/utils'; import { createReplayEnvelope } from '../../../src/util/createReplayEnvelope'; -describe('createReplayEnvelope', () => { +describe('Unit | util | createReplayEnvelope', () => { const REPLAY_ID = 'MY_REPLAY_ID'; const replayEvent: ReplayEvent = { @@ -20,13 +20,13 @@ describe('createReplayEnvelope', () => { environment: 'production', sdk: { integrations: ['BrowserTracing', 'Replay'], - name: 'sentry.javascript.browser', + name: 'sentry.javascript.unknown', version: '7.25.0', }, + replay_type: 'error', tags: { sessionSampleRate: 1, errorSampleRate: 0, - replayType: 'error', }, }; @@ -47,7 +47,7 @@ describe('createReplayEnvelope', () => { expect(envelope).toEqual([ { event_id: REPLAY_ID, - sdk: { name: 'sentry.javascript.browser', version: '7.25.0' }, + sdk: { name: 'sentry.javascript.unknown', version: '7.25.0' }, sent_at: expect.any(String), }, [ @@ -59,9 +59,10 @@ describe('createReplayEnvelope', () => { event_id: REPLAY_ID, platform: 'javascript', replay_id: REPLAY_ID, - sdk: { integrations: ['BrowserTracing', 'Replay'], name: 'sentry.javascript.browser', version: '7.25.0' }, + replay_type: 'error', + sdk: { integrations: ['BrowserTracing', 'Replay'], name: 'sentry.javascript.unknown', version: '7.25.0' }, segment_id: 3, - tags: { errorSampleRate: 0, replayType: 'error', sessionSampleRate: 1 }, + tags: { errorSampleRate: 0, sessionSampleRate: 1 }, timestamp: 1670837008.634, trace_ids: ['traceId'], type: 'replay_event', @@ -79,7 +80,7 @@ describe('createReplayEnvelope', () => { expect(envelope).toEqual([ { event_id: REPLAY_ID, - sdk: { name: 'sentry.javascript.browser', version: '7.25.0' }, + sdk: { name: 'sentry.javascript.unknown', version: '7.25.0' }, sent_at: expect.any(String), dsn: 'https://abc@sentry.io:1234/123', }, @@ -92,9 +93,10 @@ describe('createReplayEnvelope', () => { event_id: REPLAY_ID, platform: 'javascript', replay_id: REPLAY_ID, - sdk: { integrations: ['BrowserTracing', 'Replay'], name: 'sentry.javascript.browser', version: '7.25.0' }, + sdk: { integrations: ['BrowserTracing', 'Replay'], name: 'sentry.javascript.unknown', version: '7.25.0' }, segment_id: 3, - tags: { errorSampleRate: 0, replayType: 'error', sessionSampleRate: 1 }, + replay_type: 'error', + tags: { errorSampleRate: 0, sessionSampleRate: 1 }, timestamp: 1670837008.634, trace_ids: ['traceId'], type: 'replay_event', diff --git a/packages/replay/test/unit/util/debounce.test.ts b/packages/replay/test/unit/util/debounce.test.ts index 27f95c96a4c6..b75adc6bf3bd 100644 --- a/packages/replay/test/unit/util/debounce.test.ts +++ b/packages/replay/test/unit/util/debounce.test.ts @@ -1,6 +1,6 @@ import { debounce } from '../../../src/util/debounce'; -describe('debounce', () => { +describe('Unit | util | debounce', () => { jest.useFakeTimers(); it('delay the execution of the passed callback function by the passed minDelay', () => { const callback = jest.fn(); diff --git a/packages/replay/test/unit/util/dedupePerformanceEntries.test.ts b/packages/replay/test/unit/util/dedupePerformanceEntries.test.ts index 15e66421829b..08884a19407a 100644 --- a/packages/replay/test/unit/util/dedupePerformanceEntries.test.ts +++ b/packages/replay/test/unit/util/dedupePerformanceEntries.test.ts @@ -1,66 +1,67 @@ -// eslint-disable-next-line import/no-unresolved import { dedupePerformanceEntries } from '../../../src/util/dedupePerformanceEntries'; import { PerformanceEntryLcp } from './../../fixtures/performanceEntry/lcp'; import { PerformanceEntryNavigation } from './../../fixtures/performanceEntry/navigation'; import { PerformanceEntryResource } from './../../fixtures/performanceEntry/resource'; -it('does nothing with a single entry', function () { - const entries = [PerformanceEntryNavigation()]; - expect(dedupePerformanceEntries([], entries)).toEqual(entries); -}); +describe('Unit | util | dedupePerformanceEntries', () => { + it('does nothing with a single entry', function () { + const entries = [PerformanceEntryNavigation()]; + expect(dedupePerformanceEntries([], entries)).toEqual(entries); + }); -it('dedupes 2 duplicate entries correctly', function () { - const entries = [PerformanceEntryNavigation(), PerformanceEntryNavigation()]; - expect(dedupePerformanceEntries([], entries)).toEqual([entries[0]]); -}); + it('dedupes 2 duplicate entries correctly', function () { + const entries = [PerformanceEntryNavigation(), PerformanceEntryNavigation()]; + expect(dedupePerformanceEntries([], entries)).toEqual([entries[0]]); + }); -it('dedupes multiple+mixed entries from new list', function () { - const a = PerformanceEntryNavigation({ startTime: 0 }); - const b = PerformanceEntryNavigation({ - startTime: 1, - name: 'https://foo.bar/', + it('dedupes multiple+mixed entries from new list', function () { + const a = PerformanceEntryNavigation({ startTime: 0 }); + const b = PerformanceEntryNavigation({ + startTime: 1, + name: 'https://foo.bar/', + }); + const c = PerformanceEntryNavigation({ startTime: 2, type: 'reload' }); + const d = PerformanceEntryResource({ startTime: 1.5 }); + const entries = [a, a, b, d, b, c]; + expect(dedupePerformanceEntries([], entries)).toEqual([a, b, d, c]); }); - const c = PerformanceEntryNavigation({ startTime: 2, type: 'reload' }); - const d = PerformanceEntryResource({ startTime: 1.5 }); - const entries = [a, a, b, d, b, c]; - expect(dedupePerformanceEntries([], entries)).toEqual([a, b, d, c]); -}); -it('dedupes from initial list and new list', function () { - const a = PerformanceEntryNavigation({ startTime: 0 }); - const b = PerformanceEntryNavigation({ - startTime: 1, - name: 'https://foo.bar/', + it('dedupes from initial list and new list', function () { + const a = PerformanceEntryNavigation({ startTime: 0 }); + const b = PerformanceEntryNavigation({ + startTime: 1, + name: 'https://foo.bar/', + }); + const c = PerformanceEntryNavigation({ startTime: 2, type: 'reload' }); + const d = PerformanceEntryNavigation({ startTime: 1000 }); + const entries = [a, a, b, b, c]; + expect(dedupePerformanceEntries([a, d], entries)).toEqual([a, b, c, d]); }); - const c = PerformanceEntryNavigation({ startTime: 2, type: 'reload' }); - const d = PerformanceEntryNavigation({ startTime: 1000 }); - const entries = [a, a, b, b, c]; - expect(dedupePerformanceEntries([a, d], entries)).toEqual([a, b, c, d]); -}); -it('selects the latest lcp value given multiple lcps in new list', function () { - const a = PerformanceEntryResource({ startTime: 0 }); - const b = PerformanceEntryLcp({ startTime: 100, name: 'b' }); - const c = PerformanceEntryLcp({ startTime: 200, name: 'c' }); - const d = PerformanceEntryLcp({ startTime: 5, name: 'd' }); // don't assume they are ordered - const entries = [a, b, c, d]; - expect(dedupePerformanceEntries([], entries)).toEqual([a, c]); -}); + it('selects the latest lcp value given multiple lcps in new list', function () { + const a = PerformanceEntryResource({ startTime: 0 }); + const b = PerformanceEntryLcp({ startTime: 100, name: 'b' }); + const c = PerformanceEntryLcp({ startTime: 200, name: 'c' }); + const d = PerformanceEntryLcp({ startTime: 5, name: 'd' }); // don't assume they are ordered + const entries = [a, b, c, d]; + expect(dedupePerformanceEntries([], entries)).toEqual([a, c]); + }); -it('selects the latest lcp value from new list, given multiple lcps in new list with an existing lcp', function () { - const a = PerformanceEntryResource({ startTime: 0 }); - const b = PerformanceEntryLcp({ startTime: 100, name: 'b' }); - const c = PerformanceEntryLcp({ startTime: 200, name: 'c' }); - const d = PerformanceEntryLcp({ startTime: 5, name: 'd' }); // don't assume they are ordered - const entries = [b, c, d]; - expect(dedupePerformanceEntries([a, d], entries)).toEqual([a, c]); -}); + it('selects the latest lcp value from new list, given multiple lcps in new list with an existing lcp', function () { + const a = PerformanceEntryResource({ startTime: 0 }); + const b = PerformanceEntryLcp({ startTime: 100, name: 'b' }); + const c = PerformanceEntryLcp({ startTime: 200, name: 'c' }); + const d = PerformanceEntryLcp({ startTime: 5, name: 'd' }); // don't assume they are ordered + const entries = [b, c, d]; + expect(dedupePerformanceEntries([a, d], entries)).toEqual([a, c]); + }); -it('selects the existing lcp value given multiple lcps in new list with an existing lcp having the latest startTime', function () { - const a = PerformanceEntryResource({ startTime: 0 }); - const b = PerformanceEntryLcp({ startTime: 100, name: 'b' }); - const c = PerformanceEntryLcp({ startTime: 200, name: 'c' }); - const d = PerformanceEntryLcp({ startTime: 5, name: 'd' }); // don't assume they are ordered - const entries = [b, d]; - expect(dedupePerformanceEntries([a, c], entries)).toEqual([a, c]); + it('selects the existing lcp value given multiple lcps in new list with an existing lcp having the latest startTime', function () { + const a = PerformanceEntryResource({ startTime: 0 }); + const b = PerformanceEntryLcp({ startTime: 100, name: 'b' }); + const c = PerformanceEntryLcp({ startTime: 200, name: 'c' }); + const d = PerformanceEntryLcp({ startTime: 5, name: 'd' }); // don't assume they are ordered + const entries = [b, d]; + expect(dedupePerformanceEntries([a, c], entries)).toEqual([a, c]); + }); }); diff --git a/packages/replay/test/unit/util/isExpired.test.ts b/packages/replay/test/unit/util/isExpired.test.ts index a83677cf856e..2914cf1198d1 100644 --- a/packages/replay/test/unit/util/isExpired.test.ts +++ b/packages/replay/test/unit/util/isExpired.test.ts @@ -1,23 +1,25 @@ import { isExpired } from '../../../src/util/isExpired'; -it('is expired', function () { - expect(isExpired(0, 150, 200)).toBe(true); // expired at ts = 150 -}); +describe('Unit | util | isExpired', () => { + it('is expired', function () { + expect(isExpired(0, 150, 200)).toBe(true); // expired at ts = 150 + }); -it('is not expired', function () { - expect(isExpired(100, 150, 200)).toBe(false); // expires at ts >= 250 -}); + it('is not expired', function () { + expect(isExpired(100, 150, 200)).toBe(false); // expires at ts >= 250 + }); -it('is expired when target time reaches exactly the expiry time', function () { - expect(isExpired(100, 150, 250)).toBe(true); // expires at ts >= 250 -}); + it('is expired when target time reaches exactly the expiry time', function () { + expect(isExpired(100, 150, 250)).toBe(true); // expires at ts >= 250 + }); -it('never expires if expiry is 0', function () { - expect(isExpired(300, 0, 200)).toBe(false); - expect(isExpired(0, 0, 200)).toBe(false); -}); + it('never expires if expiry is 0', function () { + expect(isExpired(300, 0, 200)).toBe(false); + expect(isExpired(0, 0, 200)).toBe(false); + }); -it('always expires if expiry is < 0', function () { - expect(isExpired(300, -1, 200)).toBe(true); - expect(isExpired(0, -1, 200)).toBe(true); + it('always expires if expiry is < 0', function () { + expect(isExpired(300, -1, 200)).toBe(true); + expect(isExpired(0, -1, 200)).toBe(true); + }); }); diff --git a/packages/replay/test/unit/util/isSampled.test.ts b/packages/replay/test/unit/util/isSampled.test.ts index 6b26f1d11046..97a824cb0e9d 100644 --- a/packages/replay/test/unit/util/isSampled.test.ts +++ b/packages/replay/test/unit/util/isSampled.test.ts @@ -13,10 +13,9 @@ const cases: [number, number, boolean][] = [ [0.5, 0.0, true], ]; -jest.spyOn(Math, 'random'); -const mockRandom = Math.random as jest.MockedFunction; +describe('Unit | util | isSampled', () => { + const mockRandom = jest.spyOn(Math, 'random'); -describe('isSampled', () => { test.each(cases)( 'given sample rate of %p and RNG returns %p, result should be %p', (sampleRate: number, mockRandomValue: number, expectedResult: boolean) => { diff --git a/packages/replay/test/unit/util/isSessionExpired.test.ts b/packages/replay/test/unit/util/isSessionExpired.test.ts index 0db371e8e4ef..381b8ffe6428 100644 --- a/packages/replay/test/unit/util/isSessionExpired.test.ts +++ b/packages/replay/test/unit/util/isSessionExpired.test.ts @@ -12,18 +12,20 @@ function createSession(extra?: Record) { }); } -it('session last activity is older than expiry time', function () { - expect(isSessionExpired(createSession(), 100, 200)).toBe(true); // Session expired at ts = 100 -}); +describe('Unit | util | isSessionExpired', () => { + it('session last activity is older than expiry time', function () { + expect(isSessionExpired(createSession(), 100, 200)).toBe(true); // Session expired at ts = 100 + }); -it('session last activity is not older than expiry time', function () { - expect(isSessionExpired(createSession({ lastActivity: 100 }), 150, 200)).toBe(false); // Session expires at ts >= 250 -}); + it('session last activity is not older than expiry time', function () { + expect(isSessionExpired(createSession({ lastActivity: 100 }), 150, 200)).toBe(false); // Session expires at ts >= 250 + }); -it('session age is not older than max session life', function () { - expect(isSessionExpired(createSession(), 1_800_000, 50_000)).toBe(false); -}); + it('session age is not older than max session life', function () { + expect(isSessionExpired(createSession(), 1_800_000, 50_000)).toBe(false); + }); -it('session age is older than max session life', function () { - expect(isSessionExpired(createSession(), 1_800_000, 1_800_001)).toBe(true); // Session expires at ts >= 1_800_000 + it('session age is older than max session life', function () { + expect(isSessionExpired(createSession(), 1_800_000, 1_800_001)).toBe(true); // Session expires at ts >= 1_800_000 + }); }); diff --git a/packages/replay/test/unit/util/getReplayEvent.test.ts b/packages/replay/test/unit/util/prepareReplayEvent.test.ts similarity index 63% rename from packages/replay/test/unit/util/getReplayEvent.test.ts rename to packages/replay/test/unit/util/prepareReplayEvent.test.ts index 2ac30c1a053c..c16c4c0afbb7 100644 --- a/packages/replay/test/unit/util/getReplayEvent.test.ts +++ b/packages/replay/test/unit/util/prepareReplayEvent.test.ts @@ -1,19 +1,19 @@ -import { BrowserClient } from '@sentry/browser'; -import { getCurrentHub, Hub, Scope } from '@sentry/core'; -import { Client, ReplayEvent } from '@sentry/types'; +import type { Hub, Scope } from '@sentry/core'; +import { getCurrentHub } from '@sentry/core'; +import type { Client, ReplayEvent } from '@sentry/types'; import { REPLAY_EVENT_NAME } from '../../../src/constants'; -import { getReplayEvent } from '../../../src/util/getReplayEvent'; -import { getDefaultBrowserClientOptions } from '../../utils/getDefaultBrowserClientOptions'; +import { prepareReplayEvent } from '../../../src/util/prepareReplayEvent'; +import { getDefaultClientOptions, TestClient } from '../../utils/TestClient'; -describe('getReplayEvent', () => { +describe('Unit | util | prepareReplayEvent', () => { let hub: Hub; let client: Client; let scope: Scope; beforeEach(() => { hub = getCurrentHub(); - client = new BrowserClient(getDefaultBrowserClientOptions()); + client = new TestClient(getDefaultClientOptions()); hub.bindClient(client); client = hub.getClient()!; @@ -33,10 +33,11 @@ describe('getReplayEvent', () => { trace_ids: ['trace-ID'], urls: ['https://sentry.io/'], replay_id: replayId, + replay_type: 'session', segment_id: 3, }; - const replayEvent = await getReplayEvent({ scope, client, event }); + const replayEvent = await prepareReplayEvent({ scope, client, replayId, event }); expect(replayEvent).toEqual({ type: 'replay_event', @@ -45,13 +46,13 @@ describe('getReplayEvent', () => { trace_ids: ['trace-ID'], urls: ['https://sentry.io/'], replay_id: 'replay-ID', + replay_type: 'session', segment_id: 3, platform: 'javascript', - // generated uuid with 32 chars - event_id: expect.stringMatching(/^\w{32}$/), + event_id: 'replay-ID', environment: 'production', sdk: { - name: 'sentry.javascript.browser', + name: 'sentry.javascript.unknown', version: 'version:Test', }, sdkProcessingMetadata: {}, diff --git a/packages/replay/test/unit/worker/Compressor.test.ts b/packages/replay/test/unit/worker/Compressor.test.ts index 77b95a07439b..e8de4bd2f94a 100644 --- a/packages/replay/test/unit/worker/Compressor.test.ts +++ b/packages/replay/test/unit/worker/Compressor.test.ts @@ -2,7 +2,7 @@ import pako from 'pako'; import { Compressor } from '../../../worker/src/Compressor'; -describe('Compressor', () => { +describe('Unit | worker | Compressor', () => { it('compresses multiple events', () => { const compressor = new Compressor(); @@ -26,28 +26,16 @@ describe('Compressor', () => { expect(restored).toBe(JSON.stringify(events)); }); - it('ignores undefined events', () => { + it('throws on invalid/undefined events', () => { const compressor = new Compressor(); - const events = [ - { - id: 1, - foo: ['bar', 'baz'], - }, - undefined, - { - id: 2, - foo: [false], - }, - ] as Record[]; - - events.forEach(event => compressor.addEvent(event)); + // @ts-ignore ignoring type for test + expect(() => void compressor.addEvent(undefined)).toThrow(); const compressed = compressor.finish(); const restored = pako.inflate(compressed, { to: 'string' }); - const expected = [events[0], events[2]]; - expect(restored).toBe(JSON.stringify(expected)); + expect(restored).toBe(JSON.stringify([])); }); }); diff --git a/packages/replay/test/utils/TestClient.ts b/packages/replay/test/utils/TestClient.ts new file mode 100644 index 000000000000..ad39b82084a9 --- /dev/null +++ b/packages/replay/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/replay/src/session/deleteSession.ts b/packages/replay/test/utils/clearSession.ts similarity index 51% rename from packages/replay/src/session/deleteSession.ts rename to packages/replay/test/utils/clearSession.ts index b1567337ee96..b5b64ac04531 100644 --- a/packages/replay/src/session/deleteSession.ts +++ b/packages/replay/test/utils/clearSession.ts @@ -1,9 +1,15 @@ -import { REPLAY_SESSION_KEY, WINDOW } from '../constants'; +import { REPLAY_SESSION_KEY, WINDOW } from '../../src/constants'; +import type { ReplayContainer } from '../../src/types'; + +export function clearSession(replay: ReplayContainer) { + deleteSession(); + replay.session = undefined; +} /** * Deletes a session from storage */ -export function deleteSession(): void { +function deleteSession(): void { const hasSessionStorage = 'sessionStorage' in WINDOW; if (!hasSessionStorage) { diff --git a/packages/replay/test/utils/getDefaultBrowserClientOptions.ts b/packages/replay/test/utils/getDefaultBrowserClientOptions.ts deleted file mode 100644 index 74aafee7a1b3..000000000000 --- a/packages/replay/test/utils/getDefaultBrowserClientOptions.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { createTransport } from '@sentry/core'; -import { ClientOptions } from '@sentry/types'; -import { resolvedSyncPromise } from '@sentry/utils'; - -export function getDefaultBrowserClientOptions(options: Partial = {}): ClientOptions { - return { - integrations: [], - dsn: 'https://username@domain/123', - transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => resolvedSyncPromise({})), - stackParser: () => [], - ...options, - }; -} diff --git a/packages/replay/worker/src/Compressor.ts b/packages/replay/worker/src/Compressor.ts index 8f7698e313e3..a70eb51422ad 100644 --- a/packages/replay/worker/src/Compressor.ts +++ b/packages/replay/worker/src/Compressor.ts @@ -27,7 +27,7 @@ export class Compressor { public addEvent(data: Record): void { if (!data) { - return; + throw new Error('Adding invalid event'); } // If the event is not the first event, we need to prefix it with a `,` so // that we end up with a list of events @@ -35,10 +35,9 @@ export class Compressor { // TODO: We may want Z_SYNC_FLUSH or Z_FULL_FLUSH (not sure the difference) // Using NO_FLUSH here for now as we can create many attachments that our // web UI will get API rate limited. - this.deflate.push(prefix + JSON.stringify(data), constants.Z_NO_FLUSH); - this.added++; + this.deflate.push(prefix + JSON.stringify(data), constants.Z_SYNC_FLUSH); - return; + this.added++; } public finish(): Uint8Array { diff --git a/packages/replay/worker/src/handleMessage.ts b/packages/replay/worker/src/handleMessage.ts index 45796cd62141..0dd9e871c972 100644 --- a/packages/replay/worker/src/handleMessage.ts +++ b/packages/replay/worker/src/handleMessage.ts @@ -16,8 +16,7 @@ const handlers: Handlers = { }, addEvent: (data: Record) => { - compressor.addEvent(data); - return ''; + return compressor.addEvent(data); }, finish: () => { @@ -48,7 +47,7 @@ export function handleMessage(e: MessageEvent): void { id, method, success: false, - response: err, + response: err.message, }); // eslint-disable-next-line no-console diff --git a/packages/replay/workflows/build.yml b/packages/replay/workflows/build.yml index 3048a58f6f26..849baa8629ec 100644 --- a/packages/replay/workflows/build.yml +++ b/packages/replay/workflows/build.yml @@ -26,7 +26,7 @@ jobs: yarn build - run: | - yarn build:npm + yarn build:tarball - uses: actions/upload-artifact@v3.1.1 with: diff --git a/packages/serverless/package.json b/packages/serverless/package.json index de02c4f88ad8..75bab0bec495 100644 --- a/packages/serverless/package.json +++ b/packages/serverless/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/serverless", - "version": "7.29.0", + "version": "7.30.0", "description": "Official Sentry SDK for various serverless solutions", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/serverless", @@ -16,10 +16,10 @@ "access": "public" }, "dependencies": { - "@sentry/node": "7.29.0", - "@sentry/tracing": "7.29.0", - "@sentry/types": "7.29.0", - "@sentry/utils": "7.29.0", + "@sentry/node": "7.30.0", + "@sentry/tracing": "7.30.0", + "@sentry/types": "7.30.0", + "@sentry/utils": "7.30.0", "@types/aws-lambda": "^8.10.62", "@types/express": "^4.17.14", "tslib": "^1.9.3" @@ -38,18 +38,16 @@ "read-pkg": "^5.2.0" }, "scripts": { - "build": "run-p build:rollup build:types build:bundle && yarn build:extras", - "build:awslambda-layer": "echo 'WARNING: AWS lambda layer build emporarily moved to \\`build:bundle\\`.'", + "build": "run-p build:transpile build:types build:bundle", "build:bundle": "yarn ts-node scripts/buildLambdaLayer.ts", - "build:dev": "run-p build:rollup build:types", - "build:extras": "yarn build:awslambda-layer", - "build:rollup": "rollup -c rollup.npm.config.js", + "build:dev": "run-p build:transpile build:types", + "build:transpile": "rollup -c rollup.npm.config.js", "build:types": "tsc -p tsconfig.types.json", - "build:watch": "run-p build:rollup:watch build:types:watch", + "build:watch": "run-p build:transpile:watch build:types:watch", "build:dev:watch": "run-s build:watch", - "build:rollup:watch": "rollup -c rollup.npm.config.js --watch", + "build:transpile:watch": "rollup -c rollup.npm.config.js --watch", "build:types:watch": "tsc -p tsconfig.types.json --watch", - "build:npm": "ts-node ../../scripts/prepack.ts --bundles && npm pack ./build/npm", + "build:tarball": "ts-node ../../scripts/prepack.ts --bundles && npm pack ./build/npm", "circularDepCheck": "madge --circular src/index.ts", "clean": "rimraf build dist-awslambda-layer coverage sentry-serverless-*.tgz", "fix": "run-s fix:eslint fix:prettier", diff --git a/packages/serverless/scripts/buildLambdaLayer.ts b/packages/serverless/scripts/buildLambdaLayer.ts index 1a04aafde8aa..c7e2199aedbb 100644 --- a/packages/serverless/scripts/buildLambdaLayer.ts +++ b/packages/serverless/scripts/buildLambdaLayer.ts @@ -15,6 +15,7 @@ function run(cmd: string, options?: childProcess.ExecSyncOptions): string { async function buildLambdaLayer(): Promise { // Create the main SDK bundle + // TODO: Check if we can get rid of this, after the lerna 6/nx update?? await ensureBundleBuildPrereqs({ dependencies: ['@sentry/utils', '@sentry/hub', '@sentry/core', '@sentry/tracing', '@sentry/node'], }); diff --git a/packages/serverless/src/awslambda.ts b/packages/serverless/src/awslambda.ts index 18b9ea2387d4..29dd10da6602 100644 --- a/packages/serverless/src/awslambda.ts +++ b/packages/serverless/src/awslambda.ts @@ -1,12 +1,13 @@ /* eslint-disable max-lines */ +import type { Scope } from '@sentry/node'; import * as Sentry from '@sentry/node'; -import { captureException, captureMessage, flush, getCurrentHub, Scope, withScope } from '@sentry/node'; +import { captureException, captureMessage, flush, getCurrentHub, withScope } from '@sentry/node'; import { extractTraceparentData } from '@sentry/tracing'; -import { Integration } from '@sentry/types'; +import type { Integration } from '@sentry/types'; import { baggageHeaderToDynamicSamplingContext, dsnFromString, dsnToString, isString, logger } from '@sentry/utils'; // NOTE: I have no idea how to fix this right now, and don't want to waste more time, as it builds just fine — Kamil // eslint-disable-next-line import/no-unresolved -import { Context, Handler } from 'aws-lambda'; +import type { Context, Handler } from 'aws-lambda'; import { existsSync } from 'fs'; import { hostname } from 'os'; import { basename, resolve } from 'path'; diff --git a/packages/serverless/src/awsservices.ts b/packages/serverless/src/awsservices.ts index 66b2ea93c52d..431d0653ee69 100644 --- a/packages/serverless/src/awsservices.ts +++ b/packages/serverless/src/awsservices.ts @@ -1,9 +1,9 @@ import { getCurrentHub } from '@sentry/node'; -import { Integration, Span, Transaction } from '@sentry/types'; +import type { Integration, Span, Transaction } from '@sentry/types'; import { fill } from '@sentry/utils'; // 'aws-sdk/global' import is expected to be type-only so it's erased in the final .js file. // When TypeScript compiler is upgraded, use `import type` syntax to explicitly assert that we don't want to load a module here. -import * as AWS from 'aws-sdk/global'; +import type * as AWS from 'aws-sdk/global'; type GenericParams = { [key: string]: any }; // eslint-disable-line @typescript-eslint/no-explicit-any type MakeRequestCallback = (err: AWS.AWSError, data: TResult) => void; diff --git a/packages/serverless/src/gcpfunction/cloud_events.ts b/packages/serverless/src/gcpfunction/cloud_events.ts index 6ab7b173784c..00088687bec6 100644 --- a/packages/serverless/src/gcpfunction/cloud_events.ts +++ b/packages/serverless/src/gcpfunction/cloud_events.ts @@ -2,7 +2,7 @@ import { captureException, flush, getCurrentHub } from '@sentry/node'; import { logger } from '@sentry/utils'; import { domainify, getActiveDomain, proxyFunction } from '../utils'; -import { CloudEventFunction, CloudEventFunctionWithCallback, WrapperOptions } from './general'; +import type { CloudEventFunction, CloudEventFunctionWithCallback, WrapperOptions } from './general'; export type CloudEventFunctionWrapperOptions = WrapperOptions; diff --git a/packages/serverless/src/gcpfunction/events.ts b/packages/serverless/src/gcpfunction/events.ts index 6b83fd8bfba0..f1ad73379ca1 100644 --- a/packages/serverless/src/gcpfunction/events.ts +++ b/packages/serverless/src/gcpfunction/events.ts @@ -2,7 +2,7 @@ import { captureException, flush, getCurrentHub } from '@sentry/node'; import { logger } from '@sentry/utils'; import { domainify, getActiveDomain, proxyFunction } from '../utils'; -import { EventFunction, EventFunctionWithCallback, WrapperOptions } from './general'; +import type { EventFunction, EventFunctionWithCallback, WrapperOptions } from './general'; export type EventFunctionWrapperOptions = WrapperOptions; diff --git a/packages/serverless/src/gcpfunction/http.ts b/packages/serverless/src/gcpfunction/http.ts index 3b62d8301bf1..6636ccdab3d6 100644 --- a/packages/serverless/src/gcpfunction/http.ts +++ b/packages/serverless/src/gcpfunction/http.ts @@ -1,9 +1,10 @@ -import { AddRequestDataToEventOptions, captureException, flush, getCurrentHub } from '@sentry/node'; +import type { AddRequestDataToEventOptions } from '@sentry/node'; +import { captureException, flush, getCurrentHub } from '@sentry/node'; import { extractTraceparentData } from '@sentry/tracing'; import { baggageHeaderToDynamicSamplingContext, isString, logger, stripUrlQueryAndFragment } from '@sentry/utils'; import { domainify, getActiveDomain, proxyFunction } from './../utils'; -import { HttpFunction, WrapperOptions } from './general'; +import type { HttpFunction, WrapperOptions } from './general'; // TODO (v8 / #5257): Remove this whole old/new business and just use the new stuff type ParseRequestOptions = AddRequestDataToEventOptions['include'] & { diff --git a/packages/serverless/src/gcpfunction/index.ts b/packages/serverless/src/gcpfunction/index.ts index 2cd8a0bec73f..12e912d45b77 100644 --- a/packages/serverless/src/gcpfunction/index.ts +++ b/packages/serverless/src/gcpfunction/index.ts @@ -1,5 +1,5 @@ import * as Sentry from '@sentry/node'; -import { Integration } from '@sentry/types'; +import type { Integration } from '@sentry/types'; import { GoogleCloudGrpc } from '../google-cloud-grpc'; import { GoogleCloudHttp } from '../google-cloud-http'; diff --git a/packages/serverless/src/google-cloud-grpc.ts b/packages/serverless/src/google-cloud-grpc.ts index 4037877cb3bc..f18aee42e002 100644 --- a/packages/serverless/src/google-cloud-grpc.ts +++ b/packages/serverless/src/google-cloud-grpc.ts @@ -1,7 +1,7 @@ import { getCurrentHub } from '@sentry/node'; -import { Integration, Span, Transaction } from '@sentry/types'; +import type { Integration, Span, Transaction } from '@sentry/types'; import { fill } from '@sentry/utils'; -import { EventEmitter } from 'events'; +import type { EventEmitter } from 'events'; interface GrpcFunction extends CallableFunction { (...args: unknown[]): EventEmitter; diff --git a/packages/serverless/src/google-cloud-http.ts b/packages/serverless/src/google-cloud-http.ts index 81b8d6a69155..04dec4bbe134 100644 --- a/packages/serverless/src/google-cloud-http.ts +++ b/packages/serverless/src/google-cloud-http.ts @@ -1,8 +1,8 @@ // '@google-cloud/common' import is expected to be type-only so it's erased in the final .js file. // When TypeScript compiler is upgraded, use `import type` syntax to explicitly assert that we don't want to load a module here. -import * as common from '@google-cloud/common'; +import type * as common from '@google-cloud/common'; import { getCurrentHub } from '@sentry/node'; -import { Integration, Span, Transaction } from '@sentry/types'; +import type { Integration, Span, Transaction } from '@sentry/types'; import { fill } from '@sentry/utils'; type RequestOptions = common.DecorateRequestOptions; diff --git a/packages/serverless/src/utils.ts b/packages/serverless/src/utils.ts index 0fc9a1a776aa..38e85c6a1c4e 100644 --- a/packages/serverless/src/utils.ts +++ b/packages/serverless/src/utils.ts @@ -1,4 +1,4 @@ -import { Event } from '@sentry/node'; +import type { Event } from '@sentry/node'; import { addExceptionMechanism } from '@sentry/utils'; import * as domain from 'domain'; diff --git a/packages/serverless/test/awslambda.test.ts b/packages/serverless/test/awslambda.test.ts index 419974e73bfe..36b3ed5626e6 100644 --- a/packages/serverless/test/awslambda.test.ts +++ b/packages/serverless/test/awslambda.test.ts @@ -1,6 +1,6 @@ // NOTE: I have no idea how to fix this right now, and don't want to waste more time, as it builds just fine — Kamil // eslint-disable-next-line import/no-unresolved -import { Callback, Handler } from 'aws-lambda'; +import type { Callback, Handler } from 'aws-lambda'; import * as Sentry from '../src'; diff --git a/packages/serverless/test/gcpfunction.test.ts b/packages/serverless/test/gcpfunction.test.ts index 20f878565a56..a6a58ebb2d8b 100644 --- a/packages/serverless/test/gcpfunction.test.ts +++ b/packages/serverless/test/gcpfunction.test.ts @@ -3,7 +3,7 @@ import * as domain from 'domain'; import * as Sentry from '../src'; import { wrapCloudEventFunction, wrapEventFunction, wrapHttpFunction } from '../src/gcpfunction'; -import { +import type { CloudEventFunction, CloudEventFunctionWithCallback, EventFunction, diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 929e035cdc9e..566ece0f9d82 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/svelte", - "version": "7.29.0", + "version": "7.30.0", "description": "Official Sentry SDK for Svelte", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/svelte", @@ -16,9 +16,9 @@ "access": "public" }, "dependencies": { - "@sentry/browser": "7.29.0", - "@sentry/types": "7.29.0", - "@sentry/utils": "7.29.0", + "@sentry/browser": "7.30.0", + "@sentry/types": "7.30.0", + "@sentry/utils": "7.30.0", "magic-string": "^0.26.2", "tslib": "^1.9.3" }, @@ -31,15 +31,15 @@ "svelte-jester": "^2.3.2" }, "scripts": { - "build": "run-p build:rollup build:types", + "build": "run-p build:transpile build:types", "build:dev": "run-s build", - "build:rollup": "rollup -c rollup.npm.config.js", + "build:transpile": "rollup -c rollup.npm.config.js", "build:types": "tsc -p tsconfig.types.json", - "build:watch": "run-p build:rollup:watch build:types:watch", + "build:watch": "run-p build:transpile:watch build:types:watch", "build:dev:watch": "run-s build:watch", - "build:rollup:watch": "rollup -c rollup.npm.config.js --watch", + "build:transpile:watch": "rollup -c rollup.npm.config.js --watch", "build:types:watch": "tsc -p tsconfig.types.json --watch", - "build:npm": "ts-node ../../scripts/prepack.ts && npm pack ./build", + "build:tarball": "ts-node ../../scripts/prepack.ts && npm pack ./build", "circularDepCheck": "madge --circular src/index.ts", "clean": "rimraf build coverage sentry-svelte-*.tgz", "fix": "run-s fix:eslint fix:prettier", diff --git a/packages/svelte/src/config.ts b/packages/svelte/src/config.ts index 03c6c0dc1f01..4a62cecfbed5 100644 --- a/packages/svelte/src/config.ts +++ b/packages/svelte/src/config.ts @@ -1,7 +1,7 @@ -import { PreprocessorGroup } from 'svelte/types/compiler/preprocess'; +import type { PreprocessorGroup } from 'svelte/types/compiler/preprocess'; import { componentTrackingPreprocessor, defaultComponentTrackingOptions } from './preprocessors'; -import { SentryPreprocessorGroup, SentrySvelteConfigOptions, SvelteConfig } from './types'; +import type { SentryPreprocessorGroup, SentrySvelteConfigOptions, SvelteConfig } from './types'; const DEFAULT_SENTRY_OPTIONS: SentrySvelteConfigOptions = { componentTracking: defaultComponentTrackingOptions, diff --git a/packages/svelte/src/performance.ts b/packages/svelte/src/performance.ts index 359950e41264..5cb9e0254557 100644 --- a/packages/svelte/src/performance.ts +++ b/packages/svelte/src/performance.ts @@ -1,10 +1,10 @@ import { getCurrentHub } from '@sentry/browser'; -import { Span, Transaction } from '@sentry/types'; +import type { Span, Transaction } from '@sentry/types'; import { afterUpdate, beforeUpdate, onMount } from 'svelte'; import { current_component } from 'svelte/internal'; import { DEFAULT_COMPONENT_NAME, UI_SVELTE_INIT, UI_SVELTE_UPDATE } from './constants'; -import { TrackComponentOptions } from './types'; +import type { TrackComponentOptions } from './types'; const defaultTrackComponentOptions: { trackInit: boolean; diff --git a/packages/svelte/src/preprocessors.ts b/packages/svelte/src/preprocessors.ts index 49f8346aa47d..57722b1dad15 100644 --- a/packages/svelte/src/preprocessors.ts +++ b/packages/svelte/src/preprocessors.ts @@ -1,7 +1,7 @@ import MagicString from 'magic-string'; -import { PreprocessorGroup } from 'svelte/types/compiler/preprocess'; +import type { PreprocessorGroup } from 'svelte/types/compiler/preprocess'; -import { ComponentTrackingInitOptions, SentryPreprocessorGroup, TrackComponentOptions } from './types'; +import type { ComponentTrackingInitOptions, SentryPreprocessorGroup, TrackComponentOptions } from './types'; export const defaultComponentTrackingOptions: Required = { trackComponents: true, diff --git a/packages/svelte/src/sdk.ts b/packages/svelte/src/sdk.ts index 4857163ec137..448ccacbf046 100644 --- a/packages/svelte/src/sdk.ts +++ b/packages/svelte/src/sdk.ts @@ -1,4 +1,5 @@ -import { addGlobalEventProcessor, BrowserOptions, init as browserInit, SDK_VERSION } from '@sentry/browser'; +import type { BrowserOptions } from '@sentry/browser'; +import { addGlobalEventProcessor, init as browserInit, SDK_VERSION } from '@sentry/browser'; import type { EventProcessor } from '@sentry/types'; import { getDomElement } from '@sentry/utils'; /** diff --git a/packages/svelte/src/types.ts b/packages/svelte/src/types.ts index e601b239667d..4f4516606483 100644 --- a/packages/svelte/src/types.ts +++ b/packages/svelte/src/types.ts @@ -1,5 +1,5 @@ -import { CompileOptions } from 'svelte/types/compiler'; -import { PreprocessorGroup } from 'svelte/types/compiler/preprocess'; +import type { CompileOptions } from 'svelte/types/compiler'; +import type { PreprocessorGroup } from 'svelte/types/compiler/preprocess'; // Adds an id property to the preprocessor object we can use to check for duplication // in the preprocessors array diff --git a/packages/svelte/test/config.test.ts b/packages/svelte/test/config.test.ts index db8a699b5ab7..5e33355a4994 100644 --- a/packages/svelte/test/config.test.ts +++ b/packages/svelte/test/config.test.ts @@ -1,6 +1,6 @@ import { withSentryConfig } from '../src/config'; import { componentTrackingPreprocessor, FIRST_PASS_COMPONENT_TRACKING_PREPROC_ID } from '../src/preprocessors'; -import { SentryPreprocessorGroup, SentrySvelteConfigOptions, SvelteConfig } from '../src/types'; +import type { SentryPreprocessorGroup, SentrySvelteConfigOptions, SvelteConfig } from '../src/types'; describe('withSentryConfig', () => { it.each([ diff --git a/packages/svelte/test/performance.test.ts b/packages/svelte/test/performance.test.ts index cb039870a719..cbfd320f35bd 100644 --- a/packages/svelte/test/performance.test.ts +++ b/packages/svelte/test/performance.test.ts @@ -1,4 +1,4 @@ -import { Scope } from '@sentry/core'; +import type { Scope } from '@sentry/core'; import { act, render } from '@testing-library/svelte'; // linter doesn't like Svelte component imports diff --git a/packages/svelte/test/preprocessors.test.ts b/packages/svelte/test/preprocessors.test.ts index dd3d3cff1130..57a235ce4cfd 100644 --- a/packages/svelte/test/preprocessors.test.ts +++ b/packages/svelte/test/preprocessors.test.ts @@ -6,7 +6,7 @@ import { defaultComponentTrackingOptions, FIRST_PASS_COMPONENT_TRACKING_PREPROC_ID, } from '../src/preprocessors'; -import { SentryPreprocessorGroup } from '../src/types'; +import type { SentryPreprocessorGroup } from '../src/types'; function expectComponentCodeToBeModified( preprocessedComponents: { diff --git a/packages/svelte/test/sdk.test.ts b/packages/svelte/test/sdk.test.ts index a716cb35105e..8203b025a437 100644 --- a/packages/svelte/test/sdk.test.ts +++ b/packages/svelte/test/sdk.test.ts @@ -1,5 +1,5 @@ import { addGlobalEventProcessor, init as browserInit, SDK_VERSION } from '@sentry/browser'; -import { EventProcessor } from '@sentry/types'; +import type { EventProcessor } from '@sentry/types'; import { detectAndReportSvelteKit, init as svelteInit, isSvelteKitApp } from '../src/sdk'; diff --git a/packages/tracing/package.json b/packages/tracing/package.json index 6abfbad9a9a0..7ab1417f30c0 100644 --- a/packages/tracing/package.json +++ b/packages/tracing/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/tracing", - "version": "7.29.0", + "version": "7.30.0", "description": "Extensions for Sentry AM", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/tracing", @@ -16,29 +16,27 @@ "access": "public" }, "dependencies": { - "@sentry/core": "7.29.0", - "@sentry/types": "7.29.0", - "@sentry/utils": "7.29.0", + "@sentry/core": "7.30.0", + "@sentry/types": "7.30.0", + "@sentry/utils": "7.30.0", "tslib": "^1.9.3" }, "devDependencies": { - "@sentry/browser": "7.29.0", + "@sentry/browser": "7.30.0", "@types/express": "^4.17.14" }, "scripts": { - "build": "run-p build:rollup build:types build:bundle && yarn build:extras #necessary for integration tests", - "build:bundle": "yarn ts-node ../../scripts/ensure-bundle-deps.ts && yarn rollup --config rollup.bundle.config.js", - "build:dev": "run-p build:rollup build:types", - "build:extras": "yarn build:prepack", - "build:prepack": "ts-node ../../scripts/prepack.ts --bundles", - "build:rollup": "rollup -c rollup.npm.config.js", + "build": "run-p build:transpile build:types build:bundle", + "build:bundle": "yarn rollup --config rollup.bundle.config.js", + "build:dev": "run-p build:transpile build:types", + "build:transpile": "rollup -c rollup.npm.config.js", "build:types": "tsc -p tsconfig.types.json", - "build:watch": "run-p build:rollup:watch build:bundle:watch build:types:watch", + "build:watch": "run-p build:transpile:watch build:bundle:watch build:types:watch", "build:bundle:watch": "rollup --config rollup.bundle.config.js --watch", - "build:dev:watch": "run-p build:rollup:watch build:types:watch", - "build:rollup:watch": "rollup -c rollup.npm.config.js --watch", + "build:dev:watch": "run-p build:transpile:watch build:types:watch", + "build:transpile:watch": "rollup -c rollup.npm.config.js --watch", "build:types:watch": "tsc -p tsconfig.types.json --watch", - "build:npm": "ts-node ../../scripts/prepack.ts --bundles && npm pack ./build/npm", + "build:tarball": "ts-node ../../scripts/prepack.ts --bundles && npm pack ./build/npm", "clean": "rimraf build coverage sentry-tracing-*.tgz", "circularDepCheck": "madge --circular src/index.ts", "fix": "run-s fix:eslint fix:prettier", diff --git a/packages/tracing/src/browser/backgroundtab.ts b/packages/tracing/src/browser/backgroundtab.ts index 62df53a9c9b9..8c55e9853901 100644 --- a/packages/tracing/src/browser/backgroundtab.ts +++ b/packages/tracing/src/browser/backgroundtab.ts @@ -1,7 +1,7 @@ import { logger } from '@sentry/utils'; -import { IdleTransaction } from '../idletransaction'; -import { SpanStatusType } from '../span'; +import type { IdleTransaction } from '../idletransaction'; +import type { SpanStatusType } from '../span'; import { getActiveTransaction } from '../utils'; import { WINDOW } from './types'; diff --git a/packages/tracing/src/browser/browsertracing.ts b/packages/tracing/src/browser/browsertracing.ts index 1207ab8be9de..df275bc7357d 100644 --- a/packages/tracing/src/browser/browsertracing.ts +++ b/packages/tracing/src/browser/browsertracing.ts @@ -1,23 +1,16 @@ /* eslint-disable max-lines */ -import { Hub } from '@sentry/core'; -import { EventProcessor, Integration, Transaction, TransactionContext, TransactionSource } from '@sentry/types'; +import type { Hub } from '@sentry/core'; +import type { EventProcessor, Integration, Transaction, TransactionContext, TransactionSource } from '@sentry/types'; import { baggageHeaderToDynamicSamplingContext, getDomElement, logger } from '@sentry/utils'; import { startIdleTransaction } from '../hubextensions'; -import { - DEFAULT_FINAL_TIMEOUT, - DEFAULT_HEARTBEAT_INTERVAL, - DEFAULT_IDLE_TIMEOUT, - IdleTransaction, -} from '../idletransaction'; +import type { IdleTransaction } from '../idletransaction'; +import { DEFAULT_FINAL_TIMEOUT, DEFAULT_HEARTBEAT_INTERVAL, DEFAULT_IDLE_TIMEOUT } from '../idletransaction'; import { extractTraceparentData } from '../utils'; import { registerBackgroundTabDetection } from './backgroundtab'; import { addPerformanceEntries, startTrackingLongTasks, startTrackingWebVitals } from './metrics'; -import { - defaultRequestInstrumentationOptions, - instrumentOutgoingRequests, - RequestInstrumentationOptions, -} from './request'; +import type { RequestInstrumentationOptions } from './request'; +import { defaultRequestInstrumentationOptions, instrumentOutgoingRequests } from './request'; import { instrumentRoutingWithDefaults } from './router'; import { WINDOW } from './types'; @@ -109,13 +102,14 @@ export interface BrowserTracingOptions extends RequestInstrumentationOptions { * * @returns A (potentially) modified context object, with `sampled = false` if the transaction should be dropped. */ - beforeNavigate?(context: TransactionContext): TransactionContext | undefined; + beforeNavigate?(this: void, context: TransactionContext): TransactionContext | undefined; /** * Instrumentation that creates routing change transactions. By default creates * pageload and navigation transactions. */ routingInstrumentation( + this: void, customStartTransaction: (context: TransactionContext) => T | undefined, startTransactionOnPageLoad?: boolean, startTransactionOnLocationChange?: boolean, @@ -187,7 +181,6 @@ export class BrowserTracing implements Integration { public setupOnce(_: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void { this._getCurrentHub = getCurrentHub; - // eslint-disable-next-line @typescript-eslint/unbound-method const { routingInstrumentation: instrumentRouting, startTransactionOnLocationChange, @@ -230,7 +223,6 @@ export class BrowserTracing implements Integration { return undefined; } - // eslint-disable-next-line @typescript-eslint/unbound-method const { beforeNavigate, idleTimeout, finalTimeout, heartbeatInterval } = this.options; const isPageloadTransaction = context.op === 'pageload'; diff --git a/packages/tracing/src/browser/metrics/index.ts b/packages/tracing/src/browser/metrics/index.ts index 481a6ea59d83..7bd575e90024 100644 --- a/packages/tracing/src/browser/metrics/index.ts +++ b/packages/tracing/src/browser/metrics/index.ts @@ -1,9 +1,9 @@ /* eslint-disable max-lines */ -import { Measurements } from '@sentry/types'; +import type { Measurements } from '@sentry/types'; import { browserPerformanceTimeOrigin, htmlTreeAsString, logger } from '@sentry/utils'; -import { IdleTransaction } from '../../idletransaction'; -import { Transaction } from '../../transaction'; +import type { IdleTransaction } from '../../idletransaction'; +import type { Transaction } from '../../transaction'; import { getActiveTransaction, msToSec } from '../../utils'; import { WINDOW } from '../types'; import { onCLS } from '../web-vitals/getCLS'; @@ -11,7 +11,7 @@ import { onFID } from '../web-vitals/getFID'; import { onLCP } from '../web-vitals/getLCP'; import { getVisibilityWatcher } from '../web-vitals/lib/getVisibilityWatcher'; import { observe } from '../web-vitals/lib/observe'; -import { NavigatorDeviceMemory, NavigatorNetworkInformation } from '../web-vitals/types'; +import type { NavigatorDeviceMemory, NavigatorNetworkInformation } from '../web-vitals/types'; import { _startChild, isMeasurementValue } from './utils'; function getBrowserPerformanceAPI(): Performance | undefined { diff --git a/packages/tracing/src/browser/metrics/utils.ts b/packages/tracing/src/browser/metrics/utils.ts index 4894dbd3ec8e..7c30a9090245 100644 --- a/packages/tracing/src/browser/metrics/utils.ts +++ b/packages/tracing/src/browser/metrics/utils.ts @@ -1,6 +1,6 @@ -import { Span, SpanContext } from '@sentry/types'; +import type { Span, SpanContext } from '@sentry/types'; -import { Transaction } from '../../transaction'; +import type { Transaction } from '../../transaction'; /** * Checks if a given value is a valid measurement value. diff --git a/packages/tracing/src/browser/request.ts b/packages/tracing/src/browser/request.ts index dbd041619699..ca6c15b0e96d 100644 --- a/packages/tracing/src/browser/request.ts +++ b/packages/tracing/src/browser/request.ts @@ -49,7 +49,7 @@ export interface RequestInstrumentationOptions { * * Default: (url: string) => true */ - shouldCreateSpanForRequest?(url: string): boolean; + shouldCreateSpanForRequest?(this: void, url: string): boolean; } /** Data returned from fetch callback */ diff --git a/packages/tracing/src/browser/router.ts b/packages/tracing/src/browser/router.ts index f0b50a0d014a..b66fdee30e9d 100644 --- a/packages/tracing/src/browser/router.ts +++ b/packages/tracing/src/browser/router.ts @@ -1,4 +1,4 @@ -import { Transaction, TransactionContext } from '@sentry/types'; +import type { Transaction, TransactionContext } from '@sentry/types'; import { addInstrumentationHandler, logger } from '@sentry/utils'; import { WINDOW } from './types'; diff --git a/packages/tracing/src/browser/web-vitals/getCLS.ts b/packages/tracing/src/browser/web-vitals/getCLS.ts index 0980eb5cf06d..3abddfac07cb 100644 --- a/packages/tracing/src/browser/web-vitals/getCLS.ts +++ b/packages/tracing/src/browser/web-vitals/getCLS.ts @@ -18,7 +18,7 @@ import { bindReporter } from './lib/bindReporter'; import { initMetric } from './lib/initMetric'; import { observe } from './lib/observe'; import { onHidden } from './lib/onHidden'; -import { CLSMetric, ReportCallback } from './types'; +import type { CLSMetric, ReportCallback } from './types'; /** * Calculates the [CLS](https://web.dev/cls/) value for the current page and diff --git a/packages/tracing/src/browser/web-vitals/getFID.ts b/packages/tracing/src/browser/web-vitals/getFID.ts index fcf5d529669c..fd19e112121a 100644 --- a/packages/tracing/src/browser/web-vitals/getFID.ts +++ b/packages/tracing/src/browser/web-vitals/getFID.ts @@ -19,7 +19,7 @@ import { getVisibilityWatcher } from './lib/getVisibilityWatcher'; import { initMetric } from './lib/initMetric'; import { observe } from './lib/observe'; import { onHidden } from './lib/onHidden'; -import { FIDMetric, PerformanceEventTiming, ReportCallback } from './types'; +import type { FIDMetric, PerformanceEventTiming, ReportCallback } from './types'; /** * Calculates the [FID](https://web.dev/fid/) value for the current page and diff --git a/packages/tracing/src/browser/web-vitals/getLCP.ts b/packages/tracing/src/browser/web-vitals/getLCP.ts index 01ddb2465ebc..bf834c07ce4e 100644 --- a/packages/tracing/src/browser/web-vitals/getLCP.ts +++ b/packages/tracing/src/browser/web-vitals/getLCP.ts @@ -20,7 +20,7 @@ import { getVisibilityWatcher } from './lib/getVisibilityWatcher'; import { initMetric } from './lib/initMetric'; import { observe } from './lib/observe'; import { onHidden } from './lib/onHidden'; -import { LCPMetric, ReportCallback } from './types'; +import type { LCPMetric, ReportCallback } from './types'; const reportedMetricIDs: Record = {}; diff --git a/packages/tracing/src/browser/web-vitals/lib/bindReporter.ts b/packages/tracing/src/browser/web-vitals/lib/bindReporter.ts index 6e304d747c95..79f3f874e2d8 100644 --- a/packages/tracing/src/browser/web-vitals/lib/bindReporter.ts +++ b/packages/tracing/src/browser/web-vitals/lib/bindReporter.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Metric, ReportCallback } from '../types'; +import type { Metric, ReportCallback } from '../types'; export const bindReporter = ( callback: ReportCallback, diff --git a/packages/tracing/src/browser/web-vitals/lib/getNavigationEntry.ts b/packages/tracing/src/browser/web-vitals/lib/getNavigationEntry.ts index a639eee2bb88..9aaa8939b6dc 100644 --- a/packages/tracing/src/browser/web-vitals/lib/getNavigationEntry.ts +++ b/packages/tracing/src/browser/web-vitals/lib/getNavigationEntry.ts @@ -15,7 +15,7 @@ */ import { WINDOW } from '../../types'; -import { NavigationTimingPolyfillEntry } from '../types'; +import type { NavigationTimingPolyfillEntry } from '../types'; const getNavigationEntryFromPerformanceTiming = (): NavigationTimingPolyfillEntry => { // eslint-disable-next-line deprecation/deprecation diff --git a/packages/tracing/src/browser/web-vitals/lib/initMetric.ts b/packages/tracing/src/browser/web-vitals/lib/initMetric.ts index c486dac6b24d..2fa5854fd6db 100644 --- a/packages/tracing/src/browser/web-vitals/lib/initMetric.ts +++ b/packages/tracing/src/browser/web-vitals/lib/initMetric.ts @@ -15,7 +15,7 @@ */ import { WINDOW } from '../../types'; -import { Metric } from '../types'; +import type { Metric } from '../types'; import { generateUniqueID } from './generateUniqueID'; import { getActivationStart } from './getActivationStart'; import { getNavigationEntry } from './getNavigationEntry'; diff --git a/packages/tracing/src/browser/web-vitals/lib/observe.ts b/packages/tracing/src/browser/web-vitals/lib/observe.ts index d3f6a0f14153..685105d5c7dc 100644 --- a/packages/tracing/src/browser/web-vitals/lib/observe.ts +++ b/packages/tracing/src/browser/web-vitals/lib/observe.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { FirstInputPolyfillEntry, NavigationTimingPolyfillEntry, PerformancePaintTiming } from '../types'; +import type { FirstInputPolyfillEntry, NavigationTimingPolyfillEntry, PerformancePaintTiming } from '../types'; export interface PerformanceEntryHandler { (entry: PerformanceEntry): void; diff --git a/packages/tracing/src/browser/web-vitals/types.ts b/packages/tracing/src/browser/web-vitals/types.ts index c78152748f97..ef8de70c12bb 100644 --- a/packages/tracing/src/browser/web-vitals/types.ts +++ b/packages/tracing/src/browser/web-vitals/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { FirstInputPolyfillCallback } from './types/polyfills'; +import type { FirstInputPolyfillCallback } from './types/polyfills'; export * from './types/base'; export * from './types/polyfills'; diff --git a/packages/tracing/src/browser/web-vitals/types/base.ts b/packages/tracing/src/browser/web-vitals/types/base.ts index 5194a8fd623b..5dc45f00558d 100644 --- a/packages/tracing/src/browser/web-vitals/types/base.ts +++ b/packages/tracing/src/browser/web-vitals/types/base.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { FirstInputPolyfillEntry, NavigationTimingPolyfillEntry } from './polyfills'; +import type { FirstInputPolyfillEntry, NavigationTimingPolyfillEntry } from './polyfills'; export interface Metric { /** diff --git a/packages/tracing/src/browser/web-vitals/types/cls.ts b/packages/tracing/src/browser/web-vitals/types/cls.ts index c4252dc31916..0c97a5dde9aa 100644 --- a/packages/tracing/src/browser/web-vitals/types/cls.ts +++ b/packages/tracing/src/browser/web-vitals/types/cls.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { LoadState, Metric, ReportCallback } from './base'; +import type { LoadState, Metric, ReportCallback } from './base'; /** * A CLS-specific version of the Metric object. diff --git a/packages/tracing/src/browser/web-vitals/types/fid.ts b/packages/tracing/src/browser/web-vitals/types/fid.ts index 324c7e25ff66..926f0675b90a 100644 --- a/packages/tracing/src/browser/web-vitals/types/fid.ts +++ b/packages/tracing/src/browser/web-vitals/types/fid.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { LoadState, Metric, ReportCallback } from './base'; -import { FirstInputPolyfillEntry } from './polyfills'; +import type { LoadState, Metric, ReportCallback } from './base'; +import type { FirstInputPolyfillEntry } from './polyfills'; /** * An FID-specific version of the Metric object. diff --git a/packages/tracing/src/browser/web-vitals/types/lcp.ts b/packages/tracing/src/browser/web-vitals/types/lcp.ts index 841ddca1e6de..c94573c1caaf 100644 --- a/packages/tracing/src/browser/web-vitals/types/lcp.ts +++ b/packages/tracing/src/browser/web-vitals/types/lcp.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { Metric, ReportCallback } from './base'; -import { NavigationTimingPolyfillEntry } from './polyfills'; +import type { Metric, ReportCallback } from './base'; +import type { NavigationTimingPolyfillEntry } from './polyfills'; /** * An LCP-specific version of the Metric object. diff --git a/packages/tracing/src/errors.ts b/packages/tracing/src/errors.ts index 5a406500ffee..1952fb75a915 100644 --- a/packages/tracing/src/errors.ts +++ b/packages/tracing/src/errors.ts @@ -1,6 +1,6 @@ import { addInstrumentationHandler, logger } from '@sentry/utils'; -import { SpanStatusType } from './span'; +import type { SpanStatusType } from './span'; import { getActiveTransaction } from './utils'; /** diff --git a/packages/tracing/src/hubextensions.ts b/packages/tracing/src/hubextensions.ts index c3cc2967be9d..9514cb00b321 100644 --- a/packages/tracing/src/hubextensions.ts +++ b/packages/tracing/src/hubextensions.ts @@ -1,5 +1,6 @@ -import { getMainCarrier, Hub } from '@sentry/core'; -import { +import type { Hub } from '@sentry/core'; +import { getMainCarrier } from '@sentry/core'; +import type { ClientOptions, CustomSamplingContext, Integration, diff --git a/packages/tracing/src/idletransaction.ts b/packages/tracing/src/idletransaction.ts index f509ff931a45..395b116481b1 100644 --- a/packages/tracing/src/idletransaction.ts +++ b/packages/tracing/src/idletransaction.ts @@ -1,9 +1,10 @@ /* eslint-disable max-lines */ -import { Hub } from '@sentry/core'; -import { TransactionContext } from '@sentry/types'; +import type { Hub } from '@sentry/core'; +import type { TransactionContext } from '@sentry/types'; import { logger, timestampWithMs } from '@sentry/utils'; -import { Span, SpanRecorder } from './span'; +import type { Span } from './span'; +import { SpanRecorder } from './span'; import { Transaction } from './transaction'; export const DEFAULT_IDLE_TIMEOUT = 1000; diff --git a/packages/tracing/src/integrations/node/apollo.ts b/packages/tracing/src/integrations/node/apollo.ts index 09442216deb7..41b136abff42 100644 --- a/packages/tracing/src/integrations/node/apollo.ts +++ b/packages/tracing/src/integrations/node/apollo.ts @@ -1,5 +1,5 @@ -import { Hub } from '@sentry/core'; -import { EventProcessor, Integration } from '@sentry/types'; +import type { Hub } from '@sentry/core'; +import type { EventProcessor, Integration } from '@sentry/types'; import { arrayify, fill, isThenable, loadModule, logger } from '@sentry/utils'; import { shouldDisableAutoInstrumentation } from './utils/node-utils'; diff --git a/packages/tracing/src/integrations/node/express.ts b/packages/tracing/src/integrations/node/express.ts index cb405664600a..8b3bcb52fcf4 100644 --- a/packages/tracing/src/integrations/node/express.ts +++ b/packages/tracing/src/integrations/node/express.ts @@ -1,5 +1,5 @@ /* eslint-disable max-lines */ -import { Hub, Integration, PolymorphicRequest, Transaction } from '@sentry/types'; +import type { Hub, Integration, PolymorphicRequest, Transaction } from '@sentry/types'; import { extractPathForTransaction, getNumberOfUrlSegments, isRegExp, logger } from '@sentry/utils'; import { shouldDisableAutoInstrumentation } from './utils/node-utils'; diff --git a/packages/tracing/src/integrations/node/graphql.ts b/packages/tracing/src/integrations/node/graphql.ts index 0bf267850cec..12f04b7c1e57 100644 --- a/packages/tracing/src/integrations/node/graphql.ts +++ b/packages/tracing/src/integrations/node/graphql.ts @@ -1,5 +1,5 @@ -import { Hub } from '@sentry/core'; -import { EventProcessor, Integration } from '@sentry/types'; +import type { Hub } from '@sentry/core'; +import type { EventProcessor, Integration } from '@sentry/types'; import { fill, isThenable, loadModule, logger } from '@sentry/utils'; import { shouldDisableAutoInstrumentation } from './utils/node-utils'; diff --git a/packages/tracing/src/integrations/node/mongo.ts b/packages/tracing/src/integrations/node/mongo.ts index b2933ba37022..37335358c82e 100644 --- a/packages/tracing/src/integrations/node/mongo.ts +++ b/packages/tracing/src/integrations/node/mongo.ts @@ -1,5 +1,5 @@ -import { Hub } from '@sentry/core'; -import { EventProcessor, Integration, SpanContext } from '@sentry/types'; +import type { Hub } from '@sentry/core'; +import type { EventProcessor, Integration, SpanContext } from '@sentry/types'; import { fill, isThenable, loadModule, logger } from '@sentry/utils'; import { shouldDisableAutoInstrumentation } from './utils/node-utils'; diff --git a/packages/tracing/src/integrations/node/mysql.ts b/packages/tracing/src/integrations/node/mysql.ts index 9303522ea260..6e6a80ac59a6 100644 --- a/packages/tracing/src/integrations/node/mysql.ts +++ b/packages/tracing/src/integrations/node/mysql.ts @@ -1,5 +1,5 @@ -import { Hub } from '@sentry/core'; -import { EventProcessor, Integration } from '@sentry/types'; +import type { Hub } from '@sentry/core'; +import type { EventProcessor, Integration } from '@sentry/types'; import { fill, loadModule, logger } from '@sentry/utils'; import { shouldDisableAutoInstrumentation } from './utils/node-utils'; diff --git a/packages/tracing/src/integrations/node/postgres.ts b/packages/tracing/src/integrations/node/postgres.ts index 5883ef94527f..41ad31b20660 100644 --- a/packages/tracing/src/integrations/node/postgres.ts +++ b/packages/tracing/src/integrations/node/postgres.ts @@ -1,5 +1,5 @@ -import { Hub } from '@sentry/core'; -import { EventProcessor, Integration } from '@sentry/types'; +import type { Hub } from '@sentry/core'; +import type { EventProcessor, Integration } from '@sentry/types'; import { fill, isThenable, loadModule, logger } from '@sentry/utils'; import { shouldDisableAutoInstrumentation } from './utils/node-utils'; diff --git a/packages/tracing/src/integrations/node/prisma.ts b/packages/tracing/src/integrations/node/prisma.ts index 371052217911..2215cf2a817a 100644 --- a/packages/tracing/src/integrations/node/prisma.ts +++ b/packages/tracing/src/integrations/node/prisma.ts @@ -1,5 +1,5 @@ -import { Hub } from '@sentry/core'; -import { EventProcessor, Integration } from '@sentry/types'; +import type { Hub } from '@sentry/core'; +import type { EventProcessor, Integration } from '@sentry/types'; import { isThenable, logger } from '@sentry/utils'; import { shouldDisableAutoInstrumentation } from './utils/node-utils'; diff --git a/packages/tracing/src/integrations/node/utils/node-utils.ts b/packages/tracing/src/integrations/node/utils/node-utils.ts index 9cb6970c9ebc..11f8f2430b61 100644 --- a/packages/tracing/src/integrations/node/utils/node-utils.ts +++ b/packages/tracing/src/integrations/node/utils/node-utils.ts @@ -1,4 +1,4 @@ -import { Hub } from '@sentry/types'; +import type { Hub } from '@sentry/types'; /** * Check if Sentry auto-instrumentation should be disabled. diff --git a/packages/tracing/src/span.ts b/packages/tracing/src/span.ts index 43fbef08f623..db11bb931df7 100644 --- a/packages/tracing/src/span.ts +++ b/packages/tracing/src/span.ts @@ -1,5 +1,12 @@ /* eslint-disable max-lines */ -import { Instrumenter, Primitive, Span as SpanInterface, SpanContext, Transaction } from '@sentry/types'; +import type { + Instrumenter, + Primitive, + Span as SpanInterface, + SpanContext, + TraceContext, + Transaction, +} from '@sentry/types'; import { dropUndefinedKeys, logger, timestampWithMs, uuid4 } from '@sentry/utils'; /** @@ -305,17 +312,7 @@ export class Span implements SpanInterface { /** * @inheritDoc */ - public getTraceContext(): { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - data?: { [key: string]: any }; - description?: string; - op?: string; - parent_span_id?: string; - span_id: string; - status?: string; - tags?: { [key: string]: Primitive }; - trace_id: string; - } { + public getTraceContext(): TraceContext { return dropUndefinedKeys({ data: Object.keys(this.data).length > 0 ? this.data : undefined, description: this.description, diff --git a/packages/tracing/src/transaction.ts b/packages/tracing/src/transaction.ts index 78a33fd17313..b93d52b81c38 100644 --- a/packages/tracing/src/transaction.ts +++ b/packages/tracing/src/transaction.ts @@ -1,5 +1,6 @@ -import { getCurrentHub, Hub } from '@sentry/core'; -import { +import type { Hub } from '@sentry/core'; +import { getCurrentHub } from '@sentry/core'; +import type { Context, Contexts, DynamicSamplingContext, diff --git a/packages/tracing/src/utils.ts b/packages/tracing/src/utils.ts index d79a4f91a2df..66cba77843b7 100644 --- a/packages/tracing/src/utils.ts +++ b/packages/tracing/src/utils.ts @@ -1,5 +1,6 @@ -import { getCurrentHub, Hub } from '@sentry/core'; -import { Options, Transaction } from '@sentry/types'; +import type { Hub } from '@sentry/core'; +import { getCurrentHub } from '@sentry/core'; +import type { Options, Transaction } from '@sentry/types'; /** * The `extractTraceparentData` function and `TRACEPARENT_REGEXP` constant used diff --git a/packages/tracing/test/browser/browsertracing.test.ts b/packages/tracing/test/browser/browsertracing.test.ts index 3840ea75d6d6..a00025811874 100644 --- a/packages/tracing/test/browser/browsertracing.test.ts +++ b/packages/tracing/test/browser/browsertracing.test.ts @@ -1,19 +1,16 @@ import { BrowserClient, WINDOW } from '@sentry/browser'; import { Hub, makeMain } from '@sentry/core'; import type { BaseTransportOptions, ClientOptions, DsnComponents } from '@sentry/types'; -import { InstrumentHandlerCallback, InstrumentHandlerType } from '@sentry/utils'; +import type { InstrumentHandlerCallback, InstrumentHandlerType } from '@sentry/utils'; import { JSDOM } from 'jsdom'; -import { BrowserTracing, BrowserTracingOptions, getMetaContent } from '../../src/browser/browsertracing'; +import type { BrowserTracingOptions } from '../../src/browser/browsertracing'; +import { BrowserTracing, getMetaContent } from '../../src/browser/browsertracing'; import { defaultRequestInstrumentationOptions } from '../../src/browser/request'; import { instrumentRoutingWithDefaults } from '../../src/browser/router'; import * as hubExtensions from '../../src/hubextensions'; -import { - DEFAULT_FINAL_TIMEOUT, - DEFAULT_HEARTBEAT_INTERVAL, - DEFAULT_IDLE_TIMEOUT, - IdleTransaction, -} from '../../src/idletransaction'; +import type { IdleTransaction } from '../../src/idletransaction'; +import { DEFAULT_FINAL_TIMEOUT, DEFAULT_HEARTBEAT_INTERVAL, DEFAULT_IDLE_TIMEOUT } from '../../src/idletransaction'; import { getActiveTransaction } from '../../src/utils'; import { getDefaultBrowserClientOptions } from '../testutils'; diff --git a/packages/tracing/test/browser/metrics/index.test.ts b/packages/tracing/test/browser/metrics/index.test.ts index 2bda341e3b29..4820a70a5c9b 100644 --- a/packages/tracing/test/browser/metrics/index.test.ts +++ b/packages/tracing/test/browser/metrics/index.test.ts @@ -1,5 +1,6 @@ import { Transaction } from '../../../src'; -import { _addMeasureSpans, _addResourceSpans, ResourceEntry } from '../../../src/browser/metrics'; +import type { ResourceEntry } from '../../../src/browser/metrics'; +import { _addMeasureSpans, _addResourceSpans } from '../../../src/browser/metrics'; describe('_addMeasureSpans', () => { const transaction = new Transaction({ op: 'pageload', name: '/' }); diff --git a/packages/tracing/test/browser/request.test.ts b/packages/tracing/test/browser/request.test.ts index 732d6ea9b4f8..e2d60aa9edb2 100644 --- a/packages/tracing/test/browser/request.test.ts +++ b/packages/tracing/test/browser/request.test.ts @@ -2,15 +2,10 @@ import { BrowserClient } from '@sentry/browser'; import { Hub, makeMain } from '@sentry/core'; import * as utils from '@sentry/utils'; -import { Span, spanStatusfromHttpCode, Transaction } from '../../src'; -import { - fetchCallback, - FetchData, - instrumentOutgoingRequests, - shouldAttachHeaders, - xhrCallback, - XHRData, -} from '../../src/browser/request'; +import type { Transaction } from '../../src'; +import { Span, spanStatusfromHttpCode } from '../../src'; +import type { FetchData, XHRData } from '../../src/browser/request'; +import { fetchCallback, instrumentOutgoingRequests, shouldAttachHeaders, xhrCallback } from '../../src/browser/request'; import { addExtensionMethods } from '../../src/hubextensions'; import * as tracingUtils from '../../src/utils'; import { getDefaultBrowserClientOptions } from '../testutils'; diff --git a/packages/tracing/test/browser/router.test.ts b/packages/tracing/test/browser/router.test.ts index f0e3fec29084..65ce7e90af48 100644 --- a/packages/tracing/test/browser/router.test.ts +++ b/packages/tracing/test/browser/router.test.ts @@ -1,4 +1,4 @@ -import { InstrumentHandlerCallback, InstrumentHandlerType } from '@sentry/utils'; +import type { InstrumentHandlerCallback, InstrumentHandlerType } from '@sentry/utils'; import { JSDOM } from 'jsdom'; import { instrumentRoutingWithDefaults } from '../../src/browser/router'; diff --git a/packages/tracing/test/errors.test.ts b/packages/tracing/test/errors.test.ts index 7942bf2b85e9..554ee3b3d8c7 100644 --- a/packages/tracing/test/errors.test.ts +++ b/packages/tracing/test/errors.test.ts @@ -1,6 +1,6 @@ import { BrowserClient } from '@sentry/browser'; import { Hub, makeMain } from '@sentry/core'; -import { InstrumentHandlerCallback, InstrumentHandlerType } from '@sentry/utils'; +import type { InstrumentHandlerCallback, InstrumentHandlerType } from '@sentry/utils'; import { registerErrorInstrumentation } from '../src/errors'; import { _addTracingExtensions } from '../src/hubextensions'; diff --git a/packages/tracing/test/span.test.ts b/packages/tracing/test/span.test.ts index 378fac467069..6de0d79b7125 100644 --- a/packages/tracing/test/span.test.ts +++ b/packages/tracing/test/span.test.ts @@ -1,6 +1,6 @@ import { BrowserClient } from '@sentry/browser'; import { Hub, makeMain, Scope } from '@sentry/core'; -import { BaseTransportOptions, ClientOptions, TransactionSource } from '@sentry/types'; +import type { BaseTransportOptions, ClientOptions, TransactionSource } from '@sentry/types'; import { Span, Transaction } from '../src'; import { TRACEPARENT_REGEXP } from '../src/utils'; diff --git a/packages/tracing/test/testutils.ts b/packages/tracing/test/testutils.ts index 4ddd042a1240..071ecdf5111a 100644 --- a/packages/tracing/test/testutils.ts +++ b/packages/tracing/test/testutils.ts @@ -1,5 +1,5 @@ import { createTransport } from '@sentry/browser'; -import { Client, ClientOptions } from '@sentry/types'; +import type { Client, ClientOptions } from '@sentry/types'; import { GLOBAL_OBJ, resolvedSyncPromise } from '@sentry/utils'; import { JSDOM } from 'jsdom'; diff --git a/packages/types/package.json b/packages/types/package.json index 3c4b5f250412..6349b90812c6 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/types", - "version": "7.29.0", + "version": "7.30.0", "description": "Types for all Sentry JavaScript SDKs", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/types", @@ -16,15 +16,15 @@ "access": "public" }, "scripts": { - "build": "run-p build:rollup build:types", + "build": "run-p build:transpile build:types", "build:dev": "run-s build", - "build:rollup": "rollup -c rollup.npm.config.js", + "build:transpile": "rollup -c rollup.npm.config.js", "build:types": "tsc -p tsconfig.types.json", - "build:watch": "run-p build:rollup:watch build:types:watch", + "build:watch": "run-p build:transpile:watch build:types:watch", "build:dev:watch": "run-s build:watch", - "build:rollup:watch": "rollup -c rollup.npm.config.js --watch", + "build:transpile:watch": "rollup -c rollup.npm.config.js --watch", "build:types:watch": "tsc -p tsconfig.types.json --watch", - "build:npm": "ts-node ../../scripts/prepack.ts && npm pack ./build", + "build:tarball": "ts-node ../../scripts/prepack.ts && npm pack ./build", "clean": "rimraf build sentry-types-*.tgz", "lint": "run-s lint:prettier lint:eslint", "lint:eslint": "eslint . --cache --cache-location '../../eslintcache/' --format stylish", diff --git a/packages/types/src/breadcrumb.ts b/packages/types/src/breadcrumb.ts index 34fe2dfcd16a..b8e2552a2f34 100644 --- a/packages/types/src/breadcrumb.ts +++ b/packages/types/src/breadcrumb.ts @@ -1,4 +1,4 @@ -import { Severity, SeverityLevel } from './severity'; +import type { Severity, SeverityLevel } from './severity'; /** JSDoc */ export interface Breadcrumb { diff --git a/packages/types/src/client.ts b/packages/types/src/client.ts index 6dee7e5c9ebb..28d23025ce84 100644 --- a/packages/types/src/client.ts +++ b/packages/types/src/client.ts @@ -1,14 +1,14 @@ -import { EventDropReason } from './clientreport'; -import { DataCategory } from './datacategory'; -import { DsnComponents } from './dsn'; -import { Event, EventHint } from './event'; -import { Integration, IntegrationClass } from './integration'; -import { ClientOptions } from './options'; -import { Scope } from './scope'; -import { SdkMetadata } from './sdkmetadata'; -import { Session, SessionAggregates } from './session'; -import { Severity, SeverityLevel } from './severity'; -import { Transport } from './transport'; +import type { EventDropReason } from './clientreport'; +import type { DataCategory } from './datacategory'; +import type { DsnComponents } from './dsn'; +import type { Event, EventHint } from './event'; +import type { Integration, IntegrationClass } from './integration'; +import type { ClientOptions } from './options'; +import type { Scope } from './scope'; +import type { SdkMetadata } from './sdkmetadata'; +import type { Session, SessionAggregates } from './session'; +import type { Severity, SeverityLevel } from './severity'; +import type { Transport } from './transport'; /** * User-Facing Sentry SDK Client. @@ -108,6 +108,16 @@ export interface Client { /** Returns the client's instance of the given integration class, it any. */ getIntegration(integration: IntegrationClass): T | null; + /** + * Add an integration to the client. + * This can be used to e.g. lazy load integrations. + * In most cases, this should not be necessary, and you're better off just passing the integrations via `integrations: []` at initialization time. + * However, if you find the need to conditionally load & add an integration, you can use `addIntegration` to do so. + * + * TODO (v8): Make this a required method. + * */ + addIntegration?(integration: Integration): void; + /** This is an internal function to setup all integrations that should run on the client */ setupIntegrations(): void; diff --git a/packages/types/src/clientreport.ts b/packages/types/src/clientreport.ts index 79b3c2a4454c..7b4e181d1102 100644 --- a/packages/types/src/clientreport.ts +++ b/packages/types/src/clientreport.ts @@ -1,4 +1,4 @@ -import { DataCategory } from './datacategory'; +import type { DataCategory } from './datacategory'; export type EventDropReason = | 'before_send' diff --git a/packages/types/src/context.ts b/packages/types/src/context.ts index 4b6a08585273..bab969899cea 100644 --- a/packages/types/src/context.ts +++ b/packages/types/src/context.ts @@ -1,3 +1,5 @@ +import type { Primitive } from './misc'; + export type Context = Record; export interface Contexts extends Record { @@ -5,6 +7,7 @@ export interface Contexts extends Record { device?: DeviceContext; os?: OsContext; culture?: CultureContext; + response?: ResponseContext; } export interface AppContext extends Record { @@ -70,3 +73,22 @@ export interface CultureContext extends Record { is_24_hour_format?: boolean; timezone?: string; } + +export interface ResponseContext extends Record { + type?: string; + cookies?: string[][] | Record; + headers?: Record; + status_code?: number; + body_size?: number; // in bytes +} + +export interface TraceContext extends Record { + data?: { [key: string]: any }; + description?: string; + op?: string; + parent_span_id?: string; + span_id: string; + status?: string; + tags?: { [key: string]: Primitive }; + trace_id: string; +} diff --git a/packages/types/src/datacategory.ts b/packages/types/src/datacategory.ts index a462957b3ccd..0968cefc752b 100644 --- a/packages/types/src/datacategory.ts +++ b/packages/types/src/datacategory.ts @@ -10,6 +10,8 @@ export type DataCategory = | 'error' // Transaction type event | 'transaction' + // Replay type event + | 'replay_event' // Events with `event_type` csp, hpkp, expectct, expectstaple | 'security' // Attachment bytes stored (unused for rate limiting diff --git a/packages/types/src/envelope.ts b/packages/types/src/envelope.ts index 74cb2764b7e3..60d67b89d0da 100644 --- a/packages/types/src/envelope.ts +++ b/packages/types/src/envelope.ts @@ -1,11 +1,11 @@ -import { ClientReport } from './clientreport'; -import { DsnComponents } from './dsn'; -import { Event } from './event'; -import { ReplayEvent, ReplayRecordingData } from './replay'; -import { SdkInfo } from './sdkinfo'; -import { Session, SessionAggregates } from './session'; -import { Transaction } from './transaction'; -import { UserFeedback } from './user'; +import type { ClientReport } from './clientreport'; +import type { DsnComponents } from './dsn'; +import type { Event } from './event'; +import type { ReplayEvent, ReplayRecordingData } from './replay'; +import type { SdkInfo } from './sdkinfo'; +import type { Session, SessionAggregates } from './session'; +import type { Transaction } from './transaction'; +import type { UserFeedback } from './user'; // Based on: https://develop.sentry.dev/sdk/envelopes/ @@ -44,7 +44,7 @@ export type BaseEnvelopeItemHeaders = { length?: number; }; -type BaseEnvelopeItem = [ItemHeader & BaseEnvelopeItemHeaders, P]; // P is for payload +type BaseEnvelopeItem = [ItemHeader & BaseEnvelopeItemHeaders, P]; // P is for payload type BaseEnvelope = [ EnvelopeHeader & BaseEnvelopeHeaders, diff --git a/packages/types/src/event.ts b/packages/types/src/event.ts index 3c8b27c49f10..86280fc7e65b 100644 --- a/packages/types/src/event.ts +++ b/packages/types/src/event.ts @@ -1,19 +1,19 @@ -import { Attachment } from './attachment'; -import { Breadcrumb } from './breadcrumb'; -import { Contexts } from './context'; -import { DebugMeta } from './debugMeta'; -import { Exception } from './exception'; -import { Extras } from './extra'; -import { Measurements } from './measurement'; -import { Primitive } from './misc'; -import { Request } from './request'; -import { CaptureContext } from './scope'; -import { SdkInfo } from './sdkinfo'; -import { Severity, SeverityLevel } from './severity'; -import { Span } from './span'; -import { Thread } from './thread'; -import { TransactionNameChange, TransactionSource } from './transaction'; -import { User } from './user'; +import type { Attachment } from './attachment'; +import type { Breadcrumb } from './breadcrumb'; +import type { Contexts } from './context'; +import type { DebugMeta } from './debugMeta'; +import type { Exception } from './exception'; +import type { Extras } from './extra'; +import type { Measurements } from './measurement'; +import type { Primitive } from './misc'; +import type { Request } from './request'; +import type { CaptureContext } from './scope'; +import type { SdkInfo } from './sdkinfo'; +import type { Severity, SeverityLevel } from './severity'; +import type { Span } from './span'; +import type { Thread } from './thread'; +import type { TransactionNameChange, TransactionSource } from './transaction'; +import type { User } from './user'; /** JSDoc */ export interface Event { @@ -63,7 +63,7 @@ export interface Event { * Note that `ErrorEvent`s do not have a type (hence its undefined), * while all other events are required to have one. */ -export type EventType = 'transaction' | 'profile' | undefined; +export type EventType = 'transaction' | 'profile' | 'replay_event' | undefined; export interface ErrorEvent extends Event { type: undefined; diff --git a/packages/types/src/eventprocessor.ts b/packages/types/src/eventprocessor.ts index 5be90a755367..60a983fa0fdc 100644 --- a/packages/types/src/eventprocessor.ts +++ b/packages/types/src/eventprocessor.ts @@ -1,4 +1,4 @@ -import { Event, EventHint } from './event'; +import type { Event, EventHint } from './event'; /** * Event processors are used to change the event before it will be send. @@ -7,6 +7,6 @@ import { Event, EventHint } from './event'; * Event processing will be deferred until your Promise is resolved. */ export interface EventProcessor { - id?: string; // This field can't be named "name" because functions already have this field natively (event: Event, hint: EventHint): PromiseLike | Event | null; + id?: string; // This field can't be named "name" because functions already have this field natively } diff --git a/packages/types/src/exception.ts b/packages/types/src/exception.ts index 4b98f3a326d3..a74adf6c1603 100644 --- a/packages/types/src/exception.ts +++ b/packages/types/src/exception.ts @@ -1,5 +1,5 @@ -import { Mechanism } from './mechanism'; -import { Stacktrace } from './stacktrace'; +import type { Mechanism } from './mechanism'; +import type { Stacktrace } from './stacktrace'; /** JSDoc */ export interface Exception { diff --git a/packages/types/src/hub.ts b/packages/types/src/hub.ts index a34d4372e08d..35a3f9f4a82e 100644 --- a/packages/types/src/hub.ts +++ b/packages/types/src/hub.ts @@ -1,14 +1,14 @@ -import { Breadcrumb, BreadcrumbHint } from './breadcrumb'; -import { Client } from './client'; -import { Event, EventHint } from './event'; -import { Extra, Extras } from './extra'; -import { Integration, IntegrationClass } from './integration'; -import { Primitive } from './misc'; -import { Scope } from './scope'; -import { Session } from './session'; -import { Severity, SeverityLevel } from './severity'; -import { CustomSamplingContext, Transaction, TransactionContext } from './transaction'; -import { User } from './user'; +import type { Breadcrumb, BreadcrumbHint } from './breadcrumb'; +import type { Client } from './client'; +import type { Event, EventHint } from './event'; +import type { Extra, Extras } from './extra'; +import type { Integration, IntegrationClass } from './integration'; +import type { Primitive } from './misc'; +import type { Scope } from './scope'; +import type { Session } from './session'; +import type { Severity, SeverityLevel } from './severity'; +import type { CustomSamplingContext, Transaction, TransactionContext } from './transaction'; +import type { User } from './user'; /** * Internal class used to make sure we always have the latest internal functions @@ -227,4 +227,10 @@ export interface Hub { * @param endSession If set the session will be marked as exited and removed from the scope */ captureSession(endSession?: boolean): void; + + /** + * Returns if default PII should be sent to Sentry and propagated in ourgoing requests + * when Tracing is used. + */ + shouldSendDefaultPii(): boolean; } diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 56d5379f9e34..0b52fac74e02 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -2,7 +2,7 @@ export type { Attachment } from './attachment'; export type { Breadcrumb, BreadcrumbHint } from './breadcrumb'; export type { Client } from './client'; export type { ClientReport, Outcome, EventDropReason } from './clientreport'; -export type { Context, Contexts, DeviceContext, OsContext, AppContext, CultureContext } from './context'; +export type { Context, Contexts, DeviceContext, OsContext, AppContext, CultureContext, TraceContext } from './context'; export type { DataCategory } from './datacategory'; export type { DsnComponents, DsnLike, DsnProtocol } from './dsn'; export type { DebugImage, DebugImageType, DebugMeta } from './debugMeta'; @@ -25,7 +25,7 @@ export type { UserFeedbackItem, } from './envelope'; export type { ExtendedError } from './error'; -export type { Event, EventHint, ErrorEvent, TransactionEvent } from './event'; +export type { Event, EventHint, EventType, ErrorEvent, TransactionEvent } from './event'; export type { EventProcessor } from './eventprocessor'; export type { Exception } from './exception'; export type { Extra, Extras } from './extra'; @@ -40,7 +40,7 @@ export type { ExtractedNodeRequestData, HttpHeaderValue, Primitive, WorkerLocati export type { ClientOptions, Options } from './options'; export type { Package } from './package'; export type { PolymorphicEvent, PolymorphicRequest } from './polymorphics'; -export type { ReplayEvent, ReplayRecordingData } from './replay'; +export type { ReplayEvent, ReplayRecordingData, ReplayRecordingMode } from './replay'; export type { QueryParams, Request } from './request'; export type { Runtime } from './runtime'; export type { CaptureContext, Scope, ScopeContext } from './scope'; diff --git a/packages/types/src/integration.ts b/packages/types/src/integration.ts index 251c135973fd..c7672effc185 100644 --- a/packages/types/src/integration.ts +++ b/packages/types/src/integration.ts @@ -1,5 +1,5 @@ -import { EventProcessor } from './eventprocessor'; -import { Hub } from './hub'; +import type { EventProcessor } from './eventprocessor'; +import type { Hub } from './hub'; /** Integration Class Interface */ export interface IntegrationClass { diff --git a/packages/types/src/misc.ts b/packages/types/src/misc.ts index 3476befa78c6..af9ed8fc6bd7 100644 --- a/packages/types/src/misc.ts +++ b/packages/types/src/misc.ts @@ -1,4 +1,4 @@ -import { QueryParams } from './request'; +import type { QueryParams } from './request'; /** * Data extracted from an incoming request to a node server diff --git a/packages/types/src/options.ts b/packages/types/src/options.ts index f8f8f461543b..ca53b19f607a 100644 --- a/packages/types/src/options.ts +++ b/packages/types/src/options.ts @@ -1,12 +1,12 @@ -import { Breadcrumb, BreadcrumbHint } from './breadcrumb'; -import { ErrorEvent, Event, EventHint, TransactionEvent } from './event'; -import { Instrumenter } from './instrumenter'; -import { Integration } from './integration'; -import { CaptureContext } from './scope'; -import { SdkMetadata } from './sdkmetadata'; -import { StackLineParser, StackParser } from './stacktrace'; -import { SamplingContext } from './transaction'; -import { BaseTransportOptions, Transport } from './transport'; +import type { Breadcrumb, BreadcrumbHint } from './breadcrumb'; +import type { ErrorEvent, Event, EventHint, TransactionEvent } from './event'; +import type { Instrumenter } from './instrumenter'; +import type { Integration } from './integration'; +import type { CaptureContext } from './scope'; +import type { SdkMetadata } from './sdkmetadata'; +import type { StackLineParser, StackParser } from './stacktrace'; +import type { SamplingContext } from './transaction'; +import type { BaseTransportOptions, Transport } from './transport'; export interface ClientOptions { /** @@ -222,7 +222,7 @@ export interface ClientOptions number | boolean; - // TODO v8: Narrow the response type to `ErrorEvent` - this is technically a breaking change. + // TODO (v8): Narrow the response type to `ErrorEvent` - this is technically a breaking change. /** * An event-processing callback for error and message events, guaranteed to be invoked after all other event * processors, which allows an event to be modified or dropped. @@ -236,7 +236,7 @@ export interface ClientOptions PromiseLike | Event | null; - // TODO v8: Narrow the response type to `TransactionEvent` - this is technically a breaking change. + // TODO (v8): Narrow the response type to `TransactionEvent` - this is technically a breaking change. /** * An event-processing callback for transaction events, guaranteed to be invoked after all other event * processors. This allows an event to be modified or dropped before it's sent. diff --git a/packages/types/src/replay.ts b/packages/types/src/replay.ts index 1bec7202ecdc..79bbce4c6eb4 100644 --- a/packages/types/src/replay.ts +++ b/packages/types/src/replay.ts @@ -1,4 +1,4 @@ -import { Event } from './event'; +import type { Event } from './event'; /** * NOTE: These types are still considered Beta and subject to change. @@ -10,6 +10,7 @@ export interface ReplayEvent extends Event { trace_ids: string[]; replay_id: string; segment_id: number; + replay_type: ReplayRecordingMode; } /** @@ -17,3 +18,9 @@ export interface ReplayEvent extends Event { * @hidden */ export type ReplayRecordingData = string | Uint8Array; + +/** + * NOTE: These types are still considered Beta and subject to change. + * @hidden + */ +export type ReplayRecordingMode = 'session' | 'error'; diff --git a/packages/types/src/scope.ts b/packages/types/src/scope.ts index 1b7be6f5b938..4ed11b287421 100644 --- a/packages/types/src/scope.ts +++ b/packages/types/src/scope.ts @@ -1,14 +1,14 @@ -import { Attachment } from './attachment'; -import { Breadcrumb } from './breadcrumb'; -import { Context, Contexts } from './context'; -import { EventProcessor } from './eventprocessor'; -import { Extra, Extras } from './extra'; -import { Primitive } from './misc'; -import { RequestSession, Session } from './session'; -import { Severity, SeverityLevel } from './severity'; -import { Span } from './span'; -import { Transaction } from './transaction'; -import { User } from './user'; +import type { Attachment } from './attachment'; +import type { Breadcrumb } from './breadcrumb'; +import type { Context, Contexts } from './context'; +import type { EventProcessor } from './eventprocessor'; +import type { Extra, Extras } from './extra'; +import type { Primitive } from './misc'; +import type { RequestSession, Session } from './session'; +import type { Severity, SeverityLevel } from './severity'; +import type { Span } from './span'; +import type { Transaction } from './transaction'; +import type { User } from './user'; /** JSDocs */ export type CaptureContext = Scope | Partial | ((scope: Scope) => Scope); diff --git a/packages/types/src/sdkinfo.ts b/packages/types/src/sdkinfo.ts index 0a839b0c7c95..b287ef0674f5 100644 --- a/packages/types/src/sdkinfo.ts +++ b/packages/types/src/sdkinfo.ts @@ -1,4 +1,4 @@ -import { Package } from './package'; +import type { Package } from './package'; export interface SdkInfo { name?: string; diff --git a/packages/types/src/sdkmetadata.ts b/packages/types/src/sdkmetadata.ts index d65e989cb4c1..40df8046fe3d 100644 --- a/packages/types/src/sdkmetadata.ts +++ b/packages/types/src/sdkmetadata.ts @@ -1,4 +1,4 @@ -import { SdkInfo } from './sdkinfo'; +import type { SdkInfo } from './sdkinfo'; export interface SdkMetadata { sdk?: SdkInfo; diff --git a/packages/types/src/session.ts b/packages/types/src/session.ts index 35d5e0840a3e..3d62a8085444 100644 --- a/packages/types/src/session.ts +++ b/packages/types/src/session.ts @@ -1,4 +1,4 @@ -import { User } from './user'; +import type { User } from './user'; export interface RequestSession { status?: RequestSessionStatus; diff --git a/packages/types/src/span.ts b/packages/types/src/span.ts index 2b0a91ed9ee6..756a6808910c 100644 --- a/packages/types/src/span.ts +++ b/packages/types/src/span.ts @@ -1,6 +1,6 @@ -import { Instrumenter } from './instrumenter'; -import { Primitive } from './misc'; -import { Transaction } from './transaction'; +import type { Instrumenter } from './instrumenter'; +import type { Primitive } from './misc'; +import type { Transaction } from './transaction'; /** Interface holding all properties that can be set on a Span on creation. */ export interface SpanContext { diff --git a/packages/types/src/stacktrace.ts b/packages/types/src/stacktrace.ts index ae2f350f716b..c27cbf00a12c 100644 --- a/packages/types/src/stacktrace.ts +++ b/packages/types/src/stacktrace.ts @@ -1,4 +1,4 @@ -import { StackFrame } from './stackframe'; +import type { StackFrame } from './stackframe'; /** JSDoc */ export interface Stacktrace { diff --git a/packages/types/src/thread.ts b/packages/types/src/thread.ts index 5cc1a2b63891..1cfad253a299 100644 --- a/packages/types/src/thread.ts +++ b/packages/types/src/thread.ts @@ -1,4 +1,4 @@ -import { Stacktrace } from './stacktrace'; +import type { Stacktrace } from './stacktrace'; /** JSDoc */ export interface Thread { diff --git a/packages/types/src/transaction.ts b/packages/types/src/transaction.ts index ae2dd54e2629..e60132a8e550 100644 --- a/packages/types/src/transaction.ts +++ b/packages/types/src/transaction.ts @@ -1,10 +1,10 @@ -import { Context } from './context'; -import { DynamicSamplingContext } from './envelope'; -import { Instrumenter } from './instrumenter'; -import { MeasurementUnit } from './measurement'; -import { ExtractedNodeRequestData, Primitive, WorkerLocation } from './misc'; -import { PolymorphicRequest } from './polymorphics'; -import { Span, SpanContext } from './span'; +import type { Context } from './context'; +import type { DynamicSamplingContext } from './envelope'; +import type { Instrumenter } from './instrumenter'; +import type { MeasurementUnit } from './measurement'; +import type { ExtractedNodeRequestData, Primitive, WorkerLocation } from './misc'; +import type { PolymorphicRequest } from './polymorphics'; +import type { Span, SpanContext } from './span'; /** * Interface holding Transaction-specific properties diff --git a/packages/types/src/transport.ts b/packages/types/src/transport.ts index b9beb2148f20..05638b67228e 100644 --- a/packages/types/src/transport.ts +++ b/packages/types/src/transport.ts @@ -1,6 +1,6 @@ -import { Client } from './client'; -import { Envelope } from './envelope'; -import { TextEncoderInternal } from './textencoder'; +import type { Client } from './client'; +import type { Envelope } from './envelope'; +import type { TextEncoderInternal } from './textencoder'; export type TransportRequest = { body: string | Uint8Array; @@ -29,7 +29,8 @@ export interface BaseTransportOptions extends InternalBaseTransportOptions { } export interface Transport { - send(request: Envelope): PromiseLike; + // TODO (v8) Remove void from return as it was only retained to avoid a breaking change + send(request: Envelope): PromiseLike; flush(timeout?: number): PromiseLike; } diff --git a/packages/typescript/package.json b/packages/typescript/package.json index ed9ad431eb84..b4e8dc32c416 100644 --- a/packages/typescript/package.json +++ b/packages/typescript/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/typescript", - "version": "7.29.0", + "version": "7.30.0", "description": "Typescript configuration used at Sentry", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/typescript", @@ -15,7 +15,7 @@ }, "scripts": { "clean": "yarn rimraf sentry-internal-typescript-*.tgz", - "build:npm": "npm pack" + "build:tarball": "npm pack" }, "volta": { "extends": "../../package.json" diff --git a/packages/typescript/tsconfig.json b/packages/typescript/tsconfig.json index 1eb5750178ce..107a5888e432 100644 --- a/packages/typescript/tsconfig.json +++ b/packages/typescript/tsconfig.json @@ -13,8 +13,8 @@ "noFallthroughCasesInSwitch": true, "noImplicitReturns": true, "noImplicitUseStrict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, + "noUnusedLocals": false, + "noUnusedParameters": false, "preserveWatchOutput": true, "sourceMap": true, "strict": true, diff --git a/packages/utils/package.json b/packages/utils/package.json index 5211adfc448a..5576658642e7 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/utils", - "version": "7.29.0", + "version": "7.30.0", "description": "Utilities for all Sentry JavaScript SDKs", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/utils", @@ -16,7 +16,7 @@ "access": "public" }, "dependencies": { - "@sentry/types": "7.29.0", + "@sentry/types": "7.30.0", "tslib": "^1.9.3" }, "devDependencies": { @@ -25,15 +25,16 @@ "chai": "^4.1.2" }, "scripts": { - "build": "run-p build:rollup build:types", + "build": "run-p build:transpile build:types", "build:dev": "run-s build", - "build:rollup": "yarn ts-node scripts/buildRollup.ts", + "build:transpile": "yarn ts-node scripts/buildRollup.ts", + "build:transpile:uncached": "yarn ts-node scripts/buildRollup.ts", "build:types": "tsc -p tsconfig.types.json", - "build:watch": "run-p build:rollup:watch build:types:watch", + "build:watch": "run-p build:transpile:watch build:types:watch", "build:dev:watch": "run-s build:watch", - "build:rollup:watch": "rollup -c rollup.npm.config.js --watch", + "build:transpile:watch": "rollup -c rollup.npm.config.js --watch", "build:types:watch": "tsc -p tsconfig.types.json --watch", - "build:npm": "ts-node ../../scripts/prepack.ts && npm pack ./build", + "build:tarball": "ts-node ../../scripts/prepack.ts && npm pack ./build", "circularDepCheck": "madge --circular src/index.ts", "clean": "rimraf build coverage cjs esm sentry-utils-*.tgz", "fix": "run-s fix:eslint fix:prettier", diff --git a/packages/utils/src/baggage.ts b/packages/utils/src/baggage.ts index 1a2fa58d52a6..406ee3adc819 100644 --- a/packages/utils/src/baggage.ts +++ b/packages/utils/src/baggage.ts @@ -1,4 +1,4 @@ -import { DynamicSamplingContext } from '@sentry/types'; +import type { DynamicSamplingContext } from '@sentry/types'; import { isString } from './is'; import { logger } from './logger'; diff --git a/packages/utils/src/buildPolyfills/_asyncOptionalChain.ts b/packages/utils/src/buildPolyfills/_asyncOptionalChain.ts index 10a10aa03f3a..71c0382451b5 100644 --- a/packages/utils/src/buildPolyfills/_asyncOptionalChain.ts +++ b/packages/utils/src/buildPolyfills/_asyncOptionalChain.ts @@ -1,4 +1,4 @@ -import { GenericFunction } from './types'; +import type { GenericFunction } from './types'; /** * Polyfill for the optional chain operator, `?.`, given previous conversion of the expression into an array of values, diff --git a/packages/utils/src/buildPolyfills/_createNamedExportFrom.ts b/packages/utils/src/buildPolyfills/_createNamedExportFrom.ts index 0c632e586aba..678e7024b7a1 100644 --- a/packages/utils/src/buildPolyfills/_createNamedExportFrom.ts +++ b/packages/utils/src/buildPolyfills/_createNamedExportFrom.ts @@ -1,4 +1,4 @@ -import { GenericObject } from './types'; +import type { GenericObject } from './types'; declare const exports: GenericObject; diff --git a/packages/utils/src/buildPolyfills/_createStarExport.ts b/packages/utils/src/buildPolyfills/_createStarExport.ts index f4f36c8c041a..38d4ae340a85 100644 --- a/packages/utils/src/buildPolyfills/_createStarExport.ts +++ b/packages/utils/src/buildPolyfills/_createStarExport.ts @@ -1,4 +1,4 @@ -import { GenericObject } from './types'; +import type { GenericObject } from './types'; declare const exports: GenericObject; diff --git a/packages/utils/src/buildPolyfills/_interopDefault.ts b/packages/utils/src/buildPolyfills/_interopDefault.ts index 5bed0ef4e3f1..2b56c164b698 100644 --- a/packages/utils/src/buildPolyfills/_interopDefault.ts +++ b/packages/utils/src/buildPolyfills/_interopDefault.ts @@ -1,4 +1,4 @@ -import { RequireResult } from './types'; +import type { RequireResult } from './types'; /** * Unwraps a module if it has been wrapped in an object under the key `default`. diff --git a/packages/utils/src/buildPolyfills/_interopNamespace.ts b/packages/utils/src/buildPolyfills/_interopNamespace.ts index 2211e21accfa..1401d986e9a1 100644 --- a/packages/utils/src/buildPolyfills/_interopNamespace.ts +++ b/packages/utils/src/buildPolyfills/_interopNamespace.ts @@ -1,4 +1,4 @@ -import { RequireResult } from './types'; +import type { RequireResult } from './types'; /** * Adds a self-referential `default` property to CJS modules which aren't the result of transpilation from ESM modules. diff --git a/packages/utils/src/buildPolyfills/_interopNamespaceDefaultOnly.ts b/packages/utils/src/buildPolyfills/_interopNamespaceDefaultOnly.ts index 66785a79e92f..98d96e329119 100644 --- a/packages/utils/src/buildPolyfills/_interopNamespaceDefaultOnly.ts +++ b/packages/utils/src/buildPolyfills/_interopNamespaceDefaultOnly.ts @@ -1,4 +1,4 @@ -import { RequireResult } from './types'; +import type { RequireResult } from './types'; /** * Wrap a module in an object, as the value under the key `default`. diff --git a/packages/utils/src/buildPolyfills/_interopRequireDefault.ts b/packages/utils/src/buildPolyfills/_interopRequireDefault.ts index 9d9a7767cb7c..480d3561fc4e 100644 --- a/packages/utils/src/buildPolyfills/_interopRequireDefault.ts +++ b/packages/utils/src/buildPolyfills/_interopRequireDefault.ts @@ -1,4 +1,4 @@ -import { RequireResult } from './types'; +import type { RequireResult } from './types'; /** * Wraps modules which aren't the result of transpiling an ESM module in an object under the key `default` diff --git a/packages/utils/src/buildPolyfills/_interopRequireWildcard.ts b/packages/utils/src/buildPolyfills/_interopRequireWildcard.ts index 411939ab68d6..b545d90c5652 100644 --- a/packages/utils/src/buildPolyfills/_interopRequireWildcard.ts +++ b/packages/utils/src/buildPolyfills/_interopRequireWildcard.ts @@ -1,4 +1,4 @@ -import { RequireResult } from './types'; +import type { RequireResult } from './types'; /** * Adds a `default` property to CJS modules which aren't the result of transpilation from ESM modules. diff --git a/packages/utils/src/buildPolyfills/_optionalChain.ts b/packages/utils/src/buildPolyfills/_optionalChain.ts index 452c6ac110b0..3bb00a465c53 100644 --- a/packages/utils/src/buildPolyfills/_optionalChain.ts +++ b/packages/utils/src/buildPolyfills/_optionalChain.ts @@ -1,4 +1,4 @@ -import { GenericFunction } from './types'; +import type { GenericFunction } from './types'; /** * Polyfill for the optional chain operator, `?.`, given previous conversion of the expression into an array of values, diff --git a/packages/utils/src/buildPolyfills/types.ts b/packages/utils/src/buildPolyfills/types.ts index 41f1a8a31ee5..806f302460a8 100644 --- a/packages/utils/src/buildPolyfills/types.ts +++ b/packages/utils/src/buildPolyfills/types.ts @@ -1,4 +1,4 @@ -import { Primitive } from '@sentry/types'; +import type { Primitive } from '@sentry/types'; export type GenericObject = { [key: string]: Value }; export type GenericFunction = (...args: unknown[]) => Value; diff --git a/packages/utils/src/clientreport.ts b/packages/utils/src/clientreport.ts index f91c79ab5c5c..c0dbb6cb3aba 100644 --- a/packages/utils/src/clientreport.ts +++ b/packages/utils/src/clientreport.ts @@ -1,4 +1,4 @@ -import { ClientReport, ClientReportEnvelope, ClientReportItem } from '@sentry/types'; +import type { ClientReport, ClientReportEnvelope, ClientReportItem } from '@sentry/types'; import { createEnvelope } from './envelope'; import { dateTimestampInSeconds } from './time'; diff --git a/packages/utils/src/dsn.ts b/packages/utils/src/dsn.ts index d6dd4a7da2b7..f506a8fcb9be 100644 --- a/packages/utils/src/dsn.ts +++ b/packages/utils/src/dsn.ts @@ -1,4 +1,4 @@ -import { DsnComponents, DsnLike, DsnProtocol } from '@sentry/types'; +import type { DsnComponents, DsnLike, DsnProtocol } from '@sentry/types'; import { SentryError } from './error'; diff --git a/packages/utils/src/envelope.ts b/packages/utils/src/envelope.ts index 5cb1a675645e..8dcb7c492fed 100644 --- a/packages/utils/src/envelope.ts +++ b/packages/utils/src/envelope.ts @@ -1,4 +1,4 @@ -import { +import type { Attachment, AttachmentItem, BaseEnvelopeHeaders, diff --git a/packages/utils/src/instrument.ts b/packages/utils/src/instrument.ts index 319ac5e538a3..cc3c1986b06d 100644 --- a/packages/utils/src/instrument.ts +++ b/packages/utils/src/instrument.ts @@ -1,7 +1,7 @@ /* eslint-disable max-lines */ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/ban-types */ -import { WrappedFunction } from '@sentry/types'; +import type { WrappedFunction } from '@sentry/types'; import { isInstanceOf, isString } from './is'; import { CONSOLE_LEVELS, logger } from './logger'; diff --git a/packages/utils/src/is.ts b/packages/utils/src/is.ts index 8ec83089e4d5..350826cb567c 100644 --- a/packages/utils/src/is.ts +++ b/packages/utils/src/is.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -import { PolymorphicEvent, Primitive } from '@sentry/types'; +import type { PolymorphicEvent, Primitive } from '@sentry/types'; // eslint-disable-next-line @typescript-eslint/unbound-method const objectToString = Object.prototype.toString; diff --git a/packages/utils/src/logger.ts b/packages/utils/src/logger.ts index efa8f49716b9..b3cc6ddacdea 100644 --- a/packages/utils/src/logger.ts +++ b/packages/utils/src/logger.ts @@ -1,4 +1,4 @@ -import { WrappedFunction } from '@sentry/types'; +import type { WrappedFunction } from '@sentry/types'; import { getGlobalSingleton, GLOBAL_OBJ } from './worldwide'; diff --git a/packages/utils/src/misc.ts b/packages/utils/src/misc.ts index 39a61bd478e7..640c526db7b2 100644 --- a/packages/utils/src/misc.ts +++ b/packages/utils/src/misc.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Event, Exception, Mechanism, StackFrame } from '@sentry/types'; +import type { Event, Exception, Mechanism, StackFrame } from '@sentry/types'; import { addNonEnumerableProperty } from './object'; import { snipLine } from './string'; diff --git a/packages/utils/src/normalize.ts b/packages/utils/src/normalize.ts index ebeb5f0ff318..0b203d3749e0 100644 --- a/packages/utils/src/normalize.ts +++ b/packages/utils/src/normalize.ts @@ -1,7 +1,8 @@ -import { Primitive } from '@sentry/types'; +import type { Primitive } from '@sentry/types'; import { isNaN, isSyntheticEvent } from './is'; -import { memoBuilder, MemoFunc } from './memo'; +import type { MemoFunc } from './memo'; +import { memoBuilder } from './memo'; import { convertToPlainObject } from './object'; import { getFunctionName } from './stacktrace'; diff --git a/packages/utils/src/object.ts b/packages/utils/src/object.ts index 5b789b29060d..3a22e2aa49a3 100644 --- a/packages/utils/src/object.ts +++ b/packages/utils/src/object.ts @@ -1,6 +1,6 @@ /* eslint-disable max-lines */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { WrappedFunction } from '@sentry/types'; +import type { WrappedFunction } from '@sentry/types'; import { htmlTreeAsString } from './browser'; import { isElement, isError, isEvent, isInstanceOf, isPlainObject, isPrimitive } from './is'; @@ -99,9 +99,7 @@ export function urlEncode(object: { [key: string]: any }): string { * @returns An Event or Error turned into an object - or the value argurment itself, when value is neither an Event nor * an Error. */ -export function convertToPlainObject( - value: V, -): +export function convertToPlainObject(value: V): | { [ownProps: string]: unknown; type: string; diff --git a/packages/utils/src/path.ts b/packages/utils/src/path.ts index b626123505d3..46f68b1b16a5 100644 --- a/packages/utils/src/path.ts +++ b/packages/utils/src/path.ts @@ -95,8 +95,8 @@ function trim(arr: string[]): string[] { /** JSDoc */ export function relative(from: string, to: string): string { /* eslint-disable no-param-reassign */ - from = resolve(from).substr(1); - to = resolve(to).substr(1); + from = resolve(from).slice(1); + to = resolve(to).slice(1); /* eslint-enable no-param-reassign */ const fromParts = trim(from.split('/')); @@ -126,7 +126,7 @@ export function relative(from: string, to: string): string { /** JSDoc */ export function normalizePath(path: string): string { const isPathAbsolute = isAbsolute(path); - const trailingSlash = path.substr(-1) === '/'; + const trailingSlash = path.slice(-1) === '/'; // Normalize the path let normalizedPath = normalizeArray( @@ -169,7 +169,7 @@ export function dirname(path: string): string { if (dir) { // It has a dirname, strip trailing slash - dir = dir.substr(0, dir.length - 1); + dir = dir.slice(0, dir.length - 1); } return root + dir; @@ -178,8 +178,8 @@ export function dirname(path: string): string { /** JSDoc */ export function basename(path: string, ext?: string): string { let f = splitPath(path)[2]; - if (ext && f.substr(ext.length * -1) === ext) { - f = f.substr(0, f.length - ext.length); + if (ext && f.slice(ext.length * -1) === ext) { + f = f.slice(0, f.length - ext.length); } return f; } diff --git a/packages/utils/src/ratelimit.ts b/packages/utils/src/ratelimit.ts index 78720c888576..c3353c8034a6 100644 --- a/packages/utils/src/ratelimit.ts +++ b/packages/utils/src/ratelimit.ts @@ -1,4 +1,4 @@ -import { TransportMakeRequestResponse } from '@sentry/types'; +import type { TransportMakeRequestResponse } from '@sentry/types'; // Intentionally keeping the key broad, as we don't know for sure what rate limit headers get returned from backend export type RateLimits = Record; @@ -26,7 +26,11 @@ export function parseRetryAfterHeader(header: string, now: number = Date.now()): } /** - * Gets the time that given category is disabled until for rate limiting + * Gets the time that the given category is disabled until for rate limiting. + * In case no category-specific limit is set but a general rate limit across all categories is active, + * that time is returned. + * + * @return the time in ms that the category is disabled until or 0 if there's no active rate limit. */ export function disabledUntil(limits: RateLimits, category: string): number { return limits[category] || limits.all || 0; @@ -41,7 +45,8 @@ export function isRateLimited(limits: RateLimits, category: string, now: number /** * Update ratelimits from incoming headers. - * Returns true if headers contains a non-empty rate limiting header. + * + * @return the updated RateLimits object. */ export function updateRateLimits( limits: RateLimits, diff --git a/packages/utils/src/requestdata.ts b/packages/utils/src/requestdata.ts index 4f914bfe16c0..f6b40c3ab8bd 100644 --- a/packages/utils/src/requestdata.ts +++ b/packages/utils/src/requestdata.ts @@ -14,7 +14,13 @@ /* eslint-disable max-lines */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Event, ExtractedNodeRequestData, PolymorphicRequest, Transaction, TransactionSource } from '@sentry/types'; +import type { + Event, + ExtractedNodeRequestData, + PolymorphicRequest, + Transaction, + TransactionSource, +} from '@sentry/types'; import { isPlainObject, isString } from './is'; import { normalize } from './normalize'; diff --git a/packages/utils/src/severity.ts b/packages/utils/src/severity.ts index c40f0c2a6004..6d631b590a88 100644 --- a/packages/utils/src/severity.ts +++ b/packages/utils/src/severity.ts @@ -1,5 +1,5 @@ /* eslint-disable deprecation/deprecation */ -import { Severity, SeverityLevel } from '@sentry/types'; +import type { Severity, SeverityLevel } from '@sentry/types'; // Note: Ideally the `SeverityLevel` type would be derived from `validSeverityLevels`, but that would mean either // diff --git a/packages/utils/src/stacktrace.ts b/packages/utils/src/stacktrace.ts index fabbc9f43575..d9891c89c8de 100644 --- a/packages/utils/src/stacktrace.ts +++ b/packages/utils/src/stacktrace.ts @@ -1,4 +1,4 @@ -import { StackFrame, StackLineParser, StackLineParserFn, StackParser } from '@sentry/types'; +import type { StackFrame, StackLineParser, StackLineParserFn, StackParser } from '@sentry/types'; const STACKTRACE_LIMIT = 50; @@ -142,12 +142,12 @@ function node(getModule?: GetModuleFn): StackLineParserFn { } if (methodStart > 0) { - object = functionName.substr(0, methodStart); - method = functionName.substr(methodStart + 1); + object = functionName.slice(0, methodStart); + method = functionName.slice(methodStart + 1); const objectEnd = object.indexOf('.Module'); if (objectEnd > 0) { - functionName = functionName.substr(objectEnd + 1); - object = object.substr(0, objectEnd); + functionName = functionName.slice(objectEnd + 1); + object = object.slice(0, objectEnd); } } typeName = undefined; @@ -168,7 +168,7 @@ function node(getModule?: GetModuleFn): StackLineParserFn { functionName = typeName ? `${typeName}.${methodName}` : methodName; } - const filename = lineMatch[2]?.startsWith('file://') ? lineMatch[2].substr(7) : lineMatch[2]; + const filename = lineMatch[2]?.startsWith('file://') ? lineMatch[2].slice(7) : lineMatch[2]; const isNative = lineMatch[5] === 'native'; const isInternal = isNative || (filename && !filename.startsWith('/') && !filename.startsWith('.') && filename.indexOf(':\\') !== 1); diff --git a/packages/utils/src/string.ts b/packages/utils/src/string.ts index 90b76b6f4621..7557d1d2af77 100644 --- a/packages/utils/src/string.ts +++ b/packages/utils/src/string.ts @@ -11,7 +11,7 @@ export function truncate(str: string, max: number = 0): string { if (typeof str !== 'string' || max === 0) { return str; } - return str.length <= max ? str : `${str.substr(0, max)}...`; + return str.length <= max ? str : `${str.slice(0, max)}...`; } /** diff --git a/packages/utils/src/tracing.ts b/packages/utils/src/tracing.ts index cef6e334f7e7..7eee5312ce6d 100644 --- a/packages/utils/src/tracing.ts +++ b/packages/utils/src/tracing.ts @@ -1,4 +1,4 @@ -import { TraceparentData } from '@sentry/types'; +import type { TraceparentData } from '@sentry/types'; export const TRACEPARENT_REGEXP = new RegExp( '^[ \\t]*' + // whitespace diff --git a/packages/utils/src/worldwide.ts b/packages/utils/src/worldwide.ts index 464ca794b4a4..e341339bc980 100644 --- a/packages/utils/src/worldwide.ts +++ b/packages/utils/src/worldwide.ts @@ -12,7 +12,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Integration } from '@sentry/types'; +import type { Integration } from '@sentry/types'; /** Internal global with common properties and Sentry extensions */ export interface InternalGlobal { diff --git a/packages/utils/test/buildPolyfills/interop.test.ts b/packages/utils/test/buildPolyfills/interop.test.ts index 917d62daee2f..a53c64eb0979 100644 --- a/packages/utils/test/buildPolyfills/interop.test.ts +++ b/packages/utils/test/buildPolyfills/interop.test.ts @@ -5,7 +5,7 @@ import { _interopRequireDefault, _interopRequireWildcard, } from '../../src/buildPolyfills'; -import { RequireResult } from '../../src/buildPolyfills/types'; +import type { RequireResult } from '../../src/buildPolyfills/types'; import { _interopDefault as _interopDefaultOrig, _interopNamespace as _interopNamespaceOrig, diff --git a/packages/utils/test/buildPolyfills/nullishCoalesce.test.ts b/packages/utils/test/buildPolyfills/nullishCoalesce.test.ts index 55fd5ee9c996..d2a1d98d8a05 100644 --- a/packages/utils/test/buildPolyfills/nullishCoalesce.test.ts +++ b/packages/utils/test/buildPolyfills/nullishCoalesce.test.ts @@ -1,5 +1,5 @@ import { _nullishCoalesce } from '../../src/buildPolyfills'; -import { Value } from '../../src/buildPolyfills/types'; +import type { Value } from '../../src/buildPolyfills/types'; import { _nullishCoalesce as _nullishCoalesceOrig } from './originals'; const dogStr = 'dogs are great!'; diff --git a/packages/utils/test/buildPolyfills/optionalChain.test.ts b/packages/utils/test/buildPolyfills/optionalChain.test.ts index 4fec5ee1dc63..4ad6f14befde 100644 --- a/packages/utils/test/buildPolyfills/optionalChain.test.ts +++ b/packages/utils/test/buildPolyfills/optionalChain.test.ts @@ -1,7 +1,7 @@ import { default as arrayFlat } from 'array.prototype.flat'; import { _optionalChain } from '../../src/buildPolyfills'; -import { GenericFunction, GenericObject, Value } from '../../src/buildPolyfills/types'; +import type { GenericFunction, GenericObject, Value } from '../../src/buildPolyfills/types'; import { _optionalChain as _optionalChainOrig } from './originals'; // Older versions of Node don't have `Array.prototype.flat`, which crashes these tests. On newer versions that do have diff --git a/packages/utils/test/clientreport.test.ts b/packages/utils/test/clientreport.test.ts index 03026fd16eaf..b783886771dc 100644 --- a/packages/utils/test/clientreport.test.ts +++ b/packages/utils/test/clientreport.test.ts @@ -1,4 +1,4 @@ -import { ClientReport } from '@sentry/types'; +import type { ClientReport } from '@sentry/types'; import { TextDecoder, TextEncoder } from 'util'; import { createClientReportEnvelope } from '../src/clientreport'; diff --git a/packages/utils/test/envelope.test.ts b/packages/utils/test/envelope.test.ts index b88400dbe503..2996b6dcde06 100644 --- a/packages/utils/test/envelope.test.ts +++ b/packages/utils/test/envelope.test.ts @@ -1,4 +1,4 @@ -import { EventEnvelope } from '@sentry/types'; +import type { EventEnvelope } from '@sentry/types'; import { TextDecoder, TextEncoder } from 'util'; const encoder = new TextEncoder(); diff --git a/packages/utils/test/misc.test.ts b/packages/utils/test/misc.test.ts index ded5a7a4d037..77c6441e31a1 100644 --- a/packages/utils/test/misc.test.ts +++ b/packages/utils/test/misc.test.ts @@ -1,4 +1,4 @@ -import { Event, Mechanism, StackFrame } from '@sentry/types'; +import type { Event, Mechanism, StackFrame } from '@sentry/types'; import { addContextToFrame, diff --git a/packages/utils/test/ratelimit.test.ts b/packages/utils/test/ratelimit.test.ts index 52a04683563f..978774c78ea3 100644 --- a/packages/utils/test/ratelimit.test.ts +++ b/packages/utils/test/ratelimit.test.ts @@ -1,9 +1,9 @@ +import type { RateLimits } from '../src/ratelimit'; import { DEFAULT_RETRY_AFTER, disabledUntil, isRateLimited, parseRetryAfterHeader, - RateLimits, updateRateLimits, } from '../src/ratelimit'; diff --git a/packages/vue/package.json b/packages/vue/package.json index 7051e307b30f..7433e18e6822 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/vue", - "version": "7.29.0", + "version": "7.30.0", "description": "Official Sentry SDK for Vue.js", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/vue", @@ -16,10 +16,10 @@ "access": "public" }, "dependencies": { - "@sentry/browser": "7.29.0", - "@sentry/core": "7.29.0", - "@sentry/types": "7.29.0", - "@sentry/utils": "7.29.0", + "@sentry/browser": "7.30.0", + "@sentry/core": "7.30.0", + "@sentry/types": "7.30.0", + "@sentry/utils": "7.30.0", "tslib": "^1.9.3" }, "peerDependencies": { @@ -29,17 +29,17 @@ "vue": "~3.2.41" }, "scripts": { - "build": "run-p build:rollup build:types", - "build:bundle": "yarn ts-node ../../scripts/ensure-bundle-deps.ts && yarn rollup --config rollup.bundle.config.js", - "build:dev": "run-p build:rollup build:types", - "build:rollup": "rollup -c rollup.npm.config.js", + "build": "run-p build:transpile build:types", + "build:bundle": "rollup --config rollup.bundle.config.js", + "build:dev": "run-p build:transpile build:types", + "build:transpile": "rollup -c rollup.npm.config.js", "build:types": "tsc -p tsconfig.types.json", - "build:watch": "run-p build:rollup:watch build:types:watch", + "build:watch": "run-p build:transpile:watch build:types:watch", "build:bundle:watch": "rollup --config --watch", - "build:dev:watch": "run-p build:rollup:watch build:types:watch", - "build:rollup:watch": "rollup -c rollup.npm.config.js --watch", + "build:dev:watch": "run-p build:transpile:watch build:types:watch", + "build:transpile:watch": "rollup -c rollup.npm.config.js --watch", "build:types:watch": "tsc -p tsconfig.types.json --watch", - "build:npm": "ts-node ../../scripts/prepack.ts && npm pack ./build", + "build:tarball": "ts-node ../../scripts/prepack.ts && npm pack ./build", "circularDepCheck": "madge --circular src/index.ts", "clean": "rimraf build coverage sentry-vue-*.tgz", "fix": "run-s fix:eslint fix:prettier", diff --git a/packages/vue/src/components.ts b/packages/vue/src/components.ts index 9aa78a5a23dc..c52962aade41 100644 --- a/packages/vue/src/components.ts +++ b/packages/vue/src/components.ts @@ -1,4 +1,4 @@ -import { ViewModel } from './types'; +import type { ViewModel } from './types'; // Vendored directly from https://github.com/vuejs/vue/blob/master/src/core/util/debug.js with types only changes. const classifyRE = /(?:^|[-_])(\w)/g; @@ -8,17 +8,8 @@ const ROOT_COMPONENT_NAME = ''; const ANONYMOUS_COMPONENT_NAME = ''; const repeat = (str: string, n: number): string => { - let res = ''; - while (n) { - if (n % 2 === 1) { - res += str; - } - if (n > 1) { - str += str; // eslint-disable-line no-param-reassign - } - n >>= 1; // eslint-disable-line no-bitwise, no-param-reassign - } - return res; + // string.repeat() is not supported by IE11, we fall back to just using the string in that case + return str.repeat ? str.repeat(n) : str; }; export const formatComponentName = (vm?: ViewModel, includeFile?: boolean): string => { diff --git a/packages/vue/src/constants.ts b/packages/vue/src/constants.ts index 13c9d7af9c83..e254d988c40c 100644 --- a/packages/vue/src/constants.ts +++ b/packages/vue/src/constants.ts @@ -1,3 +1,3 @@ -import { Operation } from './types'; +import type { Operation } from './types'; export const DEFAULT_HOOKS: Operation[] = ['activate', 'mount', 'update']; diff --git a/packages/vue/src/errorhandler.ts b/packages/vue/src/errorhandler.ts index 16cb09a723ab..032ecc2386c8 100644 --- a/packages/vue/src/errorhandler.ts +++ b/packages/vue/src/errorhandler.ts @@ -1,7 +1,7 @@ import { getCurrentHub } from '@sentry/browser'; import { formatComponentName, generateComponentTrace } from './components'; -import { Options, ViewModel, Vue } from './types'; +import type { Options, ViewModel, Vue } from './types'; type UnknownFunc = (...args: unknown[]) => void; diff --git a/packages/vue/src/router.ts b/packages/vue/src/router.ts index f782246e8fb2..2bdb088a24cf 100644 --- a/packages/vue/src/router.ts +++ b/packages/vue/src/router.ts @@ -1,5 +1,5 @@ import { captureException, WINDOW } from '@sentry/browser'; -import { Transaction, TransactionContext, TransactionSource } from '@sentry/types'; +import type { Transaction, TransactionContext, TransactionSource } from '@sentry/types'; import { getActiveTransaction } from './tracing'; diff --git a/packages/vue/src/sdk.ts b/packages/vue/src/sdk.ts index a2063038402c..9c10dbec28e6 100644 --- a/packages/vue/src/sdk.ts +++ b/packages/vue/src/sdk.ts @@ -4,7 +4,7 @@ import { arrayify, GLOBAL_OBJ } from '@sentry/utils'; import { DEFAULT_HOOKS } from './constants'; import { attachErrorHandler } from './errorhandler'; import { createTracingMixins } from './tracing'; -import { Options, TracingOptions, Vue } from './types'; +import type { Options, TracingOptions, Vue } from './types'; const globalWithVue = GLOBAL_OBJ as typeof GLOBAL_OBJ & { Vue: Vue }; diff --git a/packages/vue/src/tracing.ts b/packages/vue/src/tracing.ts index 889cbe30dca6..9f56586aa717 100644 --- a/packages/vue/src/tracing.ts +++ b/packages/vue/src/tracing.ts @@ -1,10 +1,10 @@ import { getCurrentHub } from '@sentry/browser'; -import { Span, Transaction } from '@sentry/types'; +import type { Span, Transaction } from '@sentry/types'; import { logger, timestampInSeconds } from '@sentry/utils'; import { formatComponentName } from './components'; import { DEFAULT_HOOKS } from './constants'; -import { Hook, Operation, TracingOptions, ViewModel, Vue } from './types'; +import type { Hook, Operation, TracingOptions, ViewModel, Vue } from './types'; const VUE_OP = 'ui.vue'; diff --git a/packages/vue/src/types.ts b/packages/vue/src/types.ts index eb386d6f3e4b..1cc39b97b887 100644 --- a/packages/vue/src/types.ts +++ b/packages/vue/src/types.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { BrowserOptions } from '@sentry/browser'; +import type { BrowserOptions } from '@sentry/browser'; // This is not great, but kinda necessary to make it woth with Vue@2 and Vue@3 at the same time. export interface Vue { diff --git a/packages/vue/test/errorHandler.test.ts b/packages/vue/test/errorHandler.test.ts index 1c41166fd167..e7530c91304e 100644 --- a/packages/vue/test/errorHandler.test.ts +++ b/packages/vue/test/errorHandler.test.ts @@ -2,7 +2,7 @@ import { getCurrentHub } from '@sentry/browser'; import { generateComponentTrace } from '../src/components'; import { attachErrorHandler } from '../src/errorhandler'; -import { Operation, Options, ViewModel, Vue } from '../src/types'; +import type { Operation, Options, ViewModel, Vue } from '../src/types'; describe('attachErrorHandler', () => { describe('attachProps', () => { diff --git a/packages/vue/test/router.test.ts b/packages/vue/test/router.test.ts index c4072616d9af..65482f80f10b 100644 --- a/packages/vue/test/router.test.ts +++ b/packages/vue/test/router.test.ts @@ -1,8 +1,8 @@ import * as SentryBrowser from '@sentry/browser'; -import { Transaction } from '@sentry/types'; +import type { Transaction } from '@sentry/types'; import { vueRouterInstrumentation } from '../src'; -import { Route } from '../src/router'; +import type { Route } from '../src/router'; import * as vueTracing from '../src/tracing'; const captureExceptionSpy = jest.spyOn(SentryBrowser, 'captureException'); diff --git a/packages/wasm/package.json b/packages/wasm/package.json index f3d1fc801aca..07ab31bee05e 100644 --- a/packages/wasm/package.json +++ b/packages/wasm/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/wasm", - "version": "7.29.0", + "version": "7.30.0", "description": "Support for WASM.", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/wasm", @@ -16,9 +16,9 @@ "access": "public" }, "dependencies": { - "@sentry/browser": "7.29.0", - "@sentry/types": "7.29.0", - "@sentry/utils": "7.29.0", + "@sentry/browser": "7.30.0", + "@sentry/types": "7.30.0", + "@sentry/utils": "7.30.0", "tslib": "^1.9.3" }, "devDependencies": { @@ -30,17 +30,17 @@ "puppeteer": "^5.5.0" }, "scripts": { - "build": "run-p build:rollup build:bundle build:types", - "build:bundle": "yarn ts-node ../../scripts/ensure-bundle-deps.ts && yarn rollup --config rollup.bundle.config.js", - "build:dev": "run-p build:rollup build:types", - "build:rollup": "rollup -c rollup.npm.config.js", + "build": "run-p build:transpile build:bundle build:types", + "build:bundle": "rollup --config rollup.bundle.config.js", + "build:dev": "run-p build:transpile build:types", + "build:transpile": "rollup -c rollup.npm.config.js", "build:types": "tsc -p tsconfig.types.json", - "build:watch": "run-p build:rollup:watch build:bundle:watch build:types:watch", + "build:watch": "run-p build:transpile:watch build:bundle:watch build:types:watch", "build:bundle:watch": "rollup --config rollup.bundle.config.js --watch", - "build:dev:watch": "run-p build:rollup:watch build:types:watch", - "build:rollup:watch": "rollup -c rollup.npm.config.js --watch", + "build:dev:watch": "run-p build:transpile:watch build:types:watch", + "build:transpile:watch": "rollup -c rollup.npm.config.js --watch", "build:types:watch": "tsc -p tsconfig.types.json --watch", - "build:npm": "ts-node ../../scripts/prepack.ts --bundles && npm pack ./build/npm", + "build:tarball": "ts-node ../../scripts/prepack.ts --bundles && npm pack ./build/npm", "circularDepCheck": "madge --circular src/index.ts", "clean": "rimraf build coverage sentry-wasm-*.tgz", "fix": "run-s fix:eslint fix:prettier", diff --git a/packages/wasm/src/index.ts b/packages/wasm/src/index.ts index cc5e678ee123..436668e73ac2 100644 --- a/packages/wasm/src/index.ts +++ b/packages/wasm/src/index.ts @@ -1,4 +1,4 @@ -import { Event, EventProcessor, Hub, Integration, StackFrame } from '@sentry/types'; +import type { Event, EventProcessor, Hub, Integration, StackFrame } from '@sentry/types'; import { patchWebAssembly } from './patchWebAssembly'; import { getImage, getImages } from './registry'; diff --git a/packages/wasm/src/registry.ts b/packages/wasm/src/registry.ts index 56609b389cca..f0b1069ed7e9 100644 --- a/packages/wasm/src/registry.ts +++ b/packages/wasm/src/registry.ts @@ -1,4 +1,4 @@ -import { DebugImage } from '@sentry/types'; +import type { DebugImage } from '@sentry/types'; export const IMAGES: Array = []; @@ -50,7 +50,7 @@ export function registerModule(module: WebAssembly.Module, url: string): void { code_id: buildId, code_file: url, debug_file: debugFile ? new URL(debugFile, url).href : null, - debug_id: `${buildId.padEnd(32, '0').substr(0, 32)}0`, + debug_id: `${buildId.padEnd(32, '0').slice(0, 32)}0`, }); } } diff --git a/scripts/ensure-bundle-deps.ts b/scripts/ensure-bundle-deps.ts index cf14c819f509..8be6377ee4e1 100644 --- a/scripts/ensure-bundle-deps.ts +++ b/scripts/ensure-bundle-deps.ts @@ -64,7 +64,7 @@ export async function ensureBundleBuildPrereqs(options: { // dependencies. Either way, it's on us to fix the problem. // // TODO: This actually *doesn't* work for package-level `build`, not because of a flaw in this logic, but because - // `build:rollup` has similar dependency needs (it needs types rather than npm builds). We should do something like + // `build:transpile` has similar dependency needs (it needs types rather than npm builds). We should do something like // this for that at some point. if (isTopLevelBuild && yarnScript === 'build') { @@ -95,7 +95,7 @@ export async function ensureBundleBuildPrereqs(options: { for (const dependencyDir of dependencyDirs) { console.log(`\nBuilding \`${dependencyDir}\` package...`); - run('yarn build:rollup', { cwd: `${packagesDir}/${dependencyDir}` }); + run('yarn build:transpile', { cwd: `${packagesDir}/${dependencyDir}` }); } console.log('\nAll dependencies built successfully. Beginning bundle build...'); diff --git a/scripts/test.ts b/scripts/node-unit-tests.ts similarity index 75% rename from scripts/test.ts rename to scripts/node-unit-tests.ts index b1af2f918ee8..4e6efaec9d0e 100644 --- a/scripts/test.ts +++ b/scripts/node-unit-tests.ts @@ -3,25 +3,9 @@ import * as fs from 'fs'; const CURRENT_NODE_VERSION = process.version.replace('v', '').split('.')[0]; -// We run ember tests in their own job. -const DEFAULT_SKIP_TESTS_PACKAGES = ['@sentry/ember']; - -// These packages don't support Node 8 for syntax or dependency reasons. -const NODE_8_SKIP_TESTS_PACKAGES = [ +const DEFAULT_SKIP_TESTS_PACKAGES = [ '@sentry-internal/eslint-plugin-sdk', - '@sentry/react', - '@sentry/wasm', - '@sentry/gatsby', - '@sentry/serverless', - '@sentry/nextjs', - '@sentry/angular', - '@sentry/remix', - '@sentry/svelte', // svelte testing library requires Node >= 10 - '@sentry/replay', -]; - -// These can be skipped when running tests in different Node environments. -const SKIP_BROWSER_TESTS_PACKAGES = [ + '@sentry/ember', '@sentry/browser', '@sentry/vue', '@sentry/react', @@ -31,15 +15,8 @@ const SKIP_BROWSER_TESTS_PACKAGES = [ '@sentry/wasm', ]; -// These can be skipped when running tests independently of the Node version. -const SKIP_NODE_TESTS_PACKAGES = [ - '@sentry/node', - '@sentry/opentelemetry-node', - '@sentry/serverless', - '@sentry/nextjs', - '@sentry/remix', - '@sentry/gatsby', -]; +// These packages don't support Node 8 for syntax or dependency reasons. +const NODE_8_SKIP_TESTS_PACKAGES = ['@sentry/gatsby', '@sentry/serverless', '@sentry/nextjs', '@sentry/remix']; // We have to downgrade some of our dependencies in order to run tests in Node 8 and 10. const NODE_8_LEGACY_DEPENDENCIES = [ @@ -48,12 +25,14 @@ const NODE_8_LEGACY_DEPENDENCIES = [ 'jest-environment-jsdom@25.x', 'jest-environment-node@25.x', 'ts-jest@25.x', + 'lerna@3.13.4', ]; -const NODE_10_SKIP_TESTS_PACKAGES = ['@sentry/remix', '@sentry/replay']; -const NODE_10_LEGACY_DEPENDENCIES = ['jsdom@16.x']; +const NODE_10_SKIP_TESTS_PACKAGES = ['@sentry/remix']; +const NODE_10_LEGACY_DEPENDENCIES = ['jsdom@16.x', 'lerna@3.13.4']; const NODE_12_SKIP_TESTS_PACKAGES = ['@sentry/remix']; +const NODE_12_LEGACY_DEPENDENCIES = ['lerna@3.13.4']; type JSONValue = string | number | boolean | null | JSONArray | JSONObject; @@ -70,8 +49,8 @@ interface TSConfigJSON extends JSONObject { * Run the given shell command, piping the shell process's `stdin`, `stdout`, and `stderr` to that of the current * process. Returns contents of `stdout`. */ -function run(cmd: string, options?: childProcess.ExecSyncOptions) { - return childProcess.execSync(cmd, { stdio: 'inherit', ...options }); +function run(cmd: string, options?: childProcess.ExecSyncOptions): void { + childProcess.execSync(cmd, { stdio: 'inherit', ...options }); } /** @@ -135,14 +114,6 @@ function runTests(): void { DEFAULT_SKIP_TESTS_PACKAGES.forEach(dep => ignores.add(dep)); - if (process.env.TESTS_SKIP === 'browser') { - SKIP_BROWSER_TESTS_PACKAGES.forEach(dep => ignores.add(dep)); - } - - if (process.env.TESTS_SKIP === 'node') { - SKIP_NODE_TESTS_PACKAGES.forEach(dep => ignores.add(dep)); - } - switch (CURRENT_NODE_VERSION) { case '8': NODE_8_SKIP_TESTS_PACKAGES.forEach(dep => ignores.add(dep)); @@ -157,6 +128,7 @@ function runTests(): void { break; case '12': NODE_12_SKIP_TESTS_PACKAGES.forEach(dep => ignores.add(dep)); + installLegacyDeps(NODE_12_LEGACY_DEPENDENCIES); es6ifyTestTSConfig('utils'); break; } diff --git a/yarn.lock b/yarn.lock index 94f6c6af9482..c2dbdbaef239 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1589,7 +1589,7 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" -"@gar/promisify@^1.0.1": +"@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" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== @@ -1935,6 +1935,16 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@hutson/parse-repository-url@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz#98c23c950a3d9b6c8f0daed06da6c3af06981340" + integrity sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q== + +"@isaacs/string-locale-compare@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz#291c227e93fd407a96ecd59879a35809120e432b" + integrity sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ== + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -2264,626 +2274,689 @@ merge-source-map "^1.1.0" schema-utils "^2.7.0" -"@lerna/add@3.13.3": - version "3.13.3" - resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.13.3.tgz#f4c1674839780e458f0426d4f7b6d0a77b9a2ae9" - integrity sha512-T3/Lsbo9ZFq+vL3ssaHxA8oKikZAPTJTGFe4CRuQgWCDd/M61+51jeWsngdaHpwzSSRDRjxg8fJTG10y10pnfA== - dependencies: - "@lerna/bootstrap" "3.13.3" - "@lerna/command" "3.13.3" - "@lerna/filter-options" "3.13.3" - "@lerna/npm-conf" "3.13.0" - "@lerna/validation-error" "3.13.0" +"@lerna/add@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/add/-/add-6.1.0.tgz#0f09495c5e1af4c4f316344af34b6d1a91b15b19" + integrity sha512-f2cAeS1mE/p7QvSRn5TCgdUXw6QVbu8PeRxaTOxTThhTdJIWdXZfY00QjAsU6jw1PdYXK1qGUSwWOPkdR16mBg== + dependencies: + "@lerna/bootstrap" "6.1.0" + "@lerna/command" "6.1.0" + "@lerna/filter-options" "6.1.0" + "@lerna/npm-conf" "6.1.0" + "@lerna/validation-error" "6.1.0" dedent "^0.7.0" - npm-package-arg "^6.1.0" - p-map "^1.2.0" - pacote "^9.5.0" - semver "^5.5.0" - -"@lerna/batch-packages@3.13.0": - version "3.13.0" - resolved "https://registry.yarnpkg.com/@lerna/batch-packages/-/batch-packages-3.13.0.tgz#697fde5be28822af9d9dca2f750250b90a89a000" - integrity sha512-TgLBTZ7ZlqilGnzJ3xh1KdAHcySfHytgNRTdG9YomfriTU6kVfp1HrXxKJYVGs7ClPUNt2CTFEOkw0tMBronjw== - dependencies: - "@lerna/package-graph" "3.13.0" - "@lerna/validation-error" "3.13.0" - npmlog "^4.1.2" + npm-package-arg "8.1.1" + p-map "^4.0.0" + pacote "^13.6.1" + semver "^7.3.4" -"@lerna/bootstrap@3.13.3": - version "3.13.3" - resolved "https://registry.yarnpkg.com/@lerna/bootstrap/-/bootstrap-3.13.3.tgz#a0e5e466de5c100b49d558d39139204fc4db5c95" - integrity sha512-2XzijnLHRZOVQh8pwS7+5GR3cG4uh+EiLrWOishCq2TVzkqgjaS3GGBoef7KMCXfWHoLqAZRr/jEdLqfETLVqg== - dependencies: - "@lerna/batch-packages" "3.13.0" - "@lerna/command" "3.13.3" - "@lerna/filter-options" "3.13.3" - "@lerna/has-npm-version" "3.13.3" - "@lerna/npm-install" "3.13.3" - "@lerna/package-graph" "3.13.0" - "@lerna/pulse-till-done" "3.13.0" - "@lerna/rimraf-dir" "3.13.3" - "@lerna/run-lifecycle" "3.13.0" - "@lerna/run-parallel-batches" "3.13.0" - "@lerna/symlink-binary" "3.13.0" - "@lerna/symlink-dependencies" "3.13.0" - "@lerna/validation-error" "3.13.0" +"@lerna/bootstrap@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/bootstrap/-/bootstrap-6.1.0.tgz#81738f32cd431814c9943dfffe28752587d90830" + integrity sha512-aDxKqgxexVj/Z0B1aPu7P1iPbPqhk1FPkl/iayCmPlkAh90pYEH0uVytGzi1hFB5iXEfG7Pa6azGQywUodx/1g== + dependencies: + "@lerna/command" "6.1.0" + "@lerna/filter-options" "6.1.0" + "@lerna/has-npm-version" "6.1.0" + "@lerna/npm-install" "6.1.0" + "@lerna/package-graph" "6.1.0" + "@lerna/pulse-till-done" "6.1.0" + "@lerna/rimraf-dir" "6.1.0" + "@lerna/run-lifecycle" "6.1.0" + "@lerna/run-topologically" "6.1.0" + "@lerna/symlink-binary" "6.1.0" + "@lerna/symlink-dependencies" "6.1.0" + "@lerna/validation-error" "6.1.0" + "@npmcli/arborist" "5.3.0" dedent "^0.7.0" - get-port "^3.2.0" - multimatch "^2.1.0" - npm-package-arg "^6.1.0" - npmlog "^4.1.2" - p-finally "^1.0.0" - p-map "^1.2.0" - p-map-series "^1.0.0" - p-waterfall "^1.0.0" - read-package-tree "^5.1.6" - semver "^5.5.0" + get-port "^5.1.1" + multimatch "^5.0.0" + npm-package-arg "8.1.1" + npmlog "^6.0.2" + p-map "^4.0.0" + p-map-series "^2.1.0" + p-waterfall "^2.1.1" + semver "^7.3.4" -"@lerna/changed@3.13.4": - version "3.13.4" - resolved "https://registry.yarnpkg.com/@lerna/changed/-/changed-3.13.4.tgz#c69d8a079999e49611dd58987f08437baee81ad4" - integrity sha512-9lfOyRVObasw6L/z7yCSfsEl1QKy0Eamb8t2Krg1deIoAt+cE3JXOdGGC1MhOSli+7f/U9LyLXjJzIOs/pc9fw== +"@lerna/changed@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/changed/-/changed-6.1.0.tgz#4fa480cbb0e7106ea9dad30d315e953975118d06" + integrity sha512-p7C2tf1scmvoUC1Osck/XIKVKXAQ8m8neL8/rfgKSYsvUVjsOB1LbF5HH1VUZntE6S4OxkRxUQGkAHVf5xrGqw== dependencies: - "@lerna/collect-updates" "3.13.3" - "@lerna/command" "3.13.3" - "@lerna/listable" "3.13.0" - "@lerna/output" "3.13.0" - "@lerna/version" "3.13.4" + "@lerna/collect-updates" "6.1.0" + "@lerna/command" "6.1.0" + "@lerna/listable" "6.1.0" + "@lerna/output" "6.1.0" -"@lerna/check-working-tree@3.13.3": - version "3.13.3" - resolved "https://registry.yarnpkg.com/@lerna/check-working-tree/-/check-working-tree-3.13.3.tgz#836a3ffd4413a29aca92ccca4a115e4f97109992" - integrity sha512-LoGZvTkne+V1WpVdCTU0XNzFKsQa2AiAFKksGRT0v8NQj6VAPp0jfVYDayTqwaWt2Ne0OGKOFE79Y5LStOuhaQ== +"@lerna/check-working-tree@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/check-working-tree/-/check-working-tree-6.1.0.tgz#b8970fd27a26449b12456d5d0ece60477aa54e15" + integrity sha512-hSciDmRqsNPevMhAD+SYbnhjatdb7UUu9W8vTyGtUXkrq2xtRZU0vAOgqovV8meirRkbC41pZePYKqyQtF0y3w== dependencies: - "@lerna/describe-ref" "3.13.3" - "@lerna/validation-error" "3.13.0" + "@lerna/collect-uncommitted" "6.1.0" + "@lerna/describe-ref" "6.1.0" + "@lerna/validation-error" "6.1.0" -"@lerna/child-process@3.13.3": - version "3.13.3" - resolved "https://registry.yarnpkg.com/@lerna/child-process/-/child-process-3.13.3.tgz#6c084ee5cca9fc9e04d6bf4fc3f743ed26ff190c" - integrity sha512-3/e2uCLnbU+bydDnDwyadpOmuzazS01EcnOleAnuj9235CU2U97DH6OyoG1EW/fU59x11J+HjIqovh5vBaMQjQ== +"@lerna/child-process@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/child-process/-/child-process-6.1.0.tgz#6361f7945cd5b36e983f819de3cd91c315707302" + integrity sha512-jhr3sCFeps6Y15SCrWEPvqE64i+QLOTSh+OzxlziCBf7ZEUu7sF0yA4n5bAqw8j43yCKhhjkf/ZLYxZe+pnl3Q== dependencies: - chalk "^2.3.1" - execa "^1.0.0" - strong-log-transformer "^2.0.0" + chalk "^4.1.0" + execa "^5.0.0" + strong-log-transformer "^2.1.0" -"@lerna/clean@3.13.3": - version "3.13.3" - resolved "https://registry.yarnpkg.com/@lerna/clean/-/clean-3.13.3.tgz#5673a1238e0712d31711e7e4e8cb9641891daaea" - integrity sha512-xmNauF1PpmDaKdtA2yuRc23Tru4q7UMO6yB1a/TTwxYPYYsAWG/CBK65bV26J7x4RlZtEv06ztYGMa9zh34UXA== - dependencies: - "@lerna/command" "3.13.3" - "@lerna/filter-options" "3.13.3" - "@lerna/prompt" "3.13.0" - "@lerna/pulse-till-done" "3.13.0" - "@lerna/rimraf-dir" "3.13.3" - p-map "^1.2.0" - p-map-series "^1.0.0" - p-waterfall "^1.0.0" - -"@lerna/cli@3.13.0": - version "3.13.0" - resolved "https://registry.yarnpkg.com/@lerna/cli/-/cli-3.13.0.tgz#3d7b357fdd7818423e9681a7b7f2abd106c8a266" - integrity sha512-HgFGlyCZbYaYrjOr3w/EsY18PdvtsTmDfpUQe8HwDjXlPeCCUgliZjXLOVBxSjiOvPeOSwvopwIHKWQmYbwywg== - dependencies: - "@lerna/global-options" "3.13.0" +"@lerna/clean@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/clean/-/clean-6.1.0.tgz#1114fd90ad82438123726e2493d3550e73abebbc" + integrity sha512-LRK2hiNUiBhPe5tmJiefOVpkaX2Yob0rp15IFNIbuteRWUJg0oERFQo62WvnxwElfzKSOhr8OGuEq/vN4bMrRA== + dependencies: + "@lerna/command" "6.1.0" + "@lerna/filter-options" "6.1.0" + "@lerna/prompt" "6.1.0" + "@lerna/pulse-till-done" "6.1.0" + "@lerna/rimraf-dir" "6.1.0" + p-map "^4.0.0" + p-map-series "^2.1.0" + p-waterfall "^2.1.1" + +"@lerna/cli@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/cli/-/cli-6.1.0.tgz#41214331fa4c1ea5f41125befdd81b009fe12640" + integrity sha512-p4G/OSPIrHiNkEl8bXrQdFOh4ORAZp2+ljvbXmAxpdf2qmopaUdr+bZYtIAxd+Z42SxRnDNz9IEyR0kOsARRQQ== + dependencies: + "@lerna/global-options" "6.1.0" dedent "^0.7.0" - npmlog "^4.1.2" - yargs "^12.0.1" + npmlog "^6.0.2" + yargs "^16.2.0" -"@lerna/collect-updates@3.13.3": - version "3.13.3" - resolved "https://registry.yarnpkg.com/@lerna/collect-updates/-/collect-updates-3.13.3.tgz#616648da59f0aff4a8e60257795cc46ca6921edd" - integrity sha512-sTpALOAxli/ZS+Mjq6fbmjU9YXqFJ2E4FrE1Ijl4wPC5stXEosg2u0Z1uPY+zVKdM+mOIhLxPVdx83rUgRS+Cg== +"@lerna/collect-uncommitted@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/collect-uncommitted/-/collect-uncommitted-6.1.0.tgz#b6ffd7adda24d73b70304210967d3518caa3529d" + integrity sha512-VvWvqDZG+OiF4PwV4Ro695r3+8ty4w+11Bnq8tbsbu5gq8qZiam8Fkc/TQLuNNqP0SPi4qmMPaIzWvSze3SmDg== dependencies: - "@lerna/child-process" "3.13.3" - "@lerna/describe-ref" "3.13.3" + "@lerna/child-process" "6.1.0" + chalk "^4.1.0" + npmlog "^6.0.2" + +"@lerna/collect-updates@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/collect-updates/-/collect-updates-6.1.0.tgz#75fcc0733b5a9ac318a6484b890aa4061b7859c2" + integrity sha512-dgH7kgstwCXFctylQ4cxuCmhwSIE6VJZfHdh2bOaLuncs6ATMErKWN/mVuFHuUWEqPDRyy5Ky40Cu9S40nUq5w== + dependencies: + "@lerna/child-process" "6.1.0" + "@lerna/describe-ref" "6.1.0" minimatch "^3.0.4" - npmlog "^4.1.2" - slash "^1.0.0" + npmlog "^6.0.2" + slash "^3.0.0" -"@lerna/command@3.13.3": - version "3.13.3" - resolved "https://registry.yarnpkg.com/@lerna/command/-/command-3.13.3.tgz#5b20b3f507224573551039e0460bc36c39f7e9d1" - integrity sha512-WHFIQCubJV0T8gSLRNr6exZUxTswrh+iAtJCb86SE0Sa+auMPklE8af7w2Yck5GJfewmxSjke3yrjNxQrstx7w== - dependencies: - "@lerna/child-process" "3.13.3" - "@lerna/package-graph" "3.13.0" - "@lerna/project" "3.13.1" - "@lerna/validation-error" "3.13.0" - "@lerna/write-log-file" "3.13.0" +"@lerna/command@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/command/-/command-6.1.0.tgz#bcb12516f2c181822b3b5be46c18eadc9b61e885" + integrity sha512-OnMqBDaEBY0C8v9CXIWFbGGKgsiUtZrnKVvQRbupMSZDKMpVGWIUd3X98Is9j9MAmk1ynhBMWE9Fwai5ML/mcA== + dependencies: + "@lerna/child-process" "6.1.0" + "@lerna/package-graph" "6.1.0" + "@lerna/project" "6.1.0" + "@lerna/validation-error" "6.1.0" + "@lerna/write-log-file" "6.1.0" + clone-deep "^4.0.1" dedent "^0.7.0" - execa "^1.0.0" - is-ci "^1.0.10" - lodash "^4.17.5" - npmlog "^4.1.2" + execa "^5.0.0" + is-ci "^2.0.0" + npmlog "^6.0.2" -"@lerna/conventional-commits@3.13.0": - version "3.13.0" - resolved "https://registry.yarnpkg.com/@lerna/conventional-commits/-/conventional-commits-3.13.0.tgz#877aa225ca34cca61c31ea02a5a6296af74e1144" - integrity sha512-BeAgcNXuocmLhPxnmKU2Vy8YkPd/Uo+vu2i/p3JGsUldzrPC8iF3IDxH7fuXpEFN2Nfogu7KHachd4tchtOppA== +"@lerna/conventional-commits@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/conventional-commits/-/conventional-commits-6.1.0.tgz#1157bb66d84d48880dc5c5026d743cedf0f47094" + integrity sha512-Tipo3cVr8mNVca4btzrCIzct59ZJWERT8/ZCZ/TQWuI4huUJZs6LRofLtB0xsGJAVZ7Vz2WRXAeH4XYgeUxutQ== dependencies: - "@lerna/validation-error" "3.13.0" - conventional-changelog-angular "^5.0.3" - conventional-changelog-core "^3.1.6" - conventional-recommended-bump "^4.0.4" - fs-extra "^7.0.0" - get-stream "^4.0.0" - npm-package-arg "^6.1.0" - npmlog "^4.1.2" - pify "^3.0.0" - semver "^5.5.0" + "@lerna/validation-error" "6.1.0" + conventional-changelog-angular "^5.0.12" + conventional-changelog-core "^4.2.4" + conventional-recommended-bump "^6.1.0" + fs-extra "^9.1.0" + get-stream "^6.0.0" + npm-package-arg "8.1.1" + npmlog "^6.0.2" + pify "^5.0.0" + semver "^7.3.4" -"@lerna/create-symlink@3.13.0": - version "3.13.0" - resolved "https://registry.yarnpkg.com/@lerna/create-symlink/-/create-symlink-3.13.0.tgz#e01133082fe040779712c960683cb3a272b67809" - integrity sha512-PTvg3jAAJSAtLFoZDsuTMv1wTOC3XYIdtg54k7uxIHsP8Ztpt+vlilY/Cni0THAqEMHvfiToel76Xdta4TU21Q== +"@lerna/create-symlink@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/create-symlink/-/create-symlink-6.1.0.tgz#d4260831f5d10abc0c70f0a8f39bea91db87e640" + integrity sha512-ulMa5OUJEwEWBHSgCUNGxrcsJllq1YMYWqhufvIigmMPJ0Zv3TV1Hha5i2MsqLJAakxtW0pNuwdutkUTtUdgxQ== dependencies: - cmd-shim "^2.0.2" - fs-extra "^7.0.0" - npmlog "^4.1.2" + cmd-shim "^5.0.0" + fs-extra "^9.1.0" + npmlog "^6.0.2" -"@lerna/create@3.13.3": - version "3.13.3" - resolved "https://registry.yarnpkg.com/@lerna/create/-/create-3.13.3.tgz#6ded142c54b7f3cea86413c3637b067027b7f55d" - integrity sha512-4M5xT1AyUMwt1gCDph4BfW3e6fZmt0KjTa3FoXkUotf/w/eqTsc2IQ+ULz2+gOFQmtuNbqIZEOK3J4P9ArJJ/A== +"@lerna/create@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/create/-/create-6.1.0.tgz#cde219da46a7c5062c558366b4ffce2134f13845" + integrity sha512-ZqlknXu0L29cV5mcfNgBLl+1RbKTWmNk8mj545zgXc7qQDgmrY+EVvrs8Cirey8C7bBpVkzP7Brzze0MSoB4rQ== dependencies: - "@lerna/child-process" "3.13.3" - "@lerna/command" "3.13.3" - "@lerna/npm-conf" "3.13.0" - "@lerna/validation-error" "3.13.0" - camelcase "^5.0.0" + "@lerna/child-process" "6.1.0" + "@lerna/command" "6.1.0" + "@lerna/npm-conf" "6.1.0" + "@lerna/validation-error" "6.1.0" dedent "^0.7.0" - fs-extra "^7.0.0" - globby "^8.0.1" - init-package-json "^1.10.3" - npm-package-arg "^6.1.0" - p-reduce "^1.0.0" - pacote "^9.5.0" - pify "^3.0.0" - semver "^5.5.0" - slash "^1.0.0" - validate-npm-package-license "^3.0.3" - validate-npm-package-name "^3.0.0" - whatwg-url "^7.0.0" + fs-extra "^9.1.0" + init-package-json "^3.0.2" + npm-package-arg "8.1.1" + p-reduce "^2.1.0" + pacote "^13.6.1" + pify "^5.0.0" + semver "^7.3.4" + slash "^3.0.0" + validate-npm-package-license "^3.0.4" + validate-npm-package-name "^4.0.0" + yargs-parser "20.2.4" -"@lerna/describe-ref@3.13.3": - version "3.13.3" - resolved "https://registry.yarnpkg.com/@lerna/describe-ref/-/describe-ref-3.13.3.tgz#13318513613f6a407d37fc5dc025ec2cfb705606" - integrity sha512-5KcLTvjdS4gU5evW8ESbZ0BF44NM5HrP3dQNtWnOUSKJRgsES8Gj0lq9AlB2+YglZfjEftFT03uOYOxnKto4Uw== +"@lerna/describe-ref@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/describe-ref/-/describe-ref-6.1.0.tgz#60f0b8297b912aa5fe5e6ab8ef6c4127813681a7" + integrity sha512-0RQAYnxBaMz1SrEb/rhfR+8VeZx5tvCNYKRee5oXIDZdQ2c6/EPyrKCp3WcqiuOWY50SfGOVfxJEcxpK8Y3FNA== dependencies: - "@lerna/child-process" "3.13.3" - npmlog "^4.1.2" + "@lerna/child-process" "6.1.0" + npmlog "^6.0.2" -"@lerna/diff@3.13.3": - version "3.13.3" - resolved "https://registry.yarnpkg.com/@lerna/diff/-/diff-3.13.3.tgz#883cb3a83a956dbfc2c17bc9a156468a5d3fae17" - integrity sha512-/DRS2keYbnKaAC+5AkDyZRGkP/kT7v1GlUS0JGZeiRDPQ1H6PzhX09EgE5X6nj0Ytrm0sUasDeN++CDVvgaI+A== +"@lerna/diff@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/diff/-/diff-6.1.0.tgz#bfa9bc35894d88a33fa0a3a5787082dea45d8cb2" + integrity sha512-GhP+jPDbcp9QcAMSAjFn4lzM8MKpLR1yt5jll+zUD831U1sL0I5t8HUosFroe5MoRNffEL/jHuI3SbC3jjqWjQ== dependencies: - "@lerna/child-process" "3.13.3" - "@lerna/command" "3.13.3" - "@lerna/validation-error" "3.13.0" - npmlog "^4.1.2" + "@lerna/child-process" "6.1.0" + "@lerna/command" "6.1.0" + "@lerna/validation-error" "6.1.0" + npmlog "^6.0.2" -"@lerna/exec@3.13.3": - version "3.13.3" - resolved "https://registry.yarnpkg.com/@lerna/exec/-/exec-3.13.3.tgz#5d2eda3f6e584f2f15b115e8a4b5bc960ba5de85" - integrity sha512-c0bD4XqM96CTPV8+lvkxzE7mkxiFyv/WNM4H01YvvbFAJzk+S4Y7cBtRkIYFTfkFZW3FLo8pEgtG1ONtIdM+tg== - dependencies: - "@lerna/batch-packages" "3.13.0" - "@lerna/child-process" "3.13.3" - "@lerna/command" "3.13.3" - "@lerna/filter-options" "3.13.3" - "@lerna/run-parallel-batches" "3.13.0" - "@lerna/validation-error" "3.13.0" +"@lerna/exec@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/exec/-/exec-6.1.0.tgz#a2d165576471ff61e33c49952d40a5dbc36fc78f" + integrity sha512-Ej6WlPHXLF6hZHsfD+J/dxeuTrnc0HIfIXR1DU//msHW5RNCdi9+I7StwreCAQH/dLEsdBjPg5chNmuj2JLQRg== + dependencies: + "@lerna/child-process" "6.1.0" + "@lerna/command" "6.1.0" + "@lerna/filter-options" "6.1.0" + "@lerna/profiler" "6.1.0" + "@lerna/run-topologically" "6.1.0" + "@lerna/validation-error" "6.1.0" + p-map "^4.0.0" -"@lerna/filter-options@3.13.3": - version "3.13.3" - resolved "https://registry.yarnpkg.com/@lerna/filter-options/-/filter-options-3.13.3.tgz#aa42a4ab78837b8a6c4278ba871d27e92d77c54f" - integrity sha512-DbtQX4eRgrBz1wCFWRP99JBD7ODykYme9ykEK79+RrKph40znhJQRlLg4idogj6IsUEzwo1OHjihCzSfnVo6Cg== +"@lerna/filter-options@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/filter-options/-/filter-options-6.1.0.tgz#f4ee65d0db0273ce490ce6c72c9dbb1d23268ca6" + integrity sha512-kPf92Z7uLsR6MUiXnyXWebaUWArLa15wLfpfTwIp5H3MNk1lTbuG7QnrxE7OxQj+ozFmBvXeV9fuwfLsYTfmOw== dependencies: - "@lerna/collect-updates" "3.13.3" - "@lerna/filter-packages" "3.13.0" + "@lerna/collect-updates" "6.1.0" + "@lerna/filter-packages" "6.1.0" dedent "^0.7.0" + npmlog "^6.0.2" -"@lerna/filter-packages@3.13.0": - version "3.13.0" - resolved "https://registry.yarnpkg.com/@lerna/filter-packages/-/filter-packages-3.13.0.tgz#f5371249e7e1a15928e5e88c544a242e0162c21c" - integrity sha512-RWiZWyGy3Mp7GRVBn//CacSnE3Kw82PxE4+H6bQ3pDUw/9atXn7NRX+gkBVQIYeKamh7HyumJtyOKq3Pp9BADQ== +"@lerna/filter-packages@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/filter-packages/-/filter-packages-6.1.0.tgz#1ddac63a6ffdf5f058d206be5adfb39ad7aaf4f9" + integrity sha512-zW2avsZHs/ITE/37AEMhegGVHjiD0rgNk9bguNDfz6zaPa90UaW6PWDH6Tf4ThPRlbkl2Go48N3bFYHYSJKbcw== dependencies: - "@lerna/validation-error" "3.13.0" - multimatch "^2.1.0" - npmlog "^4.1.2" + "@lerna/validation-error" "6.1.0" + multimatch "^5.0.0" + npmlog "^6.0.2" -"@lerna/get-npm-exec-opts@3.13.0": - version "3.13.0" - resolved "https://registry.yarnpkg.com/@lerna/get-npm-exec-opts/-/get-npm-exec-opts-3.13.0.tgz#d1b552cb0088199fc3e7e126f914e39a08df9ea5" - integrity sha512-Y0xWL0rg3boVyJk6An/vurKzubyJKtrxYv2sj4bB8Mc5zZ3tqtv0ccbOkmkXKqbzvNNF7VeUt1OJ3DRgtC/QZw== +"@lerna/get-npm-exec-opts@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/get-npm-exec-opts/-/get-npm-exec-opts-6.1.0.tgz#22351e2ebc4adbef21ca4b86187278e15e4cb38a" + integrity sha512-10Pdf+W0z7RT34o0SWlf+WVzz2/WbnTIJ1tQqXvXx6soj2L/xGLhOPvhJiKNtl4WlvUiO/zQ91yb83ESP4TZaA== dependencies: - npmlog "^4.1.2" + npmlog "^6.0.2" -"@lerna/get-packed@3.13.0": - version "3.13.0" - resolved "https://registry.yarnpkg.com/@lerna/get-packed/-/get-packed-3.13.0.tgz#335e40d77f3c1855aa248587d3e0b2d8f4b06e16" - integrity sha512-EgSim24sjIjqQDC57bgXD9l22/HCS93uQBbGpkzEOzxAVzEgpZVm7Fm1t8BVlRcT2P2zwGnRadIvxTbpQuDPTg== +"@lerna/get-packed@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/get-packed/-/get-packed-6.1.0.tgz#b6d1c1dd1e068212e784b8dfc2e5fe64741ea8db" + integrity sha512-lg0wPpV0wPekcD0mebJp619hMxsOgbZDOH5AkL/bCR217391eha0iPhQ0dU/G0Smd2vv6Cg443+J5QdI4LGRTg== dependencies: - fs-extra "^7.0.0" - ssri "^6.0.1" - tar "^4.4.8" + fs-extra "^9.1.0" + ssri "^9.0.1" + tar "^6.1.0" -"@lerna/github-client@3.13.3": - version "3.13.3" - resolved "https://registry.yarnpkg.com/@lerna/github-client/-/github-client-3.13.3.tgz#bcf9b4ff40bdd104cb40cd257322f052b41bb9ce" - integrity sha512-fcJkjab4kX0zcLLSa/DCUNvU3v8wmy2c1lhdIbL7s7gABmDcV0QZq93LhnEee3VkC9UpnJ6GKG4EkD7eIifBnA== +"@lerna/github-client@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/github-client/-/github-client-6.1.0.tgz#cd33743e4529a0b822ae6716cb4b981e1d8ffe8f" + integrity sha512-+/4PtDgsjt0VRRZtOCN2Piyu0asU/16gSZZy/opVb8dlT44lTrH/ZghrJLE4tSL8Nuv688kx0kSgbUG8BY54jQ== dependencies: - "@lerna/child-process" "3.13.3" - "@octokit/plugin-enterprise-rest" "^2.1.1" - "@octokit/rest" "^16.16.0" - git-url-parse "^11.1.2" - npmlog "^4.1.2" + "@lerna/child-process" "6.1.0" + "@octokit/plugin-enterprise-rest" "^6.0.1" + "@octokit/rest" "^19.0.3" + git-url-parse "^13.1.0" + npmlog "^6.0.2" -"@lerna/global-options@3.13.0": - version "3.13.0" - resolved "https://registry.yarnpkg.com/@lerna/global-options/-/global-options-3.13.0.tgz#217662290db06ad9cf2c49d8e3100ee28eaebae1" - integrity sha512-SlZvh1gVRRzYLVluz9fryY1nJpZ0FHDGB66U9tFfvnnxmueckRQxLopn3tXj3NU1kc3QANT2I5BsQkOqZ4TEFQ== - -"@lerna/has-npm-version@3.13.3": - version "3.13.3" - resolved "https://registry.yarnpkg.com/@lerna/has-npm-version/-/has-npm-version-3.13.3.tgz#167e3f602a2fb58f84f93cf5df39705ca6432a2d" - integrity sha512-mQzoghRw4dBg0R9FFfHrj0TH0glvXyzdEZmYZ8Isvx5BSuEEwpsryoywuZSdppcvLu8o7NAdU5Tac8cJ/mT52w== +"@lerna/gitlab-client@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/gitlab-client/-/gitlab-client-6.1.0.tgz#bbcbf80d937e5980798ac1e0edd1f769101057d8" + integrity sha512-fUI/ppXzxJafN9ceSl+FDgsYvu3iTsO6UW0WTD63pS32CfM+PiCryLQHzuc4RkyVW8WQH3aCR/GbaKCqbu52bw== dependencies: - "@lerna/child-process" "3.13.3" - semver "^5.5.0" + node-fetch "^2.6.1" + npmlog "^6.0.2" -"@lerna/import@3.13.4": - version "3.13.4" - resolved "https://registry.yarnpkg.com/@lerna/import/-/import-3.13.4.tgz#e9a1831b8fed33f3cbeab3b84c722c9371a2eaf7" - integrity sha512-dn6eNuPEljWsifBEzJ9B6NoaLwl/Zvof7PBUPA4hRyRlqG5sXRn6F9DnusMTovvSarbicmTURbOokYuotVWQQA== +"@lerna/global-options@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/global-options/-/global-options-6.1.0.tgz#268e1de924369102e47babd9288086764ec6f9e6" + integrity sha512-1OyJ/N1XJh3ZAy8S20c6th9C4yBm/k3bRIdC+z0XxpDaHwfNt8mT9kUIDt6AIFCUvVKjSwnIsMHwhzXqBnwYSA== + +"@lerna/has-npm-version@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/has-npm-version/-/has-npm-version-6.1.0.tgz#a5d960213d1a7ca5374eb3c551a17b322b9a9e62" + integrity sha512-up5PVuP6BmKQ5/UgH/t2c5B1q4HhjwW3/bqbNayX6V0qNz8OijnMYvEUbxFk8fOdeN41qVnhAk0Tb5kbdtYh2A== dependencies: - "@lerna/child-process" "3.13.3" - "@lerna/command" "3.13.3" - "@lerna/prompt" "3.13.0" - "@lerna/pulse-till-done" "3.13.0" - "@lerna/validation-error" "3.13.0" + "@lerna/child-process" "6.1.0" + semver "^7.3.4" + +"@lerna/import@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/import/-/import-6.1.0.tgz#1c64281e3431c43c9cd140b66a6a51427afe7095" + integrity sha512-xsBhiKLUavATR32dAFL+WFY0yuab0hsM1eztKtRKk4wy7lSyxRfA5EIUcNCsLXx2xaDOKoMncCTXgNcpeYuqcQ== + dependencies: + "@lerna/child-process" "6.1.0" + "@lerna/command" "6.1.0" + "@lerna/prompt" "6.1.0" + "@lerna/pulse-till-done" "6.1.0" + "@lerna/validation-error" "6.1.0" dedent "^0.7.0" - fs-extra "^7.0.0" - p-map-series "^1.0.0" + fs-extra "^9.1.0" + p-map-series "^2.1.0" -"@lerna/init@3.13.3": - version "3.13.3" - resolved "https://registry.yarnpkg.com/@lerna/init/-/init-3.13.3.tgz#ebd522fee9b9d7d3b2dacb0261eaddb4826851ff" - integrity sha512-bK/mp0sF6jT0N+c+xrbMCqN4xRoiZCXQzlYsyACxPK99KH/mpHv7hViZlTYUGlYcymtew6ZC770miv5A9wF9hA== +"@lerna/info@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/info/-/info-6.1.0.tgz#a5d66a9c1f18398dc020a6f6073c399013081587" + integrity sha512-CsrWdW/Wyb4kcvHSnrsm7KYWFvjUNItu+ryeyWBZJtWYQOv45jNmWix6j2L4/w1+mMlWMjsfLmBscg82UBrF5w== dependencies: - "@lerna/child-process" "3.13.3" - "@lerna/command" "3.13.3" - fs-extra "^7.0.0" - p-map "^1.2.0" - write-json-file "^2.3.0" + "@lerna/command" "6.1.0" + "@lerna/output" "6.1.0" + envinfo "^7.7.4" -"@lerna/link@3.13.3": - version "3.13.3" - resolved "https://registry.yarnpkg.com/@lerna/link/-/link-3.13.3.tgz#11124d4a0c8d0b79752fbda3babedfd62dd57847" - integrity sha512-IHhtdhA0KlIdevCsq6WHkI2rF3lHWHziJs2mlrEWAKniVrFczbELON1KJAgdJS1k3kAP/WeWVqmIYZ2hJDxMvg== +"@lerna/init@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/init/-/init-6.1.0.tgz#b178775693b9c38c0f3fe3300eeb574cf76e0297" + integrity sha512-z8oUeVjn+FQYAtepAw6G47cGodLyBAyNoEjO3IsJjQLWE1yH3r83L2sjyD/EckgR3o2VTEzrKo4ArhxLp2mNmg== dependencies: - "@lerna/command" "3.13.3" - "@lerna/package-graph" "3.13.0" - "@lerna/symlink-dependencies" "3.13.0" - p-map "^1.2.0" - slash "^1.0.0" + "@lerna/child-process" "6.1.0" + "@lerna/command" "6.1.0" + "@lerna/project" "6.1.0" + fs-extra "^9.1.0" + p-map "^4.0.0" + write-json-file "^4.3.0" -"@lerna/list@3.13.3": - version "3.13.3" - resolved "https://registry.yarnpkg.com/@lerna/list/-/list-3.13.3.tgz#fa93864d43cadeb4cd540a4e78a52886c57dbe74" - integrity sha512-rLRDsBCkydMq2FL6WY1J/elvnXIjxxRtb72lfKHdvDEqVdquT5Qgt9ci42hwjmcocFwWcFJgF6BZozj5pbc13A== - dependencies: - "@lerna/command" "3.13.3" - "@lerna/filter-options" "3.13.3" - "@lerna/listable" "3.13.0" - "@lerna/output" "3.13.0" - -"@lerna/listable@3.13.0": - version "3.13.0" - resolved "https://registry.yarnpkg.com/@lerna/listable/-/listable-3.13.0.tgz#babc18442c590b549cf0966d20d75fea066598d4" - integrity sha512-liYJ/WBUYP4N4MnSVZuLUgfa/jy3BZ02/1Om7xUY09xGVSuNVNEeB8uZUMSC+nHqFHIsMPZ8QK9HnmZb1E/eTA== - dependencies: - "@lerna/batch-packages" "3.13.0" - chalk "^2.3.1" - columnify "^1.5.4" - -"@lerna/log-packed@3.13.0": - version "3.13.0" - resolved "https://registry.yarnpkg.com/@lerna/log-packed/-/log-packed-3.13.0.tgz#497b5f692a8d0e3f669125da97b0dadfd9e480f3" - integrity sha512-Rmjrcz+6aM6AEcEVWmurbo8+AnHOvYtDpoeMMJh9IZ9SmZr2ClXzmD7wSvjTQc8BwOaiWjjC/ukcT0UYA2m7wg== - dependencies: - byte-size "^4.0.3" - columnify "^1.5.4" +"@lerna/link@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/link/-/link-6.1.0.tgz#f6f0cfd0b02aecdeb304ce614e4e4e89fe0a3ad5" + integrity sha512-7OD2lYNQHl6Kl1KYmplt8KoWjVHdiaqpYqwD38AwcB09YN58nGmo4aJgC12Fdx8DSNjkumgM0ROg/JOjMCTIzQ== + dependencies: + "@lerna/command" "6.1.0" + "@lerna/package-graph" "6.1.0" + "@lerna/symlink-dependencies" "6.1.0" + "@lerna/validation-error" "6.1.0" + p-map "^4.0.0" + slash "^3.0.0" + +"@lerna/list@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/list/-/list-6.1.0.tgz#a7625bceb5224c4bf1154e715c07ea29f9698bac" + integrity sha512-7/g2hjizkvVnBGpVm+qC7lUFGhZ/0GIMUbGQwnE6yXDGm8yP9aEcNVkU4JGrDWW+uIklf9oodnMHaLXd/FJe6Q== + dependencies: + "@lerna/command" "6.1.0" + "@lerna/filter-options" "6.1.0" + "@lerna/listable" "6.1.0" + "@lerna/output" "6.1.0" + +"@lerna/listable@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/listable/-/listable-6.1.0.tgz#2510045fde7bc568b18172a5d24372a719bb5c4c" + integrity sha512-3KZ9lQ9AtNfGNH/mYJYaMKCiF2EQvLLBGYkWHeIzIs6foegcZNXe0Cyv3LNXuo5WslMNr5RT4wIgy3BOoAxdtg== + dependencies: + "@lerna/query-graph" "6.1.0" + chalk "^4.1.0" + columnify "^1.6.0" + +"@lerna/log-packed@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/log-packed/-/log-packed-6.1.0.tgz#18ae946e8b7881f2fc5b973cc6682cc599b1759b" + integrity sha512-Sq2HZJAcPuoNeEHeIutcPYQCyWBxLyVGvEhgsP3xTe6XkBGQCG8piCp9wX+sc2zT+idPdpI6qLqdh85yYIMMhA== + dependencies: + byte-size "^7.0.0" + columnify "^1.6.0" has-unicode "^2.0.1" - npmlog "^4.1.2" + npmlog "^6.0.2" -"@lerna/npm-conf@3.13.0": - version "3.13.0" - resolved "https://registry.yarnpkg.com/@lerna/npm-conf/-/npm-conf-3.13.0.tgz#6b434ed75ff757e8c14381b9bbfe5d5ddec134a7" - integrity sha512-Jg2kANsGnhg+fbPEzE0X9nX5oviEAvWj0nYyOkcE+cgWuT7W0zpnPXC4hA4C5IPQGhwhhh0IxhWNNHtjTuw53g== +"@lerna/npm-conf@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/npm-conf/-/npm-conf-6.1.0.tgz#79697260c9d14ffb9d892927f37fcde75b89ec58" + integrity sha512-+RD3mmJe9XSQj7Diibs0+UafAHPcrFCd29ODpDI+tzYl4MmYZblfrlL6mbSCiVYCZQneQ8Uku3P0r+DlbYBaFw== dependencies: - config-chain "^1.1.11" - pify "^3.0.0" + config-chain "^1.1.12" + pify "^5.0.0" -"@lerna/npm-dist-tag@3.13.0": - version "3.13.0" - resolved "https://registry.yarnpkg.com/@lerna/npm-dist-tag/-/npm-dist-tag-3.13.0.tgz#49ecbe0e82cbe4ad4a8ea6de112982bf6c4e6cd4" - integrity sha512-mcuhw34JhSRFrbPn0vedbvgBTvveG52bR2lVE3M3tfE8gmR/cKS/EJFO4AUhfRKGCTFn9rjaSEzlFGYV87pemQ== +"@lerna/npm-dist-tag@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/npm-dist-tag/-/npm-dist-tag-6.1.0.tgz#29f843aa628687a29dc3a9b905dd3002db7a3820" + integrity sha512-1zo+Yww/lvWJWZnEXpke9dZSb5poDzhUM/pQNqAQYSlbZ96o18SuCR6TEi5isMPiw63Aq1MMzbUqttQfJ11EOA== dependencies: - figgy-pudding "^3.5.1" - npm-package-arg "^6.1.0" - npm-registry-fetch "^3.9.0" - npmlog "^4.1.2" + "@lerna/otplease" "6.1.0" + npm-package-arg "8.1.1" + npm-registry-fetch "^13.3.0" + npmlog "^6.0.2" -"@lerna/npm-install@3.13.3": - version "3.13.3" - resolved "https://registry.yarnpkg.com/@lerna/npm-install/-/npm-install-3.13.3.tgz#9b09852732e51c16d2e060ff2fd8bfbbb49cf7ba" - integrity sha512-7Jig9MLpwAfcsdQ5UeanAjndChUjiTjTp50zJ+UZz4CbIBIDhoBehvNMTCL2G6pOEC7sGEg6sAqJINAqred6Tg== +"@lerna/npm-install@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/npm-install/-/npm-install-6.1.0.tgz#b75d1f152540a144bd6c81586a9f6010ed7f3046" + integrity sha512-1SHmOHZA1YJuUctLQBRjA2+yMp+UNYdOBsFb3xUVT7MjWnd1Zl0toT3jxGu96RNErD9JKkk/cGo/Aq+DU3s9pg== dependencies: - "@lerna/child-process" "3.13.3" - "@lerna/get-npm-exec-opts" "3.13.0" - fs-extra "^7.0.0" - npm-package-arg "^6.1.0" - npmlog "^4.1.2" - signal-exit "^3.0.2" - write-pkg "^3.1.0" + "@lerna/child-process" "6.1.0" + "@lerna/get-npm-exec-opts" "6.1.0" + fs-extra "^9.1.0" + npm-package-arg "8.1.1" + npmlog "^6.0.2" + signal-exit "^3.0.3" + write-pkg "^4.0.0" -"@lerna/npm-publish@3.13.2": - version "3.13.2" - resolved "https://registry.yarnpkg.com/@lerna/npm-publish/-/npm-publish-3.13.2.tgz#ad713ca6f91a852687d7d0e1bda7f9c66df21768" - integrity sha512-HMucPyEYZfom5tRJL4GsKBRi47yvSS2ynMXYxL3kO0ie+j9J7cb0Ir8NmaAMEd3uJWJVFCPuQarehyfTDZsSxg== +"@lerna/npm-publish@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/npm-publish/-/npm-publish-6.1.0.tgz#8fe561e639e6a06380354271aeca7cbc39acf7dd" + integrity sha512-N0LdR1ImZQw1r4cYaKtVbBhBPtj4Zu9NbvygzizEP5HuTfxZmE1Ans3w93Kks9VTXZXob8twNbXnzBwzTyEpEA== dependencies: - "@lerna/run-lifecycle" "3.13.0" - figgy-pudding "^3.5.1" - fs-extra "^7.0.0" - libnpmpublish "^1.1.1" - npm-package-arg "^6.1.0" - npmlog "^4.1.2" - pify "^3.0.0" - read-package-json "^2.0.13" + "@lerna/otplease" "6.1.0" + "@lerna/run-lifecycle" "6.1.0" + fs-extra "^9.1.0" + libnpmpublish "^6.0.4" + npm-package-arg "8.1.1" + npmlog "^6.0.2" + pify "^5.0.0" + read-package-json "^5.0.1" -"@lerna/npm-run-script@3.13.3": - version "3.13.3" - resolved "https://registry.yarnpkg.com/@lerna/npm-run-script/-/npm-run-script-3.13.3.tgz#9bb6389ed70cd506905d6b05b6eab336b4266caf" - integrity sha512-qR4o9BFt5hI8Od5/DqLalOJydnKpiQFEeN0h9xZi7MwzuX1Ukwh3X22vqsX4YRbipIelSFtrDzleNVUm5jj0ow== +"@lerna/npm-run-script@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/npm-run-script/-/npm-run-script-6.1.0.tgz#bc5bd414ee9696168d88d8ce78f8e8b715967100" + integrity sha512-7p13mvdxdY5+VqWvvtMsMDeyCRs0PrrTmSHRO+FKuLQuGhBvUo05vevcMEOQNDvEvl/tXPrOVbeGCiGubYTCLg== dependencies: - "@lerna/child-process" "3.13.3" - "@lerna/get-npm-exec-opts" "3.13.0" - npmlog "^4.1.2" + "@lerna/child-process" "6.1.0" + "@lerna/get-npm-exec-opts" "6.1.0" + npmlog "^6.0.2" -"@lerna/output@3.13.0": - version "3.13.0" - resolved "https://registry.yarnpkg.com/@lerna/output/-/output-3.13.0.tgz#3ded7cc908b27a9872228a630d950aedae7a4989" - integrity sha512-7ZnQ9nvUDu/WD+bNsypmPG5MwZBwu86iRoiW6C1WBuXXDxM5cnIAC1m2WxHeFnjyMrYlRXM9PzOQ9VDD+C15Rg== +"@lerna/otplease@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/otplease/-/otplease-6.1.0.tgz#d25dbe2d867215b69f06de12ab4ff559d83d1d01" + integrity sha512-gqSE6IbaD4IeNJePkaDLaFLoGp0Ceu35sn7z0AHAOoHiQGGorOmvM+h1Md3xZZRSXQmY9LyJVhG5eRa38SoG4g== dependencies: - npmlog "^4.1.2" + "@lerna/prompt" "6.1.0" -"@lerna/pack-directory@3.13.1": - version "3.13.1" - resolved "https://registry.yarnpkg.com/@lerna/pack-directory/-/pack-directory-3.13.1.tgz#5ad4d0945f86a648f565e24d53c1e01bb3a912d1" - integrity sha512-kXnyqrkQbCIZOf1054N88+8h0ItC7tUN5v9ca/aWpx298gsURpxUx/1TIKqijL5TOnHMyIkj0YJmnH/PyBVLKA== +"@lerna/output@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/output/-/output-6.1.0.tgz#d470146c6ee8ee063fd416081c1ca64fb132c4d8" + integrity sha512-mgCIzLKIuroytXuxjTB689ERtpfgyNXW0rMv9WHOa6ufQc+QJPjh3L4jVsOA0l+/OxZyi97PUXotduNj+0cbnA== dependencies: - "@lerna/get-packed" "3.13.0" - "@lerna/package" "3.13.0" - "@lerna/run-lifecycle" "3.13.0" - figgy-pudding "^3.5.1" - npm-packlist "^1.4.1" - npmlog "^4.1.2" - tar "^4.4.8" - temp-write "^3.4.0" + npmlog "^6.0.2" -"@lerna/package-graph@3.13.0": - version "3.13.0" - resolved "https://registry.yarnpkg.com/@lerna/package-graph/-/package-graph-3.13.0.tgz#607062f8d2ce22b15f8d4a0623f384736e67f760" - integrity sha512-3mRF1zuqFE1HEFmMMAIggXy+f+9cvHhW/jzaPEVyrPNLKsyfJQtpTNzeI04nfRvbAh+Gd2aNksvaW/w3xGJnnw== +"@lerna/pack-directory@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/pack-directory/-/pack-directory-6.1.0.tgz#3252ba7250d826b9922238c775abf5004e7580c4" + integrity sha512-Xsixqm2nkGXs9hvq08ClbGpRlCYnlBV4TwSrLttIDL712RlyXoPe2maJzTUqo9OXBbOumFSahUEInCMT2OS05g== + dependencies: + "@lerna/get-packed" "6.1.0" + "@lerna/package" "6.1.0" + "@lerna/run-lifecycle" "6.1.0" + "@lerna/temp-write" "6.1.0" + npm-packlist "^5.1.1" + npmlog "^6.0.2" + tar "^6.1.0" + +"@lerna/package-graph@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/package-graph/-/package-graph-6.1.0.tgz#2373617605f48f53b5fa9d13188838b6c09022b0" + integrity sha512-yGyxd/eHTDjkpnBbDhTV0hwKF+i01qZc+6/ko65wOsh8xtgqpQeE6mtdgbvsLKcuMcIQ7PDy1ntyIv9phg14gQ== dependencies: - "@lerna/validation-error" "3.13.0" - npm-package-arg "^6.1.0" - semver "^5.5.0" + "@lerna/prerelease-id-from-version" "6.1.0" + "@lerna/validation-error" "6.1.0" + npm-package-arg "8.1.1" + npmlog "^6.0.2" + semver "^7.3.4" -"@lerna/package@3.13.0": - version "3.13.0" - resolved "https://registry.yarnpkg.com/@lerna/package/-/package-3.13.0.tgz#4baeebc49a57fc9b31062cc59f5ee38384429fc8" - integrity sha512-kSKO0RJQy093BufCQnkhf1jB4kZnBvL7kK5Ewolhk5gwejN+Jofjd8DGRVUDUJfQ0CkW1o6GbUeZvs8w8VIZDg== +"@lerna/package@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/package/-/package-6.1.0.tgz#e9e33876c0509a86c1b676045b19fd3f7f1c77e2" + integrity sha512-PyNFtdH2IcLasp/nyMDshmeXotriOSlhbeFIxhdl1XuGj5v1so3utMSOrJMO5kzZJQg5zyx8qQoxL+WH/hkrVQ== dependencies: - load-json-file "^4.0.0" - npm-package-arg "^6.1.0" - write-pkg "^3.1.0" + load-json-file "^6.2.0" + npm-package-arg "8.1.1" + write-pkg "^4.0.0" -"@lerna/project@3.13.1": - version "3.13.1" - resolved "https://registry.yarnpkg.com/@lerna/project/-/project-3.13.1.tgz#bce890f60187bd950bcf36c04b5260642e295e79" - integrity sha512-/GoCrpsCCTyb9sizk1+pMBrIYchtb+F1uCOn3cjn9yenyG/MfYEnlfrbV5k/UDud0Ei75YBLbmwCbigHkAKazQ== +"@lerna/prerelease-id-from-version@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/prerelease-id-from-version/-/prerelease-id-from-version-6.1.0.tgz#4ee5beeef4e81d77001e94ec5613c140b6615616" + integrity sha512-ngC4I6evvZztB6aOaSDEnhUgRTlqX3TyBXwWwLGTOXCPaCQBTPaLNokhmRdJ+ZVdZ4iHFbzEDSL07ubZrYUcmQ== + dependencies: + semver "^7.3.4" + +"@lerna/profiler@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/profiler/-/profiler-6.1.0.tgz#aae2249f1a39c79db72a548ce50bf32f86a0f3a5" + integrity sha512-WFDQNpuqPqMJLg8llvrBHF8Ib5Asgp23lMeNUe89T62NUX6gkjVBTYdjsduxM0tZH6Pa0GAGaQcha97P6fxfdQ== dependencies: - "@lerna/package" "3.13.0" - "@lerna/validation-error" "3.13.0" - cosmiconfig "^5.1.0" + fs-extra "^9.1.0" + npmlog "^6.0.2" + upath "^2.0.1" + +"@lerna/project@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/project/-/project-6.1.0.tgz#605afe28fb15d8b8b890fafe0ec1da2700964056" + integrity sha512-EOkfjjrTM16c3GUxGqcfYD2stV35p9mBEmkF41NPmyjfbzjol/irDF1r6Q7BsQSRsdClMJRCeZ168xdSxC2X0A== + dependencies: + "@lerna/package" "6.1.0" + "@lerna/validation-error" "6.1.0" + cosmiconfig "^7.0.0" dedent "^0.7.0" - dot-prop "^4.2.0" - glob-parent "^3.1.0" - globby "^8.0.1" - load-json-file "^4.0.0" - npmlog "^4.1.2" - p-map "^1.2.0" - resolve-from "^4.0.0" - write-json-file "^2.3.0" + dot-prop "^6.0.1" + glob-parent "^5.1.1" + globby "^11.0.2" + js-yaml "^4.1.0" + load-json-file "^6.2.0" + npmlog "^6.0.2" + p-map "^4.0.0" + resolve-from "^5.0.0" + write-json-file "^4.3.0" -"@lerna/prompt@3.13.0": - version "3.13.0" - resolved "https://registry.yarnpkg.com/@lerna/prompt/-/prompt-3.13.0.tgz#53571462bb3f5399cc1ca6d335a411fe093426a5" - integrity sha512-P+lWSFokdyvYpkwC3it9cE0IF2U5yy2mOUbGvvE4iDb9K7TyXGE+7lwtx2thtPvBAfIb7O13POMkv7df03HJeA== +"@lerna/prompt@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/prompt/-/prompt-6.1.0.tgz#98e228220428d33620822f77e39f592ce29c776c" + integrity sha512-981J/C53TZ2l2mFVlWJN7zynSzf5GEHKvKQa12Td9iknhASZOuwTAWb6eq46246Ant6W5tWwb0NSPu3I5qtcrA== dependencies: - inquirer "^6.2.0" - npmlog "^4.1.2" + inquirer "^8.2.4" + npmlog "^6.0.2" -"@lerna/publish@3.13.4": - version "3.13.4" - resolved "https://registry.yarnpkg.com/@lerna/publish/-/publish-3.13.4.tgz#25b678c285110897a7fc5198a35bdfa9db7f9cc1" - integrity sha512-v03pabiPlqCDwX6cVNis1PDdT6/jBgkVb5Nl4e8wcJXevIhZw3ClvtI94gSZu/wdoVFX0RMfc8QBVmaimSO0qg== - dependencies: - "@lerna/batch-packages" "3.13.0" - "@lerna/check-working-tree" "3.13.3" - "@lerna/child-process" "3.13.3" - "@lerna/collect-updates" "3.13.3" - "@lerna/command" "3.13.3" - "@lerna/describe-ref" "3.13.3" - "@lerna/log-packed" "3.13.0" - "@lerna/npm-conf" "3.13.0" - "@lerna/npm-dist-tag" "3.13.0" - "@lerna/npm-publish" "3.13.2" - "@lerna/output" "3.13.0" - "@lerna/pack-directory" "3.13.1" - "@lerna/prompt" "3.13.0" - "@lerna/pulse-till-done" "3.13.0" - "@lerna/run-lifecycle" "3.13.0" - "@lerna/run-parallel-batches" "3.13.0" - "@lerna/validation-error" "3.13.0" - "@lerna/version" "3.13.4" - figgy-pudding "^3.5.1" - fs-extra "^7.0.0" - libnpmaccess "^3.0.1" - npm-package-arg "^6.1.0" - npm-registry-fetch "^3.9.0" - npmlog "^4.1.2" - p-finally "^1.0.0" - p-map "^1.2.0" - p-pipe "^1.2.0" - p-reduce "^1.0.0" - pacote "^9.5.0" - semver "^5.5.0" +"@lerna/publish@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/publish/-/publish-6.1.0.tgz#9d62c327bc3541a0430951d726b39a2fb17b7925" + integrity sha512-XtvuydtU0IptbAapLRgoN1AZj/WJR+e3UKnx9BQ1Dwc+Fpg2oqPxR/vi+6hxAsr95pdQ5CnWBdgS+dg2wEUJ7Q== + dependencies: + "@lerna/check-working-tree" "6.1.0" + "@lerna/child-process" "6.1.0" + "@lerna/collect-updates" "6.1.0" + "@lerna/command" "6.1.0" + "@lerna/describe-ref" "6.1.0" + "@lerna/log-packed" "6.1.0" + "@lerna/npm-conf" "6.1.0" + "@lerna/npm-dist-tag" "6.1.0" + "@lerna/npm-publish" "6.1.0" + "@lerna/otplease" "6.1.0" + "@lerna/output" "6.1.0" + "@lerna/pack-directory" "6.1.0" + "@lerna/prerelease-id-from-version" "6.1.0" + "@lerna/prompt" "6.1.0" + "@lerna/pulse-till-done" "6.1.0" + "@lerna/run-lifecycle" "6.1.0" + "@lerna/run-topologically" "6.1.0" + "@lerna/validation-error" "6.1.0" + "@lerna/version" "6.1.0" + fs-extra "^9.1.0" + libnpmaccess "^6.0.3" + npm-package-arg "8.1.1" + npm-registry-fetch "^13.3.0" + npmlog "^6.0.2" + p-map "^4.0.0" + p-pipe "^3.1.0" + pacote "^13.6.1" + semver "^7.3.4" -"@lerna/pulse-till-done@3.13.0": - version "3.13.0" - resolved "https://registry.yarnpkg.com/@lerna/pulse-till-done/-/pulse-till-done-3.13.0.tgz#c8e9ce5bafaf10d930a67d7ed0ccb5d958fe0110" - integrity sha512-1SOHpy7ZNTPulzIbargrgaJX387csN7cF1cLOGZiJQA6VqnS5eWs2CIrG8i8wmaUavj2QlQ5oEbRMVVXSsGrzA== +"@lerna/pulse-till-done@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/pulse-till-done/-/pulse-till-done-6.1.0.tgz#df0112a9a5b8547b53d18742ce21104eb360d731" + integrity sha512-a2RVT82E4R9nVXtehzp2TQL6iXp0QfEM3bu8tBAR/SfI1A9ggZWQhuuUqtRyhhVCajdQDOo7rS0UG7R5JzK58w== dependencies: - npmlog "^4.1.2" + npmlog "^6.0.2" -"@lerna/resolve-symlink@3.13.0": - version "3.13.0" - resolved "https://registry.yarnpkg.com/@lerna/resolve-symlink/-/resolve-symlink-3.13.0.tgz#3e6809ef53b63fe914814bfa071cd68012e22fbb" - integrity sha512-Lc0USSFxwDxUs5JvIisS8JegjA6SHSAWJCMvi2osZx6wVRkEDlWG2B1JAfXUzCMNfHoZX0/XX9iYZ+4JIpjAtg== +"@lerna/query-graph@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/query-graph/-/query-graph-6.1.0.tgz#e78c47c78d4691231fc379570e036bc2753cf6fa" + integrity sha512-YkyCc+6aR7GlCOcZXEKPcl5o5L2v+0YUNs59JrfAS0mctFosZ/2tP7pkdu2SI4qXIi5D0PMNsh/0fRni56znsQ== dependencies: - fs-extra "^7.0.0" - npmlog "^4.1.2" - read-cmd-shim "^1.0.1" + "@lerna/package-graph" "6.1.0" -"@lerna/rimraf-dir@3.13.3": - version "3.13.3" - resolved "https://registry.yarnpkg.com/@lerna/rimraf-dir/-/rimraf-dir-3.13.3.tgz#3a8e71317fde853893ef0262bc9bba6a180b7227" - integrity sha512-d0T1Hxwu3gpYVv73ytSL+/Oy8JitsmvOYUR5ouRSABsmqS7ZZCh5t6FgVDDGVXeuhbw82+vuny1Og6Q0k4ilqw== +"@lerna/resolve-symlink@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/resolve-symlink/-/resolve-symlink-6.1.0.tgz#5a8686b99c838bc6e869930e5b5fd582607ebbe7" + integrity sha512-8ILO+h5fsE0q8MSLfdL+MT1GEsNhAB1fDyMkSsYgLRCsssN/cViZbffpclZyT/EfAhpyKfBCHZ0CmT1ZGofU1A== dependencies: - "@lerna/child-process" "3.13.3" - npmlog "^4.1.2" - path-exists "^3.0.0" - rimraf "^2.6.2" + fs-extra "^9.1.0" + npmlog "^6.0.2" + read-cmd-shim "^3.0.0" -"@lerna/run-lifecycle@3.13.0": - version "3.13.0" - resolved "https://registry.yarnpkg.com/@lerna/run-lifecycle/-/run-lifecycle-3.13.0.tgz#d8835ee83425edee40f687a55f81b502354d3261" - integrity sha512-oyiaL1biZdjpmjh6X/5C4w07wNFyiwXSSHH5GQB4Ay4BPwgq9oNhCcxRoi0UVZlZ1YwzSW8sTwLgj8emkIo3Yg== +"@lerna/rimraf-dir@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/rimraf-dir/-/rimraf-dir-6.1.0.tgz#75559585d5921563eff0e206bb9ec8ab0cc967c6" + integrity sha512-J9YeGHkCCeAIzsnKURYeGECBexiIii6HA+Bbd+rAgoKPsNCOj6ql4+qJE8Jbd7fQEFNDPQeBCYvM7JcdMc0WSA== dependencies: - "@lerna/npm-conf" "3.13.0" - figgy-pudding "^3.5.1" - npm-lifecycle "^2.1.0" - npmlog "^4.1.2" + "@lerna/child-process" "6.1.0" + npmlog "^6.0.2" + path-exists "^4.0.0" + rimraf "^3.0.2" + +"@lerna/run-lifecycle@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/run-lifecycle/-/run-lifecycle-6.1.0.tgz#e1fa6cd300842ef1d688af77648fed05ec2d5345" + integrity sha512-GbTdKxL+hWHEPgyBEKtqY9Nf+jFlt6YLtP5VjEVc5SdLkm+FeRquar9/YcZVUbzr3c+NJwWNgVjHuePfowdpUA== + dependencies: + "@lerna/npm-conf" "6.1.0" + "@npmcli/run-script" "^4.1.7" + npmlog "^6.0.2" + p-queue "^6.6.2" -"@lerna/run-parallel-batches@3.13.0": - version "3.13.0" - resolved "https://registry.yarnpkg.com/@lerna/run-parallel-batches/-/run-parallel-batches-3.13.0.tgz#0276bb4e7cd0995297db82d134ca2bd08d63e311" - integrity sha512-bICFBR+cYVF1FFW+Tlm0EhWDioTUTM6dOiVziDEGE1UZha1dFkMYqzqdSf4bQzfLS31UW/KBd/2z8jy2OIjEjg== +"@lerna/run-topologically@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/run-topologically/-/run-topologically-6.1.0.tgz#8f1a428b5d4b800bced178edabfa2262b328572f" + integrity sha512-kpTaSBKdKjtf61be8Z1e7TIaMt/aksfxswQtpFxEuKDsPsdHfR8htSkADO4d/3SZFtmcAHIHNCQj9CaNj4O4Xw== dependencies: - p-map "^1.2.0" - p-map-series "^1.0.0" + "@lerna/query-graph" "6.1.0" + p-queue "^6.6.2" -"@lerna/run@3.13.3": - version "3.13.3" - resolved "https://registry.yarnpkg.com/@lerna/run/-/run-3.13.3.tgz#0781c82d225ef6e85e28d3e763f7fc090a376a21" - integrity sha512-ygnLIfIYS6YY1JHWOM4CsdZiY8kTYPsDFOLAwASlRnlAXF9HiMT08GFXLmMHIblZJ8yJhsM2+QgraCB0WdxzOQ== - dependencies: - "@lerna/batch-packages" "3.13.0" - "@lerna/command" "3.13.3" - "@lerna/filter-options" "3.13.3" - "@lerna/npm-run-script" "3.13.3" - "@lerna/output" "3.13.0" - "@lerna/run-parallel-batches" "3.13.0" - "@lerna/timer" "3.13.0" - "@lerna/validation-error" "3.13.0" - p-map "^1.2.0" - -"@lerna/symlink-binary@3.13.0": - version "3.13.0" - resolved "https://registry.yarnpkg.com/@lerna/symlink-binary/-/symlink-binary-3.13.0.tgz#36a9415d468afcb8105750296902f6f000a9680d" - integrity sha512-obc4Y6jxywkdaCe+DB0uTxYqP0IQ8mFWvN+k/YMbwH4G2h7M7lCBWgPy8e7xw/50+1II9tT2sxgx+jMus1sTJg== - dependencies: - "@lerna/create-symlink" "3.13.0" - "@lerna/package" "3.13.0" - fs-extra "^7.0.0" - p-map "^1.2.0" +"@lerna/run@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/run/-/run-6.1.0.tgz#efaea1acc78cb7fc73b4906be70002e118628d64" + integrity sha512-vlEEKPcTloiob6EK7gxrjEdB6fQQ/LNfWhSJCGxJlvNVbrMpoWIu0Kpp20b0nE+lzX7rRJ4seWr7Wdo/Fjub4Q== + dependencies: + "@lerna/command" "6.1.0" + "@lerna/filter-options" "6.1.0" + "@lerna/npm-run-script" "6.1.0" + "@lerna/output" "6.1.0" + "@lerna/profiler" "6.1.0" + "@lerna/run-topologically" "6.1.0" + "@lerna/timer" "6.1.0" + "@lerna/validation-error" "6.1.0" + fs-extra "^9.1.0" + p-map "^4.0.0" -"@lerna/symlink-dependencies@3.13.0": - version "3.13.0" - resolved "https://registry.yarnpkg.com/@lerna/symlink-dependencies/-/symlink-dependencies-3.13.0.tgz#76c23ecabda7824db98a0561364f122b457509cf" - integrity sha512-7CyN5WYEPkbPLbqHBIQg/YiimBzb5cIGQB0E9IkLs3+racq2vmUNQZn38LOaazQacAA83seB+zWSxlI6H+eXSg== +"@lerna/symlink-binary@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/symlink-binary/-/symlink-binary-6.1.0.tgz#7d476499b86ae5fcb853c510603cff9a27acf105" + integrity sha512-DaiRNZk/dvomNxgEaTW145PyL7vIGP7rvnfXV2FO+rjX8UUSNUOjmVmHlYfs64gV9Eqx/dLfQClIbKcwYMD83A== dependencies: - "@lerna/create-symlink" "3.13.0" - "@lerna/resolve-symlink" "3.13.0" - "@lerna/symlink-binary" "3.13.0" - fs-extra "^7.0.0" - p-finally "^1.0.0" - p-map "^1.2.0" - p-map-series "^1.0.0" + "@lerna/create-symlink" "6.1.0" + "@lerna/package" "6.1.0" + fs-extra "^9.1.0" + p-map "^4.0.0" -"@lerna/timer@3.13.0": - version "3.13.0" - resolved "https://registry.yarnpkg.com/@lerna/timer/-/timer-3.13.0.tgz#bcd0904551db16e08364d6c18e5e2160fc870781" - integrity sha512-RHWrDl8U4XNPqY5MQHkToWS9jHPnkLZEt5VD+uunCKTfzlxGnRCr3/zVr8VGy/uENMYpVP3wJa4RKGY6M0vkRw== +"@lerna/symlink-dependencies@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/symlink-dependencies/-/symlink-dependencies-6.1.0.tgz#f44d33e043fed21a366c4ced2cbde8fa8be0c5fc" + integrity sha512-hrTvtY1Ek+fLA4JjXsKsvwPjuJD0rwB/+K4WY57t00owj//BpCsJ37w3kkkS7f/PcW/5uRjCuHcY67LOEwsRxw== + dependencies: + "@lerna/create-symlink" "6.1.0" + "@lerna/resolve-symlink" "6.1.0" + "@lerna/symlink-binary" "6.1.0" + fs-extra "^9.1.0" + p-map "^4.0.0" + p-map-series "^2.1.0" -"@lerna/validation-error@3.13.0": - version "3.13.0" - resolved "https://registry.yarnpkg.com/@lerna/validation-error/-/validation-error-3.13.0.tgz#c86b8f07c5ab9539f775bd8a54976e926f3759c3" - integrity sha512-SiJP75nwB8GhgwLKQfdkSnDufAaCbkZWJqEDlKOUPUvVOplRGnfL+BPQZH5nvq2BYSRXsksXWZ4UHVnQZI/HYA== +"@lerna/temp-write@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/temp-write/-/temp-write-6.1.0.tgz#a5d532090dd7b2d4f8965fbb475376aae06b9242" + integrity sha512-ZcQl88H9HbQ/TeWUOVt+vDYwptm7kwprGvj9KkZXr9S5Bn6SiKRQOeydCCfCrQT+9Q3dm7QZXV6rWzLsACcAlQ== dependencies: - npmlog "^4.1.2" + graceful-fs "^4.1.15" + is-stream "^2.0.0" + make-dir "^3.0.0" + temp-dir "^1.0.0" + uuid "^8.3.2" + +"@lerna/timer@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/timer/-/timer-6.1.0.tgz#245b02c05b2dec6d2aed2da8a0962cf0343d83d5" + integrity sha512-du+NQ9q7uO4d2nVU4AD2DSPuAZqUapA/bZKuVpFVxvY9Qhzb8dQKLsFISe4A9TjyoNAk8ZeWK0aBc/6N+Qer9A== -"@lerna/version@3.13.4": - version "3.13.4" - resolved "https://registry.yarnpkg.com/@lerna/version/-/version-3.13.4.tgz#ea23b264bebda425ccbfcdcd1de13ef45a390e59" - integrity sha512-pptWUEgN/lUTQZu34+gfH1g4Uhs7TDKRcdZY9A4T9k6RTOwpKC2ceLGiXdeR+ZgQJAey2C4qiE8fo5Z6Rbc6QA== - dependencies: - "@lerna/batch-packages" "3.13.0" - "@lerna/check-working-tree" "3.13.3" - "@lerna/child-process" "3.13.3" - "@lerna/collect-updates" "3.13.3" - "@lerna/command" "3.13.3" - "@lerna/conventional-commits" "3.13.0" - "@lerna/github-client" "3.13.3" - "@lerna/output" "3.13.0" - "@lerna/prompt" "3.13.0" - "@lerna/run-lifecycle" "3.13.0" - "@lerna/validation-error" "3.13.0" - chalk "^2.3.1" +"@lerna/validation-error@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/validation-error/-/validation-error-6.1.0.tgz#03bd46f6219b6db7c4420528d5aaf047f92693e3" + integrity sha512-q0c3XCi5OpyTr8AcfbisS6e3svZaJF/riCvBDqRMaQUT4A8QOPzB4fVF3/+J2u54nidBuTlIk0JZu9aOdWTUkQ== + dependencies: + npmlog "^6.0.2" + +"@lerna/version@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/version/-/version-6.1.0.tgz#44d8649e978df9d6a14d97c9d7631a7dcd4a9cbf" + integrity sha512-RUxVFdzHt0739lRNMrAbo6HWcFrcyG7atM1pn+Eo61fUoA5R/9N4bCk4m9xUGkJ/mOcROjuwAGe+wT1uOs58Bg== + dependencies: + "@lerna/check-working-tree" "6.1.0" + "@lerna/child-process" "6.1.0" + "@lerna/collect-updates" "6.1.0" + "@lerna/command" "6.1.0" + "@lerna/conventional-commits" "6.1.0" + "@lerna/github-client" "6.1.0" + "@lerna/gitlab-client" "6.1.0" + "@lerna/output" "6.1.0" + "@lerna/prerelease-id-from-version" "6.1.0" + "@lerna/prompt" "6.1.0" + "@lerna/run-lifecycle" "6.1.0" + "@lerna/run-topologically" "6.1.0" + "@lerna/temp-write" "6.1.0" + "@lerna/validation-error" "6.1.0" + "@nrwl/devkit" ">=14.8.6 < 16" + chalk "^4.1.0" dedent "^0.7.0" + load-json-file "^6.2.0" minimatch "^3.0.4" - npmlog "^4.1.2" - p-map "^1.2.0" - p-pipe "^1.2.0" - p-reduce "^1.0.0" - p-waterfall "^1.0.0" - semver "^5.5.0" - slash "^1.0.0" - temp-write "^3.4.0" + npmlog "^6.0.2" + p-map "^4.0.0" + p-pipe "^3.1.0" + p-reduce "^2.1.0" + p-waterfall "^2.1.1" + semver "^7.3.4" + slash "^3.0.0" + write-json-file "^4.3.0" -"@lerna/write-log-file@3.13.0": - version "3.13.0" - resolved "https://registry.yarnpkg.com/@lerna/write-log-file/-/write-log-file-3.13.0.tgz#b78d9e4cfc1349a8be64d91324c4c8199e822a26" - integrity sha512-RibeMnDPvlL8bFYW5C8cs4mbI3AHfQef73tnJCQ/SgrXZHehmHnsyWUiE7qDQCAo+B1RfTapvSyFF69iPj326A== +"@lerna/write-log-file@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@lerna/write-log-file/-/write-log-file-6.1.0.tgz#b811cffd2ea2b3be6239a756c64dac9a3795707a" + integrity sha512-09omu2w4NCt8mJH/X9ZMuToQQ3xu/KpC7EU4yDl2Qy8nxKf8HiG8Oe+YYNprngmkdsq60F5eUZvoiFDZ5JeGIg== dependencies: - npmlog "^4.1.2" - write-file-atomic "^2.3.0" + npmlog "^6.0.2" + write-file-atomic "^4.0.1" "@lint-todo/utils@^13.0.3": version "13.0.3" @@ -2898,14 +2971,6 @@ tslib "^2.3.1" upath "^2.0.1" -"@mrmlnc/readdir-enhanced@^2.2.1": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" - integrity sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g== - dependencies: - call-me-maybe "^1.0.1" - glob-to-regexp "^0.3.0" - "@next/env@10.1.3": version "10.1.3" resolved "https://registry.yarnpkg.com/@next/env/-/env-10.1.3.tgz#29e5d62919b4a7b1859f8d36169848dc3f5ddebe" @@ -2960,11 +3025,6 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz#a3f2dd61bab43b8db8fa108a121cfffe4c676655" integrity sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q== -"@nodelib/fs.stat@^1.1.2": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" - integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== - "@nodelib/fs.walk@^1.2.3": version "1.2.6" resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz#cce9396b30aa5afe9e3756608f5831adcb53d063" @@ -2973,135 +3033,286 @@ "@nodelib/fs.scandir" "2.1.4" fastq "^1.6.0" +"@npmcli/arborist@5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@npmcli/arborist/-/arborist-5.3.0.tgz#321d9424677bfc08569e98a5ac445ee781f32053" + integrity sha512-+rZ9zgL1lnbl8Xbb1NQdMjveOMwj4lIYfcDtyJHHi5x4X8jtR6m8SXooJMZy5vmFVZ8w7A2Bnd/oX9eTuU8w5A== + dependencies: + "@isaacs/string-locale-compare" "^1.1.0" + "@npmcli/installed-package-contents" "^1.0.7" + "@npmcli/map-workspaces" "^2.0.3" + "@npmcli/metavuln-calculator" "^3.0.1" + "@npmcli/move-file" "^2.0.0" + "@npmcli/name-from-folder" "^1.0.1" + "@npmcli/node-gyp" "^2.0.0" + "@npmcli/package-json" "^2.0.0" + "@npmcli/run-script" "^4.1.3" + bin-links "^3.0.0" + cacache "^16.0.6" + common-ancestor-path "^1.0.1" + json-parse-even-better-errors "^2.3.1" + json-stringify-nice "^1.1.4" + mkdirp "^1.0.4" + mkdirp-infer-owner "^2.0.0" + nopt "^5.0.0" + npm-install-checks "^5.0.0" + npm-package-arg "^9.0.0" + npm-pick-manifest "^7.0.0" + npm-registry-fetch "^13.0.0" + npmlog "^6.0.2" + pacote "^13.6.1" + parse-conflict-json "^2.0.1" + proc-log "^2.0.0" + promise-all-reject-late "^1.0.0" + promise-call-limit "^1.0.1" + read-package-json-fast "^2.0.2" + readdir-scoped-modules "^1.1.0" + rimraf "^3.0.2" + semver "^7.3.7" + ssri "^9.0.0" + treeverse "^2.0.0" + walk-up-path "^1.0.0" + "@npmcli/fs@^1.0.0": version "1.1.1" resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257" integrity sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ== dependencies: - "@gar/promisify" "^1.0.1" - semver "^7.3.5" + "@gar/promisify" "^1.0.1" + semver "^7.3.5" + +"@npmcli/fs@^2.1.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-2.1.2.tgz#a9e2541a4a2fec2e69c29b35e6060973da79b865" + integrity sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ== + dependencies: + "@gar/promisify" "^1.1.3" + semver "^7.3.5" + +"@npmcli/git@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@npmcli/git/-/git-3.0.2.tgz#5c5de6b4d70474cf2d09af149ce42e4e1dacb931" + integrity sha512-CAcd08y3DWBJqJDpfuVL0uijlq5oaXaOJEKHKc4wqrjd00gkvTZB+nFuLn+doOOKddaQS9JfqtNoFCO2LCvA3w== + dependencies: + "@npmcli/promise-spawn" "^3.0.0" + lru-cache "^7.4.4" + mkdirp "^1.0.4" + npm-pick-manifest "^7.0.0" + proc-log "^2.0.0" + promise-inflight "^1.0.1" + promise-retry "^2.0.1" + semver "^7.3.5" + which "^2.0.2" + +"@npmcli/installed-package-contents@^1.0.7": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz#ab7408c6147911b970a8abe261ce512232a3f4fa" + integrity sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw== + dependencies: + npm-bundled "^1.1.1" + npm-normalize-package-bin "^1.0.1" + +"@npmcli/map-workspaces@^2.0.3": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@npmcli/map-workspaces/-/map-workspaces-2.0.4.tgz#9e5e8ab655215a262aefabf139782b894e0504fc" + integrity sha512-bMo0aAfwhVwqoVM5UzX1DJnlvVvzDCHae821jv48L1EsrYwfOZChlqWYXEtto/+BkBXetPbEWgau++/brh4oVg== + dependencies: + "@npmcli/name-from-folder" "^1.0.1" + glob "^8.0.1" + minimatch "^5.0.1" + read-package-json-fast "^2.0.3" + +"@npmcli/metavuln-calculator@^3.0.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@npmcli/metavuln-calculator/-/metavuln-calculator-3.1.1.tgz#9359bd72b400f8353f6a28a25c8457b562602622" + integrity sha512-n69ygIaqAedecLeVH3KnO39M6ZHiJ2dEv5A7DGvcqCB8q17BGUgW8QaanIkbWUo2aYGZqJaOORTLAlIvKjNDKA== + dependencies: + cacache "^16.0.0" + json-parse-even-better-errors "^2.3.1" + pacote "^13.0.3" + semver "^7.3.5" + +"@npmcli/move-file@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" + integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== + dependencies: + mkdirp "^1.0.4" + rimraf "^3.0.2" + +"@npmcli/move-file@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-2.0.1.tgz#26f6bdc379d87f75e55739bab89db525b06100e4" + integrity sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ== + dependencies: + mkdirp "^1.0.4" + rimraf "^3.0.2" + +"@npmcli/name-from-folder@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/name-from-folder/-/name-from-folder-1.0.1.tgz#77ecd0a4fcb772ba6fe927e2e2e155fbec2e6b1a" + integrity sha512-qq3oEfcLFwNfEYOQ8HLimRGKlD8WSeGEdtUa7hmzpR8Sa7haL1KVQrvgO6wqMjhWFFVjgtrh1gIxDz+P8sjUaA== + +"@npmcli/node-gyp@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/node-gyp/-/node-gyp-2.0.0.tgz#8c20e53e34e9078d18815c1d2dda6f2420d75e35" + integrity sha512-doNI35wIe3bBaEgrlPfdJPaCpUR89pJWep4Hq3aRdh6gKazIVWfs0jHttvSSoq47ZXgC7h73kDsUl8AoIQUB+A== + +"@npmcli/package-json@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/package-json/-/package-json-2.0.0.tgz#3bbcf4677e21055adbe673d9f08c9f9cde942e4a" + integrity sha512-42jnZ6yl16GzjWSH7vtrmWyJDGVa/LXPdpN2rcUWolFjc9ON2N3uz0qdBbQACfmhuJZ2lbKYtmK5qx68ZPLHMA== + dependencies: + json-parse-even-better-errors "^2.3.1" + +"@npmcli/promise-spawn@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/promise-spawn/-/promise-spawn-3.0.0.tgz#53283b5f18f855c6925f23c24e67c911501ef573" + integrity sha512-s9SgS+p3a9Eohe68cSI3fi+hpcZUmXq5P7w0kMlAsWVtR7XbK3ptkZqKT2cK1zLDObJ3sR+8P59sJE0w/KTL1g== + dependencies: + infer-owner "^1.0.4" + +"@npmcli/run-script@^4.1.0", "@npmcli/run-script@^4.1.3", "@npmcli/run-script@^4.1.7": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-4.2.1.tgz#c07c5c71bc1c70a5f2a06b0d4da976641609b946" + integrity sha512-7dqywvVudPSrRCW5nTHpHgeWnbBtz8cFkOuKrecm6ih+oO9ciydhWt6OF7HlqupRRmB8Q/gECVdB9LMfToJbRg== + dependencies: + "@npmcli/node-gyp" "^2.0.0" + "@npmcli/promise-spawn" "^3.0.0" + node-gyp "^9.0.0" + read-package-json-fast "^2.0.3" + which "^2.0.2" + +"@nrwl/cli@15.2.1": + version "15.2.1" + resolved "https://registry.yarnpkg.com/@nrwl/cli/-/cli-15.2.1.tgz#9de75e20429315bf42516504601a785141fa6e8d" + integrity sha512-ufBJ5o3WCixdp6/TPkpn4rH3QBFJcqCMG1d14A/SvAkEnXu3vWlj3E+4GXf3CK+sGNjjf3ZGoNm7OFV+/Ml4GA== + dependencies: + nx "15.2.1" + +"@nrwl/devkit@>=14.8.6 < 16": + version "15.2.1" + resolved "https://registry.yarnpkg.com/@nrwl/devkit/-/devkit-15.2.1.tgz#3907ee18d4bcb9ab36fc950c48c9cb0466ff8ab8" + integrity sha512-si6DK0sjtr6Ln+7hh9QD50KqxnOxuP20xJ0fxMVafT/Lz/qtxuCPw9lwJWVagMHWw3pQAtN6lbkWiLOwj8+YPA== + dependencies: + "@phenomnomnominal/tsquery" "4.1.1" + ejs "^3.1.7" + ignore "^5.0.4" + semver "7.3.4" + tslib "^2.3.0" -"@npmcli/move-file@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" - integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== +"@nrwl/tao@15.2.1": + version "15.2.1" + resolved "https://registry.yarnpkg.com/@nrwl/tao/-/tao-15.2.1.tgz#da6a6813eaedcb907b3917d06e4d5bbc4501dbaf" + integrity sha512-6ApglWKORasLhtjlJmqo2co/UexSSiD7mWVxT2886oKG2Y/T/5RnPFNhJgGnKJIURCNMxiSKhq7GAA+CE9zRZg== dependencies: - mkdirp "^1.0.4" - rimraf "^3.0.2" + nx "15.2.1" -"@octokit/auth-token@^2.4.0": - version "2.4.5" - resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.5.tgz#568ccfb8cb46f36441fac094ce34f7a875b197f3" - integrity sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA== +"@octokit/auth-token@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-3.0.2.tgz#a0fc8de149fd15876e1ac78f6525c1c5ab48435f" + integrity sha512-pq7CwIMV1kmzkFTimdwjAINCXKTajZErLB4wMLYapR2nuB/Jpr66+05wOTZMSCBXP6n4DdDWT2W19Bm17vU69Q== dependencies: - "@octokit/types" "^6.0.3" + "@octokit/types" "^8.0.0" + +"@octokit/core@^4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-4.1.0.tgz#b6b03a478f1716de92b3f4ec4fd64d05ba5a9251" + integrity sha512-Czz/59VefU+kKDy+ZfDwtOIYIkFjExOKf+HA92aiTZJ6EfWpFzYQWw0l54ji8bVmyhc+mGaLUbSUmXazG7z5OQ== + dependencies: + "@octokit/auth-token" "^3.0.0" + "@octokit/graphql" "^5.0.0" + "@octokit/request" "^6.0.0" + "@octokit/request-error" "^3.0.0" + "@octokit/types" "^8.0.0" + before-after-hook "^2.2.0" + universal-user-agent "^6.0.0" -"@octokit/endpoint@^6.0.1": - version "6.0.11" - resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.11.tgz#082adc2aebca6dcefa1fb383f5efb3ed081949d1" - integrity sha512-fUIPpx+pZyoLW4GCs3yMnlj2LfoXTWDUVPTC4V3MUEKZm48W+XYpeWSZCv+vYF1ZABUm2CqnDVf1sFtIYrj7KQ== +"@octokit/endpoint@^7.0.0": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-7.0.3.tgz#0b96035673a9e3bedf8bab8f7335de424a2147ed" + integrity sha512-57gRlb28bwTsdNXq+O3JTQ7ERmBTuik9+LelgcLIVfYwf235VHbN9QNo4kXExtp/h8T423cR5iJThKtFYxC7Lw== dependencies: - "@octokit/types" "^6.0.3" + "@octokit/types" "^8.0.0" is-plain-object "^5.0.0" universal-user-agent "^6.0.0" -"@octokit/openapi-types@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-6.0.0.tgz#7da8d7d5a72d3282c1a3ff9f951c8133a707480d" - integrity sha512-CnDdK7ivHkBtJYzWzZm7gEkanA7gKH6a09Eguz7flHw//GacPJLmkHA3f3N++MJmlxD1Fl+mB7B32EEpSCwztQ== +"@octokit/graphql@^5.0.0": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-5.0.4.tgz#519dd5c05123868276f3ae4e50ad565ed7dff8c8" + integrity sha512-amO1M5QUQgYQo09aStR/XO7KAl13xpigcy/kI8/N1PnZYSS69fgte+xA4+c2DISKqUZfsh0wwjc2FaCt99L41A== + dependencies: + "@octokit/request" "^6.0.0" + "@octokit/types" "^8.0.0" + universal-user-agent "^6.0.0" -"@octokit/plugin-enterprise-rest@^2.1.1": - version "2.2.2" - resolved "https://registry.yarnpkg.com/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-2.2.2.tgz#c0e22067a043e19f96ff9c7832e2a3019f9be75c" - integrity sha512-CTZr64jZYhGWNTDGlSJ2mvIlFsm9OEO3LqWn9I/gmoHI4jRBp4kpHoFYNemG4oA75zUAcmbuWblb7jjP877YZw== +"@octokit/openapi-types@^14.0.0": + version "14.0.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-14.0.0.tgz#949c5019028c93f189abbc2fb42f333290f7134a" + integrity sha512-HNWisMYlR8VCnNurDU6os2ikx0s0VyEjDYHNS/h4cgb8DeOxQ0n72HyinUtdDVxJhFy3FWLGl0DJhfEWk3P5Iw== -"@octokit/plugin-paginate-rest@^1.1.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-1.1.2.tgz#004170acf8c2be535aba26727867d692f7b488fc" - integrity sha512-jbsSoi5Q1pj63sC16XIUboklNw+8tL9VOnJsWycWYR78TKss5PVpIPb1TUUcMQ+bBh7cY579cVAWmf5qG+dw+Q== +"@octokit/plugin-enterprise-rest@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-6.0.1.tgz#e07896739618dab8da7d4077c658003775f95437" + integrity sha512-93uGjlhUD+iNg1iWhUENAtJata6w5nE+V4urXOAlIXdco6xNZtUSfYY8dzp3Udy74aqO/B5UZL80x/YMa5PKRw== + +"@octokit/plugin-paginate-rest@^5.0.0": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-5.0.1.tgz#93d7e74f1f69d68ba554fa6b888c2a9cf1f99a83" + integrity sha512-7A+rEkS70pH36Z6JivSlR7Zqepz3KVucEFVDnSrgHXzG7WLAzYwcHZbKdfTXHwuTHbkT1vKvz7dHl1+HNf6Qyw== dependencies: - "@octokit/types" "^2.0.1" + "@octokit/types" "^8.0.0" -"@octokit/plugin-request-log@^1.0.0": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.3.tgz#70a62be213e1edc04bb8897ee48c311482f9700d" - integrity sha512-4RFU4li238jMJAzLgAwkBAw+4Loile5haQMQr+uhFq27BmyJXcXSKvoQKqh0agsZEiUlW6iSv3FAgvmGkur7OQ== +"@octokit/plugin-request-log@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz#5e50ed7083a613816b1e4a28aeec5fb7f1462e85" + integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA== -"@octokit/plugin-rest-endpoint-methods@2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-2.4.0.tgz#3288ecf5481f68c494dd0602fc15407a59faf61e" - integrity sha512-EZi/AWhtkdfAYi01obpX0DF7U6b1VRr30QNQ5xSFPITMdLSfhcBqjamE3F+sKcxPbD7eZuMHu3Qkk2V+JGxBDQ== +"@octokit/plugin-rest-endpoint-methods@^6.7.0": + version "6.7.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-6.7.0.tgz#2f6f17f25b6babbc8b41d2bb0a95a8839672ce7c" + integrity sha512-orxQ0fAHA7IpYhG2flD2AygztPlGYNAdlzYz8yrD8NDgelPfOYoRPROfEyIe035PlxvbYrgkfUZIhSBKju/Cvw== dependencies: - "@octokit/types" "^2.0.1" + "@octokit/types" "^8.0.0" deprecation "^2.3.1" -"@octokit/request-error@^1.0.2": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-1.2.1.tgz#ede0714c773f32347576c25649dc013ae6b31801" - integrity sha512-+6yDyk1EES6WK+l3viRDElw96MvwfJxCt45GvmjDUKWjYIb3PJZQkq3i46TwGwoPD4h8NmTrENmtyA1FwbmhRA== - dependencies: - "@octokit/types" "^2.0.0" - deprecation "^2.0.0" - once "^1.4.0" - -"@octokit/request-error@^2.0.0": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.0.5.tgz#72cc91edc870281ad583a42619256b380c600143" - integrity sha512-T/2wcCFyM7SkXzNoyVNWjyVlUwBvW3igM3Btr/eKYiPmucXTtkxt2RBsf6gn3LTzaLSLTQtNmvg+dGsOxQrjZg== +"@octokit/request-error@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-3.0.2.tgz#f74c0f163d19463b87528efe877216c41d6deb0a" + integrity sha512-WMNOFYrSaX8zXWoJg9u/pKgWPo94JXilMLb2VManNOby9EZxrQaBe/QSC4a1TzpAlpxofg2X/jMnCyZgL6y7eg== dependencies: - "@octokit/types" "^6.0.3" + "@octokit/types" "^8.0.0" deprecation "^2.0.0" once "^1.4.0" -"@octokit/request@^5.2.0": - version "5.4.14" - resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.14.tgz#ec5f96f78333bb2af390afa5ff66f114b063bc96" - integrity sha512-VkmtacOIQp9daSnBmDI92xNIeLuSRDOIuplp/CJomkvzt7M18NXgG044Cx/LFKLgjKt9T2tZR6AtJayba9GTSA== +"@octokit/request@^6.0.0": + version "6.2.2" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-6.2.2.tgz#a2ba5ac22bddd5dcb3f539b618faa05115c5a255" + integrity sha512-6VDqgj0HMc2FUX2awIs+sM6OwLgwHvAi4KCK3mT2H2IKRt6oH9d0fej5LluF5mck1lRR/rFWN0YIDSYXYSylbw== dependencies: - "@octokit/endpoint" "^6.0.1" - "@octokit/request-error" "^2.0.0" - "@octokit/types" "^6.7.1" - deprecation "^2.0.0" + "@octokit/endpoint" "^7.0.0" + "@octokit/request-error" "^3.0.0" + "@octokit/types" "^8.0.0" is-plain-object "^5.0.0" - node-fetch "^2.6.1" - once "^1.4.0" + node-fetch "^2.6.7" universal-user-agent "^6.0.0" -"@octokit/rest@^16.16.0": - version "16.43.2" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-16.43.2.tgz#c53426f1e1d1044dee967023e3279c50993dd91b" - integrity sha512-ngDBevLbBTFfrHZeiS7SAMAZ6ssuVmXuya+F/7RaVvlysgGa1JKJkKWY+jV6TCJYcW0OALfJ7nTIGXcBXzycfQ== - dependencies: - "@octokit/auth-token" "^2.4.0" - "@octokit/plugin-paginate-rest" "^1.1.1" - "@octokit/plugin-request-log" "^1.0.0" - "@octokit/plugin-rest-endpoint-methods" "2.4.0" - "@octokit/request" "^5.2.0" - "@octokit/request-error" "^1.0.2" - atob-lite "^2.0.0" - before-after-hook "^2.0.0" - btoa-lite "^1.0.0" - deprecation "^2.0.0" - lodash.get "^4.4.2" - lodash.set "^4.3.2" - lodash.uniq "^4.5.0" - octokit-pagination-methods "^1.1.0" - once "^1.4.0" - universal-user-agent "^4.0.0" - -"@octokit/types@^2.0.0", "@octokit/types@^2.0.1": - version "2.16.2" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-2.16.2.tgz#4c5f8da3c6fecf3da1811aef678fda03edac35d2" - integrity sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q== +"@octokit/rest@^19.0.3": + version "19.0.5" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-19.0.5.tgz#4dbde8ae69b27dca04b5f1d8119d282575818f6c" + integrity sha512-+4qdrUFq2lk7Va+Qff3ofREQWGBeoTKNqlJO+FGjFP35ZahP+nBenhZiGdu8USSgmq4Ky3IJ/i4u0xbLqHaeow== dependencies: - "@types/node" ">= 8" + "@octokit/core" "^4.1.0" + "@octokit/plugin-paginate-rest" "^5.0.0" + "@octokit/plugin-request-log" "^1.0.4" + "@octokit/plugin-rest-endpoint-methods" "^6.7.0" -"@octokit/types@^6.0.3", "@octokit/types@^6.7.1": - version "6.13.0" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.13.0.tgz#779e5b7566c8dde68f2f6273861dd2f0409480d0" - integrity sha512-W2J9qlVIU11jMwKHUp5/rbVUeErqelCsO5vW5PKNb7wAXQVUz87Rc+imjlEvpvbH8yUb+KHmv8NEjVZdsdpyxA== +"@octokit/types@^8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-8.0.0.tgz#93f0b865786c4153f0f6924da067fe0bb7426a9f" + integrity sha512-65/TPpOJP1i3K4lBJMnWqPUJ6zuOtzhtagDvydAWbEXpbFYA0oMKKyLb95NFZZP0lSh/4b6K+DQlzvYQJQQePg== dependencies: - "@octokit/openapi-types" "^6.0.0" + "@octokit/openapi-types" "^14.0.0" "@opentelemetry/api@0.14.0": version "0.14.0" @@ -3225,13 +3436,28 @@ "@opentelemetry/resources" "^0.12.0" "@opentelemetry/semantic-conventions" "^0.12.0" -"@playwright/test@^1.27.1": - version "1.28.1" - resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.28.1.tgz#e5be297e024a3256610cac2baaa9347fd57c7860" - integrity sha512-xN6spdqrNlwSn9KabIhqfZR7IWjPpFK1835tFNgjrlysaSezuX8PYUwaz38V/yI8TJLG9PkAMEXoHRXYXlpTPQ== +"@parcel/watcher@2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.4.tgz#f300fef4cc38008ff4b8c29d92588eced3ce014b" + integrity sha512-cTDi+FUDBIUOBKEtj+nhiJ71AZVlkAsQFuGQTun5tV9mwQBQgZvhCzG+URPQc8myeN32yRVZEfVAPCs1RW+Jvg== + dependencies: + node-addon-api "^3.2.1" + node-gyp-build "^4.3.0" + +"@phenomnomnominal/tsquery@4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@phenomnomnominal/tsquery/-/tsquery-4.1.1.tgz#42971b83590e9d853d024ddb04a18085a36518df" + integrity sha512-jjMmK1tnZbm1Jq5a7fBliM4gQwjxMU7TFoRNwIyzwlO+eHPRCFv/Nv+H/Gi1jc3WR7QURG8D5d0Tn12YGrUqBQ== + dependencies: + esquery "^1.0.1" + +"@playwright/test@^1.29.2": + version "1.29.2" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.29.2.tgz#c48184721d0f0b7627a886e2ec42f1efb2be339d" + integrity sha512-+3/GPwOgcoF0xLz/opTnahel1/y42PdcgZ4hs+BZGIUjtmEFSXGg+nFoaH3NSmuc7a6GSFwXDJ5L7VXpqzigNg== dependencies: "@types/node" "*" - playwright-core "1.28.1" + playwright-core "1.29.2" "@polka/url@^1.0.0-next.9": version "1.0.0-next.12" @@ -3385,6 +3611,18 @@ dependencies: web-streams-polyfill "^3.1.1" +"@rollup/plugin-commonjs@24.0.0": + version "24.0.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.0.tgz#fb7cf4a6029f07ec42b25daa535c75b05a43f75c" + integrity sha512-0w0wyykzdyRRPHOb0cQt14mIBLujfAv6GgP6g8nvg/iBxEm112t3YPPq+Buqe2+imvElTka+bjNlJ/gB56TD8g== + dependencies: + "@rollup/pluginutils" "^5.0.1" + commondir "^1.0.1" + estree-walker "^2.0.2" + glob "^8.0.3" + is-reference "1.2.1" + magic-string "^0.27.0" + "@rollup/plugin-commonjs@^15.0.0": version "15.1.0" resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-15.1.0.tgz#1e7d076c4f1b2abf7e65248570e555defc37c238" @@ -3450,7 +3688,7 @@ "@rollup/pluginutils" "^3.1.0" magic-string "^0.25.7" -"@rollup/plugin-sucrase@4.0.4", "@rollup/plugin-sucrase@^4.0.3": +"@rollup/plugin-sucrase@^4.0.3": version "4.0.4" resolved "https://registry.yarnpkg.com/@rollup/plugin-sucrase/-/plugin-sucrase-4.0.4.tgz#0a3b3d97cdc239ec3399f5a10711f751e9f95d98" integrity sha512-YH4J8yoJb5EVnLhAwWxYAQNh2SJOR+SdZ6XdgoKEv6Kxm33riYkM8MlMaggN87UoISP52qAFyZ5ey56wu6umGg== @@ -3466,11 +3704,6 @@ "@rollup/pluginutils" "^3.1.0" resolve "^1.17.0" -"@rollup/plugin-virtual@3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@rollup/plugin-virtual/-/plugin-virtual-3.0.0.tgz#8c3f54b4ab4b267d9cd3dcbaedc58d4fd1deddca" - integrity sha512-K9KORe1myM62o0lKkNR4MmCxjwuAXsZEtIHpaILfv4kILXTOrXt/R2ha7PzMcCHPYdnkWPiBZK8ed4Zr3Ll5lQ== - "@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.0.9", "@rollup/pluginutils@^3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" @@ -3488,6 +3721,15 @@ estree-walker "^2.0.1" picomatch "^2.2.2" +"@rollup/pluginutils@^5.0.1": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.0.2.tgz#012b8f53c71e4f6f9cb317e311df1404f56e7a33" + integrity sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA== + dependencies: + "@types/estree" "^1.0.0" + estree-walker "^2.0.2" + picomatch "^2.3.1" + "@schematics/angular@10.2.4": version "10.2.4" resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-10.2.4.tgz#3b99b9da572b57381d221e2008804e6bb9c98b82" @@ -4055,11 +4297,6 @@ "@types/eslint" "*" "@types/estree" "*" -"@types/eslint-visitor-keys@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" - integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== - "@types/eslint@*": version "8.2.1" resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.2.1.tgz#13f3d69bac93c2ae008019c28783868d0a1d6605" @@ -4076,16 +4313,21 @@ "@types/estree" "*" "@types/json-schema" "*" -"@types/estree@*", "@types/estree@^0.0.51": - version "0.0.51" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" - integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== +"@types/estree@*", "@types/estree@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" + integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== "@types/estree@0.0.39": version "0.0.39" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== +"@types/estree@^0.0.51": + version "0.0.51" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" + integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== + "@types/express-serve-static-core@4.17.28", "@types/express-serve-static-core@4.17.30", "@types/express-serve-static-core@^4.17.18": version "4.17.30" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.30.tgz#0f2f99617fa8f9696170c46152ccf7500b34ac04" @@ -4311,7 +4553,7 @@ "@types/node" "*" form-data "^3.0.0" -"@types/node@*", "@types/node@>= 8", "@types/node@>=10.0.0", "@types/node@>=12.12.47", "@types/node@>=13.7.0": +"@types/node@*", "@types/node@>=10.0.0", "@types/node@>=12.12.47", "@types/node@>=13.7.0": version "17.0.38" resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.38.tgz#f8bb07c371ccb1903f3752872c89f44006132947" integrity sha512-5jY9RhV7c0Z4Jy09G+NIDTsCZ5G0L5n+Z+p+Y7t5VJHM30bgwzSjVtlcBxqAj+6L/swIlvtOSzr8rBk/aNyV2g== @@ -4336,6 +4578,11 @@ resolved "https://registry.yarnpkg.com/@types/pako/-/pako-2.0.0.tgz#12ab4c19107528452e73ac99132c875ccd43bdfb" integrity sha512-10+iaz93qR5WYxTo+PMifD5TSxiOtdRaxBf7INGGXMQgTCu8Z/7GYWYFUOS3q/G0nE5boj1r4FEB+WSy7s5gbA== +"@types/parse-json@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + "@types/parse5@*": version "6.0.0" resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.0.tgz#38590dc2c3cf5717154064e3ee9b6947ee21b299" @@ -4464,10 +4711,10 @@ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275" integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA== -"@types/semver@^7.3.9": - version "7.3.9" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.9.tgz#152c6c20a7688c30b967ec1841d31ace569863fc" - integrity sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ== +"@types/semver@^7.3.12", "@types/semver@^7.3.9": + version "7.3.13" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" + integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw== "@types/serve-static@*": version "1.13.9" @@ -4592,19 +4839,22 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@^3.9.0": - version "3.10.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.10.1.tgz#7e061338a1383f59edc204c605899f93dc2e2c8f" - integrity sha512-PQg0emRtzZFWq6PxBcdxRH3QIQiyFO3WCVpRL3fgj5oQS3CDs3AeAKfv4DxNhzn8ITdNJGJ4D3Qw8eAJf3lXeQ== +"@typescript-eslint/eslint-plugin@^5.48.0": + version "5.48.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.0.tgz#54f8368d080eb384a455f60c2ee044e948a8ce67" + integrity sha512-SVLafp0NXpoJY7ut6VFVUU9I+YeFsDzeQwtK0WZ+xbRN3mtxJ08je+6Oi2N89qDn087COdO0u3blKZNv9VetRQ== dependencies: - "@typescript-eslint/experimental-utils" "3.10.1" - debug "^4.1.1" - functional-red-black-tree "^1.0.1" - regexpp "^3.0.0" - semver "^7.3.2" - tsutils "^3.17.1" + "@typescript-eslint/scope-manager" "5.48.0" + "@typescript-eslint/type-utils" "5.48.0" + "@typescript-eslint/utils" "5.48.0" + debug "^4.3.4" + ignore "^5.2.0" + natural-compare-lite "^1.4.0" + regexpp "^3.2.0" + semver "^7.3.7" + tsutils "^3.21.0" -"@typescript-eslint/experimental-utils@3.10.1", "@typescript-eslint/experimental-utils@^2.19.2 || ^3.0.0": +"@typescript-eslint/experimental-utils@^2.19.2 || ^3.0.0": version "3.10.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz#e179ffc81a80ebcae2ea04e0332f8b251345a686" integrity sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw== @@ -4615,16 +4865,33 @@ eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/parser@^3.9.0": - version "3.10.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.10.1.tgz#1883858e83e8b442627e1ac6f408925211155467" - integrity sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw== +"@typescript-eslint/parser@^5.48.0": + version "5.48.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.48.0.tgz#02803355b23884a83e543755349809a50b7ed9ba" + integrity sha512-1mxNA8qfgxX8kBvRDIHEzrRGrKHQfQlbW6iHyfHYS0Q4X1af+S6mkLNtgCOsGVl8+/LUPrqdHMssAemkrQ01qg== dependencies: - "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "3.10.1" - "@typescript-eslint/types" "3.10.1" - "@typescript-eslint/typescript-estree" "3.10.1" - eslint-visitor-keys "^1.1.0" + "@typescript-eslint/scope-manager" "5.48.0" + "@typescript-eslint/types" "5.48.0" + "@typescript-eslint/typescript-estree" "5.48.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@5.48.0": + version "5.48.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.48.0.tgz#607731cb0957fbc52fd754fd79507d1b6659cecf" + integrity sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow== + dependencies: + "@typescript-eslint/types" "5.48.0" + "@typescript-eslint/visitor-keys" "5.48.0" + +"@typescript-eslint/type-utils@5.48.0": + version "5.48.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.48.0.tgz#40496dccfdc2daa14a565f8be80ad1ae3882d6d6" + integrity sha512-vbtPO5sJyFjtHkGlGK4Sthmta0Bbls4Onv0bEqOGm7hP9h8UpRsHJwsrCiWtCUndTRNQO/qe6Ijz9rnT/DB+7g== + dependencies: + "@typescript-eslint/typescript-estree" "5.48.0" + "@typescript-eslint/utils" "5.48.0" + debug "^4.3.4" + tsutils "^3.21.0" "@typescript-eslint/types@3.10.1": version "3.10.1" @@ -4636,6 +4903,11 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.23.0.tgz#da1654c8a5332f4d1645b2d9a1c64193cae3aa3b" integrity sha512-oqkNWyG2SLS7uTWLZf6Sr7Dm02gA5yxiz1RP87tvsmDsguVATdpVguHr4HoGOcFOpCvx9vtCSCyQUGfzq28YCw== +"@typescript-eslint/types@5.48.0": + version "5.48.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.48.0.tgz#d725da8dfcff320aab2ac6f65c97b0df30058449" + integrity sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw== + "@typescript-eslint/typescript-estree@3.10.1": version "3.10.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz#fd0061cc38add4fad45136d654408569f365b853" @@ -4650,6 +4922,19 @@ semver "^7.3.2" tsutils "^3.17.1" +"@typescript-eslint/typescript-estree@5.48.0": + version "5.48.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.0.tgz#a7f04bccb001003405bb5452d43953a382c2fac2" + integrity sha512-7pjd94vvIjI1zTz6aq/5wwE/YrfIyEPLtGJmRfyNR9NYIW+rOvzzUv3Cmq2hRKpvt6e9vpvPUQ7puzX7VSmsEw== + dependencies: + "@typescript-eslint/types" "5.48.0" + "@typescript-eslint/visitor-keys" "5.48.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + "@typescript-eslint/typescript-estree@^4.8.2": version "4.23.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.23.0.tgz#0753b292097523852428a6f5a1aa8ccc1aae6cd9" @@ -4663,6 +4948,20 @@ semver "^7.3.2" tsutils "^3.17.1" +"@typescript-eslint/utils@5.48.0": + version "5.48.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.48.0.tgz#eee926af2733f7156ad8d15e51791e42ce300273" + integrity sha512-x2jrMcPaMfsHRRIkL+x96++xdzvrdBCnYRd5QiW5Wgo1OB4kDYPbC1XjWP/TNqlfK93K/lUL92erq5zPLgFScQ== + dependencies: + "@types/json-schema" "^7.0.9" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.48.0" + "@typescript-eslint/types" "5.48.0" + "@typescript-eslint/typescript-estree" "5.48.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + semver "^7.3.7" + "@typescript-eslint/visitor-keys@3.10.1": version "3.10.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz#cd4274773e3eb63b2e870ac602274487ecd1e931" @@ -4678,6 +4977,14 @@ "@typescript-eslint/types" "4.23.0" eslint-visitor-keys "^2.0.0" +"@typescript-eslint/visitor-keys@5.48.0": + version "5.48.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz#4446d5e7f6cadde7140390c0e284c8702d944904" + integrity sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q== + dependencies: + "@typescript-eslint/types" "5.48.0" + eslint-visitor-keys "^3.3.0" + "@vue/compiler-core@3.2.45": version "3.2.45" resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.45.tgz#d9311207d96f6ebd5f4660be129fb99f01ddb41b" @@ -5074,6 +5381,21 @@ resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== +"@yarnpkg/parsers@^3.0.0-rc.18": + version "3.0.0-rc.31" + resolved "https://registry.yarnpkg.com/@yarnpkg/parsers/-/parsers-3.0.0-rc.31.tgz#fbcce77c3783b2be8a381edf70bea3182e0b8b16" + integrity sha512-7M67TPmTM5OmtoypK0KHV3vIY9z0v4qZ6zF7flH8THLgjGuoA7naop8pEfL9x5vCtid1PDC4A4COrcym4WAZpQ== + dependencies: + js-yaml "^3.10.0" + tslib "^2.4.0" + +"@zkochan/js-yaml@0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@zkochan/js-yaml/-/js-yaml-0.0.6.tgz#975f0b306e705e28b8068a07737fa46d3fc04826" + integrity sha512-nzvgl3VfhcELQ8LyVrYOru+UtAy1nrygk2+AGbTm8a5YcO6o8lSjAT+pfg3vJWxIoZKOUhrK6UU7xW/+00kQrg== + dependencies: + argparse "^2.0.1" + "@zxing/text-encoding@0.9.0": version "0.9.0" resolved "https://registry.yarnpkg.com/@zxing/text-encoding/-/text-encoding-0.9.0.tgz#fb50ffabc6c7c66a0c96b4c03e3d9be74864b70b" @@ -5092,7 +5414,7 @@ abab@^2.0.3, abab@^2.0.5: resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== -abbrev@1: +abbrev@1, abbrev@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== @@ -5165,6 +5487,11 @@ acorn@^8.0.4, acorn@^8.2.4, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.0, acorn@^8.7 resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== +add-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa" + integrity sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ== + adjust-sourcemap-loader@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/adjust-sourcemap-loader/-/adjust-sourcemap-loader-3.0.0.tgz#5ae12fb5b7b1c585e80bbb5a63ec163a1a45e61e" @@ -5173,7 +5500,7 @@ adjust-sourcemap-loader@3.0.0: loader-utils "^2.0.0" regex-parser "^2.2.11" -agent-base@4, agent-base@5, agent-base@6, agent-base@^4.3.0, agent-base@~4.2.1: +agent-base@4, agent-base@5, agent-base@6, agent-base@^4.3.0, agent-base@^6.0.2, agent-base@~4.2.1: version "5.1.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== @@ -5185,6 +5512,15 @@ agentkeepalive@^3.4.1: dependencies: humanize-ms "^1.2.1" +agentkeepalive@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.2.1.tgz#a7975cbb9f83b367f06c90cc51ff28fe7d499717" + integrity sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA== + dependencies: + debug "^4.1.0" + depd "^1.1.2" + humanize-ms "^1.2.1" + aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" @@ -5574,10 +5910,10 @@ arr-union@^3.1.0: resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= -array-differ@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" - integrity sha1-7/UuN1gknTO+QCuLuOVkuytdQDE= +array-differ@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" + integrity sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg== array-equal@^1.0.0: version "1.0.0" @@ -5690,7 +6026,7 @@ array.prototype.tosorted@^1.1.1: es-shim-unscopables "^1.0.0" get-intrinsic "^1.1.3" -arrify@^1.0.0, arrify@^1.0.1: +arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= @@ -5857,10 +6193,10 @@ async@^2.4.1, async@^2.6.2, async@^2.6.4: dependencies: lodash "^4.17.14" -async@^3.0.1: - version "3.2.0" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" - integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== +async@^3.0.1, async@^3.2.3: + version "3.2.4" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" + integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== async@~0.2.9: version "0.2.10" @@ -5877,11 +6213,6 @@ at-least-node@^1.0.0: resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== -atob-lite@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/atob-lite/-/atob-lite-2.0.0.tgz#0fef5ad46f1bd7a8502c65727f0367d5ee43d696" - integrity sha1-D+9a1G8b16hQLGVyfwNn1e5D1pY= - atob@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" @@ -5953,6 +6284,15 @@ axios@^0.27.2: follow-redirects "^1.14.9" form-data "^4.0.0" +axios@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.2.0.tgz#1cb65bd75162c70e9f8d118a905126c4a201d383" + integrity sha512-zT7wZyNYu3N5Bu0wuZ6QccIf93Qk1eV8LOewxgjOZFd2DenOs98cJ7+Y6703d0wkaXGY6/nZd4EweJaHz9uzQw== + dependencies: + follow-redirects "^1.15.0" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" @@ -6774,10 +7114,10 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -before-after-hook@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.0.tgz#09c40d92e936c64777aa385c4e9b904f8147eaf0" - integrity sha512-jH6rKQIfroBbhEXVmI7XmXe3ix5S/PgJqpzdDPnR8JGLHWNYLsYZ6tK5iWOF/Ra3oqEX0NobXGlzbiylIzVphQ== +before-after-hook@^2.2.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c" + integrity sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ== big.js@^5.2.2: version "5.2.2" @@ -6799,6 +7139,18 @@ bignumber.js@^9.0.0: resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5" integrity sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA== +bin-links@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/bin-links/-/bin-links-3.0.3.tgz#3842711ef3db2cd9f16a5f404a996a12db355a6e" + integrity sha512-zKdnMPWEdh4F5INR07/eBrodC7QrF5JKvqskjz/ZZRXg5YSAZIbn8zGhbhUrElzHBZ2fvEQdOU59RHcTG3GiwA== + dependencies: + cmd-shim "^5.0.0" + mkdirp-infer-owner "^2.0.0" + npm-normalize-package-bin "^2.0.0" + read-cmd-shim "^3.0.0" + rimraf "^3.0.0" + write-file-atomic "^4.0.0" + binary-extensions@^1.0.0: version "1.13.1" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" @@ -7587,11 +7939,6 @@ bson@^1.1.4: resolved "https://registry.yarnpkg.com/bson/-/bson-1.1.6.tgz#fb819be9a60cd677e0853aee4ca712a785d6618a" integrity sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg== -btoa-lite@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337" - integrity sha1-M3dm2hWAEhD92VbCLpxokaudAzc= - btoa@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/btoa/-/btoa-1.2.1.tgz#01a9909f8b2c93f6bf680ba26131eb30f7fa3d73" @@ -7674,15 +8021,10 @@ builtins@^5.0.0: dependencies: semver "^7.0.0" -byline@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" - integrity sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE= - -byte-size@^4.0.3: - version "4.0.4" - resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-4.0.4.tgz#29d381709f41aae0d89c631f1c81aec88cd40b23" - integrity sha512-82RPeneC6nqCdSwCX2hZUz3JPOvN5at/nTEw/CMf05Smu3Hrpo9Psb7LjN+k+XndNArG1EY8L4+BM3aTM4BCvw== +byte-size@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-7.0.1.tgz#b1daf3386de7ab9d706b941a748dbfc71130dee3" + integrity sha512-crQdqyCwhokxwV1UyDzLZanhkugAgft7vt0qbbdt60C6Zf3CAiGmtUCylbtYwrU6loOUw3euGrNtW1J651ot1A== bytes@1: version "1.0.0" @@ -7727,26 +8069,6 @@ cacache@15.0.5: tar "^6.0.2" unique-filename "^1.1.1" -cacache@^11.3.3: - version "11.3.3" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.3.tgz#8bd29df8c6a718a6ebd2d010da4d7972ae3bbadc" - integrity sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA== - dependencies: - bluebird "^3.5.5" - chownr "^1.1.1" - figgy-pudding "^3.5.1" - glob "^7.1.4" - graceful-fs "^4.1.15" - lru-cache "^5.1.1" - mississippi "^3.0.0" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - promise-inflight "^1.0.1" - rimraf "^2.6.3" - ssri "^6.0.1" - unique-filename "^1.1.1" - y18n "^4.0.0" - cacache@^12.0.0, cacache@^12.0.2: version "12.0.4" resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c" @@ -7792,6 +8114,30 @@ cacache@^15.0.4, cacache@^15.0.5: tar "^6.0.2" unique-filename "^1.1.1" +cacache@^16.0.0, cacache@^16.0.6, cacache@^16.1.0: + version "16.1.3" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-16.1.3.tgz#a02b9f34ecfaf9a78c9f4bc16fceb94d5d67a38e" + integrity sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ== + dependencies: + "@npmcli/fs" "^2.1.0" + "@npmcli/move-file" "^2.0.0" + chownr "^2.0.0" + fs-minipass "^2.1.0" + glob "^8.0.1" + infer-owner "^1.0.4" + lru-cache "^7.7.1" + minipass "^3.1.6" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + mkdirp "^1.0.4" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^9.0.0" + tar "^6.1.11" + unique-filename "^2.0.0" + cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -7848,11 +8194,6 @@ call-bind@^1.0.0, call-bind@^1.0.2: function-bind "^1.1.1" get-intrinsic "^1.0.2" -call-me-maybe@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" - integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= - caller-callsite@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" @@ -7893,15 +8234,6 @@ camelcase-keys@^2.0.0: camelcase "^2.0.0" map-obj "^1.0.0" -camelcase-keys@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-4.2.0.tgz#a2aa5fb1af688758259c32c141426d78923b9b77" - integrity sha1-oqpfsa9oh1glnDLBQUJteJI7m3c= - dependencies: - camelcase "^4.1.0" - map-obj "^2.0.0" - quick-lru "^1.0.0" - camelcase-keys@^6.2.2: version "6.2.2" resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" @@ -7921,11 +8253,6 @@ camelcase@^2.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= -camelcase@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" - integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= - camelcase@^6.0.0, camelcase@^6.1.0, camelcase@^6.2.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" @@ -7990,7 +8317,7 @@ chai@^4.1.2: pathval "^1.1.1" type-detect "^4.0.5" -chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.4.1, chalk@^2.4.2: +chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -8015,6 +8342,14 @@ chalk@4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^1.0.0, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -8026,7 +8361,7 @@ chalk@^1.0.0, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: +chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -8122,11 +8457,6 @@ chrome-trace-event@^1.0.2: dependencies: tslib "^1.9.0" -ci-info@^1.5.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" - integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A== - ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" @@ -8219,6 +8549,13 @@ cli-boxes@^2.2.1: resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== +cli-cursor@3.1.0, cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + cli-cursor@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" @@ -8226,14 +8563,7 @@ cli-cursor@^2.1.0: dependencies: restore-cursor "^2.0.0" -cli-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" - integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== - dependencies: - restore-cursor "^3.1.0" - -cli-spinners@^2.0.0, cli-spinners@^2.4.0, cli-spinners@^2.5.0: +cli-spinners@2.6.1, cli-spinners@^2.0.0, cli-spinners@^2.4.0, cli-spinners@^2.5.0: version "2.6.1" resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.1.tgz#adc954ebe281c37a6319bfa401e6dd2488ffb70d" integrity sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g== @@ -8264,15 +8594,6 @@ cli-width@^3.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== -cliui@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" - integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ== - dependencies: - string-width "^2.1.1" - strip-ansi "^4.0.0" - wrap-ansi "^2.0.0" - cliui@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" @@ -8320,6 +8641,15 @@ clone-deep@^0.2.4: lazy-cache "^1.0.3" shallow-clone "^0.1.2" +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + clone-response@1.0.2, clone-response@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" @@ -8337,13 +8667,12 @@ clone@^2.1.2: resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= -cmd-shim@^2.0.2: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-2.1.0.tgz#e59a08d4248dda3bb502044083a4db4ac890579a" - integrity sha512-A5C0Cyf2H8sKsHqX0tvIWRXw5/PK++3Dc0lDbsugr90nOECLLuSPahVQBG8pgmgiXgm/TzBWMqI2rWdZwHduAw== +cmd-shim@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-5.0.0.tgz#8d0aaa1a6b0708630694c4dbde070ed94c707724" + integrity sha512-qkCtZ59BidfEwHltnJwkyVZn+XQojdAySM1D1gSeh11Z4pW1Kpolkyo53L5noc0nrxmIvyFwTmJRo4xs7FFLPw== dependencies: - graceful-fs "^4.1.2" - mkdirp "~0.5.0" + mkdirp-infer-owner "^2.0.0" co@^4.6.0: version "4.6.0" @@ -8448,12 +8777,12 @@ colors@1.4.0, colors@^1.4.0: resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== -columnify@^1.5.4: - version "1.5.4" - resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.5.4.tgz#4737ddf1c7b69a8a7c340570782e947eec8e78bb" - integrity sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs= +columnify@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.6.0.tgz#6989531713c9008bb29735e61e37acf5bd553cf3" + integrity sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q== dependencies: - strip-ansi "^3.0.0" + strip-ansi "^6.0.1" wcwidth "^1.0.0" combine-source-map@^0.8.0: @@ -8525,6 +8854,11 @@ commenting@1.1.0: resolved "https://registry.yarnpkg.com/commenting/-/commenting-1.1.0.tgz#fae14345c6437b8554f30bc6aa6c1e1633033590" integrity sha512-YeNK4tavZwtH7jEgK1ZINXzLKm6DZdEMfsaaieOsCAN0S8vsY7UeuO3Q7d/M018EFgE+IeUAuBOKkFccBZsUZA== +common-ancestor-path@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz#4f7d2d1394d91b7abdf51871c62f71eadb0182a7" + integrity sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w== + common-tags@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" @@ -8600,10 +8934,10 @@ concat-stream@^2.0.0: readable-stream "^3.0.2" typedarray "^0.0.6" -config-chain@^1.1.11: - version "1.1.12" - resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" - integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA== +config-chain@^1.1.12: + version "1.1.13" + resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4" + integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ== dependencies: ini "^1.3.4" proto-list "~1.2.1" @@ -8690,47 +9024,47 @@ continuable-cache@^0.3.1: resolved "https://registry.yarnpkg.com/continuable-cache/-/continuable-cache-0.3.1.tgz#bd727a7faed77e71ff3985ac93351a912733ad0f" integrity sha1-vXJ6f67XfnH/OYWskzUakSczrQ8= -conventional-changelog-angular@^5.0.3: - version "5.0.12" - resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.12.tgz#c979b8b921cbfe26402eb3da5bbfda02d865a2b9" - integrity sha512-5GLsbnkR/7A89RyHLvvoExbiGbd9xKdKqDTrArnPbOqBqG/2wIosu0fHwpeIRI8Tl94MhVNBXcLJZl92ZQ5USw== +conventional-changelog-angular@^5.0.12: + version "5.0.13" + resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz#896885d63b914a70d4934b59d2fe7bde1832b28c" + integrity sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA== dependencies: compare-func "^2.0.0" q "^1.5.1" -conventional-changelog-core@^3.1.6: - version "3.2.3" - resolved "https://registry.yarnpkg.com/conventional-changelog-core/-/conventional-changelog-core-3.2.3.tgz#b31410856f431c847086a7dcb4d2ca184a7d88fb" - integrity sha512-LMMX1JlxPIq/Ez5aYAYS5CpuwbOk6QFp8O4HLAcZxe3vxoCtABkhfjetk8IYdRB9CDQGwJFLR3Dr55Za6XKgUQ== +conventional-changelog-core@^4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/conventional-changelog-core/-/conventional-changelog-core-4.2.4.tgz#e50d047e8ebacf63fac3dc67bf918177001e1e9f" + integrity sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg== dependencies: - conventional-changelog-writer "^4.0.6" - conventional-commits-parser "^3.0.3" + add-stream "^1.0.0" + conventional-changelog-writer "^5.0.0" + conventional-commits-parser "^3.2.0" dateformat "^3.0.0" - get-pkg-repo "^1.0.0" - git-raw-commits "2.0.0" + get-pkg-repo "^4.0.0" + git-raw-commits "^2.0.8" git-remote-origin-url "^2.0.0" - git-semver-tags "^2.0.3" - lodash "^4.2.1" - normalize-package-data "^2.3.5" + git-semver-tags "^4.1.1" + lodash "^4.17.15" + normalize-package-data "^3.0.0" q "^1.5.1" read-pkg "^3.0.0" read-pkg-up "^3.0.0" - through2 "^3.0.0" + through2 "^4.0.0" -conventional-changelog-preset-loader@^2.1.1: +conventional-changelog-preset-loader@^2.3.4: version "2.3.4" resolved "https://registry.yarnpkg.com/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz#14a855abbffd59027fd602581f1f34d9862ea44c" integrity sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g== -conventional-changelog-writer@^4.0.6: - version "4.1.0" - resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-4.1.0.tgz#1ca7880b75aa28695ad33312a1f2366f4b12659f" - integrity sha512-WwKcUp7WyXYGQmkLsX4QmU42AZ1lqlvRW9mqoyiQzdD+rJWbTepdWoKJuwXTS+yq79XKnQNa93/roViPQrAQgw== +conventional-changelog-writer@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-5.0.1.tgz#e0757072f045fe03d91da6343c843029e702f359" + integrity sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ== dependencies: - compare-func "^2.0.0" conventional-commits-filter "^2.0.7" dateformat "^3.0.0" - handlebars "^4.7.6" + handlebars "^4.7.7" json-stringify-safe "^5.0.1" lodash "^4.17.15" meow "^8.0.0" @@ -8738,7 +9072,7 @@ conventional-changelog-writer@^4.0.6: split "^1.0.0" through2 "^4.0.0" -conventional-commits-filter@^2.0.2, conventional-commits-filter@^2.0.7: +conventional-commits-filter@^2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz#f8d9b4f182fce00c9af7139da49365b136c8a0b3" integrity sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA== @@ -8746,10 +9080,10 @@ conventional-commits-filter@^2.0.2, conventional-commits-filter@^2.0.7: lodash.ismatch "^4.4.0" modify-values "^1.0.0" -conventional-commits-parser@^3.0.2, conventional-commits-parser@^3.0.3: - version "3.2.1" - resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-3.2.1.tgz#ba44f0b3b6588da2ee9fd8da508ebff50d116ce2" - integrity sha512-OG9kQtmMZBJD/32NEw5IhN5+HnBqVjy03eC+I71I0oQRFA5rOgA4OtPOYG7mz1GkCfCNxn3gKIX8EiHJYuf1cA== +conventional-commits-parser@^3.2.0: + version "3.2.4" + resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-3.2.4.tgz#a7d3b77758a202a9b2293d2112a8d8052c740972" + integrity sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q== dependencies: JSONStream "^1.0.4" is-text-path "^1.0.1" @@ -8757,20 +9091,19 @@ conventional-commits-parser@^3.0.2, conventional-commits-parser@^3.0.3: meow "^8.0.0" split2 "^3.0.0" through2 "^4.0.0" - trim-off-newlines "^1.0.0" -conventional-recommended-bump@^4.0.4: - version "4.1.1" - resolved "https://registry.yarnpkg.com/conventional-recommended-bump/-/conventional-recommended-bump-4.1.1.tgz#37014fadeda267d0607e2fc81124da840a585127" - integrity sha512-JT2vKfSP9kR18RXXf55BRY1O3AHG8FPg5btP3l7LYfcWJsiXI6MCf30DepQ98E8Qhowvgv7a8iev0J1bEDkTFA== +conventional-recommended-bump@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/conventional-recommended-bump/-/conventional-recommended-bump-6.1.0.tgz#cfa623285d1de554012f2ffde70d9c8a22231f55" + integrity sha512-uiApbSiNGM/kkdL9GTOLAqC4hbptObFo4wW2QRyHsKciGAfQuLU1ShZ1BIVI/+K2BE/W1AWYQMCXAsv4dyKPaw== dependencies: concat-stream "^2.0.0" - conventional-changelog-preset-loader "^2.1.1" - conventional-commits-filter "^2.0.2" - conventional-commits-parser "^3.0.2" - git-raw-commits "2.0.0" - git-semver-tags "^2.0.2" - meow "^4.0.0" + conventional-changelog-preset-loader "^2.3.4" + conventional-commits-filter "^2.0.7" + conventional-commits-parser "^3.2.0" + git-raw-commits "^2.0.8" + git-semver-tags "^4.1.1" + meow "^8.0.0" q "^1.5.1" convert-source-map@1.7.0, convert-source-map@^1.4.0, convert-source-map@^1.5.1, convert-source-map@^1.6.0, convert-source-map@^1.7.0: @@ -8898,7 +9231,7 @@ cors@^2.8.5, cors@~2.8.5: object-assign "^4" vary "^1" -cosmiconfig@^5.0.0, cosmiconfig@^5.1.0: +cosmiconfig@^5.0.0: version "5.2.1" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== @@ -8908,6 +9241,17 @@ cosmiconfig@^5.0.0, cosmiconfig@^5.1.0: js-yaml "^3.13.1" parse-json "^4.0.0" +cosmiconfig@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" + integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + create-ecdh@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" @@ -9314,12 +9658,10 @@ dag-map@^2.0.2: resolved "https://registry.yarnpkg.com/dag-map/-/dag-map-2.0.2.tgz#9714b472de82a1843de2fba9b6876938cab44c68" integrity sha1-lxS0ct6CoYQ94vuptodpOMq0TGg= -dargs@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/dargs/-/dargs-4.1.0.tgz#03a9dbb4b5c2f139bf14ae53f0b8a2a6a86f4e17" - integrity sha1-A6nbtLXC8Tm/FK5T8LiipqhvThc= - dependencies: - number-is-nan "^1.0.0" +dargs@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc" + integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== dashdash@^1.12.0: version "1.14.1" @@ -9405,7 +9747,7 @@ debug@3.2.6: dependencies: ms "^2.1.1" -debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@~4.3.1, debug@~4.3.2: +debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -9431,7 +9773,7 @@ debuglog@^1.0.1: resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= -decamelize-keys@^1.0.0, decamelize-keys@^1.1.0: +decamelize-keys@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk= @@ -9527,6 +9869,11 @@ defer-to-connect@^1.0.1: resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== +define-lazy-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" + integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== + define-properties@^1.1.2, define-properties@^1.1.3, define-properties@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" @@ -9590,10 +9937,10 @@ depd@2.0.0, depd@~2.0.0: resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== -depd@~1.1.2: +depd@^1.1.2, depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== dependency-graph@^0.7.2: version "0.7.2" @@ -9795,14 +10142,6 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" -dir-glob@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.0.0.tgz#0b205d2b6aef98238ca286598a8204d29d0a0034" - integrity sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag== - dependencies: - arrify "^1.0.1" - path-type "^3.0.0" - dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -9957,13 +10296,6 @@ dot-case@^3.0.4: no-case "^3.0.4" tslib "^2.0.3" -dot-prop@^4.2.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.1.tgz#45884194a71fc2cda71cbb4bceb3a4dd2f433ba4" - integrity sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ== - dependencies: - is-obj "^1.0.0" - dot-prop@^5.1.0, dot-prop@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" @@ -9971,6 +10303,23 @@ dot-prop@^5.1.0, dot-prop@^5.2.0: dependencies: is-obj "^2.0.0" +dot-prop@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-6.0.1.tgz#fc26b3cf142b9e59b74dbd39ed66ce620c681083" + integrity sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA== + dependencies: + is-obj "^2.0.0" + +dotenv@16.0.3: + version "16.0.3" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07" + integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== + +dotenv@~10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== + duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" @@ -10034,6 +10383,13 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= +ejs@^3.1.7: + version "3.1.8" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.8.tgz#758d32910c78047585c7ef1f92f9ee041c1c190b" + integrity sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ== + dependencies: + jake "^10.8.5" + electron-to-chromium@^1.3.47, electron-to-chromium@^1.3.634, electron-to-chromium@^1.4.251: version "1.4.284" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" @@ -10750,7 +11106,7 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= -encoding@0.1.13, encoding@^0.1.11: +encoding@0.1.13, encoding@^0.1.11, encoding@^0.1.13: version "0.1.13" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== @@ -10813,7 +11169,7 @@ enhanced-resolve@^5.10.0, enhanced-resolve@^5.3.2: graceful-fs "^4.2.4" tapable "^2.2.0" -enquirer@^2.3.5: +enquirer@^2.3.5, enquirer@~2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== @@ -10845,11 +11201,26 @@ entities@~3.0.1: resolved "https://registry.yarnpkg.com/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4" integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q== +env-paths@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + +envinfo@^7.7.4: + version "7.8.1" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475" + integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== + err-code@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/err-code/-/err-code-1.1.2.tgz#06e0116d3028f6aef4806849eb0ea6a748ae6960" integrity sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA= +err-code@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" + integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== + errlop@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/errlop/-/errlop-2.2.0.tgz#1ff383f8f917ae328bebb802d6ca69666a42d21b" @@ -11189,6 +11560,11 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== +eslint-visitor-keys@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" + integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== + eslint@7.32.0: version "7.32.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" @@ -11264,7 +11640,7 @@ esprima@~3.0.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.0.0.tgz#53cf247acda77313e551c3aa2e73342d3fb4f7d9" integrity sha1-U88kes2ncxPlUcOqLnM0LT+099k= -esquery@^1.4.0: +esquery@^1.0.1, esquery@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== @@ -11336,7 +11712,7 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== -eventemitter3@^4.0.0: +eventemitter3@^4.0.0, eventemitter3@^4.0.4: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== @@ -11604,17 +11980,16 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^2.0.2: - version "2.2.7" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" - integrity sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw== +fast-glob@3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" + integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== dependencies: - "@mrmlnc/readdir-enhanced" "^2.2.1" - "@nodelib/fs.stat" "^1.1.2" - glob-parent "^3.1.0" - is-glob "^4.0.0" - merge2 "^1.2.3" - micromatch "^3.1.10" + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" fast-glob@^3.0.3, fast-glob@^3.2.11, fast-glob@^3.2.4, fast-glob@^3.2.9: version "3.2.12" @@ -11721,6 +12096,13 @@ figgy-pudding@^3.4.1, figgy-pudding@^3.5.1: resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== +figures@3.2.0, figures@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + figures@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" @@ -11728,13 +12110,6 @@ figures@^2.0.0: dependencies: escape-string-regexp "^1.0.5" -figures@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" - integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== - dependencies: - escape-string-regexp "^1.0.5" - file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -11763,6 +12138,13 @@ file-uri-to-path@1.0.0: resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== +filelist@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + filesize@^9.0.11: version "9.0.11" resolved "https://registry.yarnpkg.com/filesize/-/filesize-9.0.11.tgz#4ac3a42c084232dd9b2a1da0107f32d42fcfa5e4" @@ -11804,11 +12186,6 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -filter-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" - integrity sha1-mzERErxsYSehbgFsbF1/GeCAXFs= - finalhandler@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" @@ -12041,6 +12418,11 @@ flat@^4.1.0: dependencies: is-buffer "~2.0.3" +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + flatted@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" @@ -12064,10 +12446,10 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" -follow-redirects@^1.0.0, follow-redirects@^1.14.9: - version "1.15.1" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5" - integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== +follow-redirects@^1.0.0, follow-redirects@^1.14.9, follow-redirects@^1.15.0: + version "1.15.2" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" + integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== for-each@^0.3.3: version "0.3.3" @@ -12269,7 +12651,7 @@ fs-minipass@^1.2.7: dependencies: minipass "^2.6.0" -fs-minipass@^2.0.0: +fs-minipass@^2.0.0, fs-minipass@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== @@ -12436,11 +12818,6 @@ get-amd-module-type@^3.0.0: ast-module-types "^2.3.2" node-source-walk "^4.0.0" -get-caller-file@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" - integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== - get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -12477,21 +12854,15 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-pkg-repo@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/get-pkg-repo/-/get-pkg-repo-1.4.0.tgz#c73b489c06d80cc5536c2c853f9e05232056972d" - integrity sha1-xztInAbYDMVTbCyFP54FIyBWly0= +get-pkg-repo@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/get-pkg-repo/-/get-pkg-repo-4.2.1.tgz#75973e1c8050c73f48190c52047c4cee3acbf385" + integrity sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA== dependencies: - hosted-git-info "^2.1.4" - meow "^3.3.0" - normalize-package-data "^2.3.0" - parse-github-repo-url "^1.3.0" + "@hutson/parse-repository-url" "^3.0.0" + hosted-git-info "^4.0.0" through2 "^2.0.0" - -get-port@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" - integrity sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw= + yargs "^16.2.0" get-port@^5.1.1: version "5.1.1" @@ -12562,16 +12933,16 @@ git-hooks-list@1.0.3: resolved "https://registry.yarnpkg.com/git-hooks-list/-/git-hooks-list-1.0.3.tgz#be5baaf78203ce342f2f844a9d2b03dba1b45156" integrity sha512-Y7wLWcrLUXwk2noSka166byGCvhMtDRpgHdzCno1UQv/n/Hegp++a2xBWJL1lJarnKD3SWaljD+0z1ztqxuKyQ== -git-raw-commits@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-2.0.0.tgz#d92addf74440c14bcc5c83ecce3fb7f8a79118b5" - integrity sha512-w4jFEJFgKXMQJ0H0ikBk2S+4KP2VEjhCvLCNqbNRQC8BgGWgLKNCO7a9K9LI+TVT7Gfoloje502sEnctibffgg== +git-raw-commits@^2.0.8: + version "2.0.11" + resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-2.0.11.tgz#bc3576638071d18655e1cc60d7f524920008d723" + integrity sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A== dependencies: - dargs "^4.0.1" - lodash.template "^4.0.2" - meow "^4.0.0" - split2 "^2.0.0" - through2 "^2.0.0" + dargs "^7.0.0" + lodash "^4.17.15" + meow "^8.0.0" + split2 "^3.0.0" + through2 "^4.0.0" git-remote-origin-url@^2.0.0: version "2.0.0" @@ -12586,28 +12957,28 @@ git-repo-info@^2.1.1: resolved "https://registry.yarnpkg.com/git-repo-info/-/git-repo-info-2.1.1.tgz#220ffed8cbae74ef8a80e3052f2ccb5179aed058" integrity sha512-8aCohiDo4jwjOwma4FmYFd3i97urZulL8XL24nIPxuE+GZnfsAyy/g2Shqx6OjUiFKUXZM+Yy+KHnOmmA3FVcg== -git-semver-tags@^2.0.2, git-semver-tags@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/git-semver-tags/-/git-semver-tags-2.0.3.tgz#48988a718acf593800f99622a952a77c405bfa34" - integrity sha512-tj4FD4ww2RX2ae//jSrXZzrocla9db5h0V7ikPl1P/WwoZar9epdUhwR7XHXSgc+ZkNq72BEEerqQuicoEQfzA== +git-semver-tags@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/git-semver-tags/-/git-semver-tags-4.1.1.tgz#63191bcd809b0ec3e151ba4751c16c444e5b5780" + integrity sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA== dependencies: - meow "^4.0.0" + meow "^8.0.0" semver "^6.0.0" -git-up@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/git-up/-/git-up-4.0.2.tgz#10c3d731051b366dc19d3df454bfca3f77913a7c" - integrity sha512-kbuvus1dWQB2sSW4cbfTeGpCMd8ge9jx9RKnhXhuJ7tnvT+NIrTVfYZxjtflZddQYcmdOTlkAcjmx7bor+15AQ== +git-up@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/git-up/-/git-up-7.0.0.tgz#bace30786e36f56ea341b6f69adfd83286337467" + integrity sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ== dependencies: - is-ssh "^1.3.0" - parse-url "^5.0.0" + is-ssh "^1.4.0" + parse-url "^8.1.0" -git-url-parse@^11.1.2: - version "11.4.4" - resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-11.4.4.tgz#5d747debc2469c17bc385719f7d0427802d83d77" - integrity sha512-Y4o9o7vQngQDIU9IjyCmRJBin5iYjI5u9ZITnddRZpD7dcCFQj2sL2XuMNbLRE4b4B/4ENPsp2Q8P44fjAZ0Pw== +git-url-parse@^13.1.0: + version "13.1.0" + resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-13.1.0.tgz#07e136b5baa08d59fabdf0e33170de425adf07b4" + integrity sha512-5FvPJP/70WkIprlUZ33bm4UAaFdjcLkJLpWft1BeZKqwR0uhhNGoKwlUaPtVb4LxCSQ++erHapRak9kWGj+FCA== dependencies: - git-up "^4.0.0" + git-up "^7.0.0" gitconfiglocal@^1.0.0: version "1.0.0" @@ -12631,11 +13002,6 @@ glob-parent@^5.1.1, glob-parent@^5.1.2, glob-parent@~5.1.0, glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" -glob-to-regexp@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" - integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= - glob-to-regexp@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" @@ -12653,6 +13019,18 @@ glob@7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" +glob@7.1.4: + version "7.1.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" + integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" @@ -12677,7 +13055,7 @@ glob@7.2.0: once "^1.3.0" path-is-absolute "^1.0.0" -glob@8.0.3: +glob@8.0.3, glob@^8.0.1, glob@^8.0.3: version "8.0.3" resolved "https://registry.yarnpkg.com/glob/-/glob-8.0.3.tgz#415c6eb2deed9e502c68fa44a272e6da6eeca42e" integrity sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ== @@ -12792,7 +13170,7 @@ globby@10.0.0: merge2 "^1.2.3" slash "^3.0.0" -globby@^11.0.1, globby@^11.0.2, globby@^11.0.3: +globby@^11.0.1, globby@^11.0.2, globby@^11.0.3, globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== @@ -12826,19 +13204,6 @@ globby@^6.1.0: pify "^2.0.0" pinkie-promise "^2.0.0" -globby@^8.0.1: - version "8.0.2" - resolved "https://registry.yarnpkg.com/globby/-/globby-8.0.2.tgz#5697619ccd95c5275dbb2d6faa42087c1a941d8d" - integrity sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w== - dependencies: - array-union "^1.0.1" - dir-glob "2.0.0" - fast-glob "^2.0.2" - glob "^7.1.2" - ignore "^3.3.5" - pify "^3.0.0" - slash "^1.0.0" - globrex@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" @@ -13010,7 +13375,7 @@ handle-thing@^2.0.0: resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== -handlebars@^4.0.1, handlebars@^4.0.4, handlebars@^4.3.1, handlebars@^4.7.3, handlebars@^4.7.6: +handlebars@^4.0.1, handlebars@^4.0.4, handlebars@^4.3.1, handlebars@^4.7.3, handlebars@^4.7.6, handlebars@^4.7.7: version "4.7.7" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== @@ -13326,17 +13691,17 @@ hosted-git-info@^2.1.4, hosted-git-info@^2.7.1: resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== -hosted-git-info@^3.0.2: +hosted-git-info@^3.0.2, hosted-git-info@^3.0.6: version "3.0.8" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.8.tgz#6e35d4cc87af2c5f816e4cb9ce350ba87a3f370d" integrity sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw== dependencies: lru-cache "^6.0.0" -hosted-git-info@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.0.2.tgz#5e425507eede4fea846b7262f0838456c4209961" - integrity sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg== +hosted-git-info@^4.0.0, hosted-git-info@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224" + integrity sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA== dependencies: lru-cache "^6.0.0" @@ -13435,7 +13800,7 @@ http-cache-semantics@3.8.1, http-cache-semantics@^3.8.1: resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w== -http-cache-semantics@^4.0.0: +http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== @@ -13640,20 +14005,22 @@ ignore-walk@3.0.3, ignore-walk@^3.0.1, ignore-walk@^3.0.3: dependencies: minimatch "^3.0.4" -ignore@^3.3.5: - version "3.3.10" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" - integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== +ignore-walk@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-5.0.1.tgz#5f199e23e1288f518d90358d461387788a154776" + integrity sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw== + dependencies: + minimatch "^5.0.1" ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.1.1, ignore@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" - integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== +ignore@^5.0.4, ignore@^5.1.1, ignore@^5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.1.tgz#c2b1f76cb999ede1502f3a226a9310fdfe88d46c" + integrity sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA== image-size@~0.5.0: version "0.5.5" @@ -13705,14 +14072,6 @@ import-lazy@^2.1.0: resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" integrity sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A== -import-local@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-1.0.0.tgz#5e4ffdc03f4fe6c009c6729beb29631c2f8227bc" - integrity sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ== - dependencies: - pkg-dir "^2.0.0" - resolve-cwd "^2.0.0" - import-local@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" @@ -13741,11 +14100,6 @@ indent-string@^2.1.0: dependencies: repeating "^2.0.0" -indent-string@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" - integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok= - indent-string@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" @@ -13804,19 +14158,18 @@ ini@^1.3.2, ini@^1.3.4, ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== -init-package-json@^1.10.3: - version "1.10.3" - resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-1.10.3.tgz#45ffe2f610a8ca134f2bd1db5637b235070f6cbe" - integrity sha512-zKSiXKhQveNteyhcj1CoOP8tqp1QuxPIPBl8Bid99DGLFqA1p87M6lNgfjJHSBoWJJlidGOv5rWjyYKEB3g2Jw== +init-package-json@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-3.0.2.tgz#f5bc9bac93f2bdc005778bc2271be642fecfcd69" + integrity sha512-YhlQPEjNFqlGdzrBfDNRLhvoSgX7iQRgSxgsNknRQ9ITXFT7UMfVMWhBTOh2Y+25lRnGrv5Xz8yZwQ3ACR6T3A== dependencies: - glob "^7.1.1" - npm-package-arg "^4.0.0 || ^5.0.0 || ^6.0.0" + npm-package-arg "^9.0.1" promzard "^0.3.0" - read "~1.0.1" - read-package-json "1 || 2" - semver "2.x || 3.x || 4 || 5" - validate-npm-package-license "^3.0.1" - validate-npm-package-name "^3.0.0" + read "^1.0.7" + read-package-json "^5.0.0" + semver "^7.3.5" + validate-npm-package-license "^3.0.4" + validate-npm-package-name "^4.0.0" injection-js@^2.2.1: version "2.4.0" @@ -13862,7 +14215,7 @@ inquirer@7.3.3, inquirer@^7.0.1: strip-ansi "^6.0.0" through "^2.3.6" -inquirer@^6, inquirer@^6.2.0: +inquirer@^6: version "6.5.2" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca" integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ== @@ -13881,6 +14234,27 @@ inquirer@^6, inquirer@^6.2.0: strip-ansi "^5.1.0" through "^2.3.6" +inquirer@^8.2.4: + version "8.2.5" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.5.tgz#d8654a7542c35a9b9e069d27e2df4858784d54f8" + integrity sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.1" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.21" + mute-stream "0.0.8" + ora "^5.4.1" + run-async "^2.4.0" + rxjs "^7.5.5" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + wrap-ansi "^7.0.0" + internal-ip@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907" @@ -13918,11 +14292,6 @@ invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4: dependencies: loose-envify "^1.0.0" -invert-kv@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" - integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== - ip-regex@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" @@ -13933,6 +14302,11 @@ ip@1.1.5, ip@^1.1.0, ip@^1.1.5: resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= +ip@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" + integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== + ipaddr.js@1.9.1, ipaddr.js@^1.9.0: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" @@ -14020,13 +14394,6 @@ is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-ci@^1.0.10: - version "1.2.1" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c" - integrity sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg== - dependencies: - ci-info "^1.5.0" - is-ci@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" @@ -14046,7 +14413,7 @@ is-color-stop@^1.0.0: rgb-regex "^1.0.1" rgba-regex "^1.0.0" -is-core-module@^2.2.0, is-core-module@^2.9.0: +is-core-module@^2.2.0, is-core-module@^2.8.1, is-core-module@^2.9.0: version "2.11.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== @@ -14095,7 +14462,7 @@ is-directory@^0.3.1: resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= -is-docker@^2.0.0: +is-docker@^2.0.0, is-docker@^2.1.1: version "2.2.1" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== @@ -14181,6 +14548,11 @@ is-interactive@^1.0.0: resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== +is-lambda@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" + integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== + is-language-code@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/is-language-code/-/is-language-code-3.1.0.tgz#b2386b49227e7010636f16d0c2c681ca40136ab5" @@ -14228,7 +14600,7 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-obj@^1.0.0, is-obj@^1.0.1: +is-obj@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= @@ -14267,7 +14639,7 @@ is-path-inside@^3.0.2: resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== -is-plain-obj@2.1.0: +is-plain-obj@2.1.0, is-plain-obj@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== @@ -14294,7 +14666,7 @@ is-potential-custom-element-name@^1.0.1: resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== -is-reference@^1.2.1: +is-reference@1.2.1, is-reference@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ== @@ -14341,12 +14713,12 @@ is-shared-array-buffer@^1.0.2: dependencies: call-bind "^1.0.2" -is-ssh@^1.3.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.3.2.tgz#a4b82ab63d73976fd8263cceee27f99a88bdae2b" - integrity sha512-elEw0/0c2UscLrNG+OAorbP539E3rhliKPg+hDMWN9VwrDXfYK+4PBEykDPfxlYYtQvl84TascnQyobfQLHEhQ== +is-ssh@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.4.0.tgz#4f8220601d2839d8fa624b3106f8e8884f01b8b2" + integrity sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ== dependencies: - protocols "^1.1.0" + protocols "^2.0.1" is-stream-ended@^0.1.4: version "0.1.4" @@ -14639,6 +15011,16 @@ isurl@^1.0.0-alpha5: has-to-string-tag-x "^1.2.0" is-object "^1.0.1" +jake@^10.8.5: + version "10.8.5" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46" + integrity sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.1" + minimatch "^3.0.4" + jest-changed-files@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-27.5.1.tgz#a348aed00ec9bf671cc58a66fcbe7c3dfd6a68f5" @@ -15234,7 +15616,7 @@ js-yaml@3.14.0: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@3.x, js-yaml@^3.13.1, js-yaml@^3.2.5, js-yaml@^3.2.7: +js-yaml@3.x, js-yaml@^3.10.0, js-yaml@^3.13.1, js-yaml@^3.2.5, js-yaml@^3.2.7: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== @@ -15242,7 +15624,7 @@ js-yaml@3.x, js-yaml@^3.13.1, js-yaml@^3.2.5, js-yaml@^3.2.7: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^4.1.0: +js-yaml@4.1.0, js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== @@ -15407,6 +15789,11 @@ json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1: dependencies: jsonify "~0.0.0" +json-stringify-nice@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz#2c937962b80181d3f317dd39aa323e14f5a60a67" + integrity sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw== + json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -15439,6 +15826,11 @@ jsonc-parser@2.3.0: resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.3.0.tgz#7c7fc988ee1486d35734faaaa866fadb00fa91ee" integrity sha512-b0EBt8SWFNnixVdvoR2ZtEGa9ZqLhbJnOjezn+WP+8kspFm+PFYDN8Z4Bc7pRlDjvuVcADSUkroIuTWWn/YiIA== +jsonc-parser@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" + integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== + jsonfile@^2.1.0: version "2.4.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" @@ -15467,10 +15859,10 @@ jsonify@~0.0.0: resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= -jsonparse@^1.2.0: +jsonparse@^1.2.0, jsonparse@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" - integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= + integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== jsprim@^1.2.2: version "1.4.1" @@ -15490,6 +15882,16 @@ jsprim@^1.2.2: array-includes "^3.1.2" object.assign "^4.1.2" +just-diff-apply@^5.2.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/just-diff-apply/-/just-diff-apply-5.4.1.tgz#1debed059ad009863b4db0e8d8f333d743cdd83b" + integrity sha512-AAV5Jw7tsniWwih8Ly3fXxEZ06y+6p5TwQMsw0dzZ/wPKilzyDgdAnL0Ug4NNIquPUOh1vfFWEHbmXUqM5+o8g== + +just-diff@^5.0.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/just-diff/-/just-diff-5.1.1.tgz#8da6414342a5ed6d02ccd64f5586cbbed3146202" + integrity sha512-u8HXJ3HlNrTzY7zrYYKjNEfBlyjqhdBkoyTVdjtn7p02RJD5NvR8rIClzeGA7t+UYP1/7eAkWNLU0+P3QrEqKQ== + just-extend@^4.0.2: version "4.1.1" resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.1.1.tgz#158f1fdb01f128c411dc8b286a7b4837b3545282" @@ -15796,13 +16198,6 @@ lazy-cache@^1.0.3: resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" integrity sha1-odePw6UEdMuAhF07O24dpJpEbo4= -lcid@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf" - integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA== - dependencies: - invert-kv "^2.0.0" - leek@0.0.24: version "0.0.24" resolved "https://registry.yarnpkg.com/leek/-/leek-0.0.24.tgz#e400e57f0e60d8ef2bd4d068dc428a54345dbcda" @@ -15812,28 +16207,34 @@ leek@0.0.24: lodash.assign "^3.2.0" rsvp "^3.0.21" -lerna@3.13.4: - version "3.13.4" - resolved "https://registry.yarnpkg.com/lerna/-/lerna-3.13.4.tgz#03026c11c5643f341fda42e4fb1882e2df35e6cb" - integrity sha512-qTp22nlpcgVrJGZuD7oHnFbTk72j2USFimc2Pj4kC0/rXmcU2xPtCiyuxLl8y6/6Lj5g9kwEuvKDZtSXujjX/A== - dependencies: - "@lerna/add" "3.13.3" - "@lerna/bootstrap" "3.13.3" - "@lerna/changed" "3.13.4" - "@lerna/clean" "3.13.3" - "@lerna/cli" "3.13.0" - "@lerna/create" "3.13.3" - "@lerna/diff" "3.13.3" - "@lerna/exec" "3.13.3" - "@lerna/import" "3.13.4" - "@lerna/init" "3.13.3" - "@lerna/link" "3.13.3" - "@lerna/list" "3.13.3" - "@lerna/publish" "3.13.4" - "@lerna/run" "3.13.3" - "@lerna/version" "3.13.4" - import-local "^1.0.0" - npmlog "^4.1.2" +lerna@^6.0.3: + version "6.1.0" + resolved "https://registry.yarnpkg.com/lerna/-/lerna-6.1.0.tgz#693145393ec22fd3ca98d817deab2246c1e2b107" + integrity sha512-3qAjIj8dgBwHtCAiLbq4VU/C1V9D1tvTLm2owZubdGAN72aB5TxuCu2mcw+yeEorOcXuR9YWx7EXIkAf+G0N2w== + dependencies: + "@lerna/add" "6.1.0" + "@lerna/bootstrap" "6.1.0" + "@lerna/changed" "6.1.0" + "@lerna/clean" "6.1.0" + "@lerna/cli" "6.1.0" + "@lerna/command" "6.1.0" + "@lerna/create" "6.1.0" + "@lerna/diff" "6.1.0" + "@lerna/exec" "6.1.0" + "@lerna/import" "6.1.0" + "@lerna/info" "6.1.0" + "@lerna/init" "6.1.0" + "@lerna/link" "6.1.0" + "@lerna/list" "6.1.0" + "@lerna/publish" "6.1.0" + "@lerna/run" "6.1.0" + "@lerna/version" "6.1.0" + "@nrwl/devkit" ">=14.8.6 < 16" + import-local "^3.0.2" + inquirer "^8.2.4" + npmlog "^6.0.2" + nx ">=14.8.6 < 16" + typescript "^3 || ^4" less-loader@6.2.0: version "6.2.0" @@ -15889,30 +16290,26 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -libnpmaccess@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-3.0.2.tgz#8b2d72345ba3bef90d3b4f694edd5c0417f58923" - integrity sha512-01512AK7MqByrI2mfC7h5j8N9V4I7MHJuk9buo8Gv+5QgThpOgpjB7sQBDDkeZqRteFb1QM/6YNdHfG7cDvfAQ== +libnpmaccess@^6.0.3: + version "6.0.4" + resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-6.0.4.tgz#2dd158bd8a071817e2207d3b201d37cf1ad6ae6b" + integrity sha512-qZ3wcfIyUoW0+qSFkMBovcTrSGJ3ZeyvpR7d5N9pEYv/kXs8sHP2wiqEIXBKLFrZlmM0kR0RJD7mtfLngtlLag== dependencies: aproba "^2.0.0" - get-stream "^4.0.0" - npm-package-arg "^6.1.0" - npm-registry-fetch "^4.0.0" + minipass "^3.1.1" + npm-package-arg "^9.0.1" + npm-registry-fetch "^13.0.0" -libnpmpublish@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/libnpmpublish/-/libnpmpublish-1.1.3.tgz#e3782796722d79eef1a0a22944c117e0c4ca4280" - integrity sha512-/3LsYqVc52cHXBmu26+J8Ed7sLs/hgGVFMH1mwYpL7Qaynb9RenpKqIKu0sJ130FB9PMkpMlWjlbtU8A4m7CQw== +libnpmpublish@^6.0.4: + version "6.0.5" + resolved "https://registry.yarnpkg.com/libnpmpublish/-/libnpmpublish-6.0.5.tgz#5a894f3de2e267d62f86be2a508e362599b5a4b1" + integrity sha512-LUR08JKSviZiqrYTDfywvtnsnxr+tOvBU0BF8H+9frt7HMvc6Qn6F8Ubm72g5hDTHbq8qupKfDvDAln2TVPvFg== dependencies: - aproba "^2.0.0" - figgy-pudding "^3.5.1" - get-stream "^4.0.0" - lodash.clonedeep "^4.5.0" - normalize-package-data "^2.4.0" - npm-package-arg "^6.1.0" - npm-registry-fetch "^4.0.0" - semver "^5.5.1" - ssri "^6.0.1" + normalize-package-data "^4.0.0" + npm-package-arg "^9.0.1" + npm-registry-fetch "^13.0.0" + semver "^7.3.7" + ssri "^9.0.0" license-webpack-plugin@2.3.0: version "2.3.0" @@ -15997,6 +16394,16 @@ load-json-file@^4.0.0: pify "^3.0.0" strip-bom "^3.0.0" +load-json-file@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-6.2.0.tgz#5c7770b42cafa97074ca2848707c61662f4251a1" + integrity sha512-gUD/epcRms75Cw8RT1pUdHugZYM5ce64ucs2GEISABwkRsOQr0q2wm/MV2TKThycIe5e0ytRweW2RZxclogCdQ== + dependencies: + graceful-fs "^4.1.15" + parse-json "^5.0.0" + strip-bom "^4.0.0" + type-fest "^0.6.0" + loader-runner@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" @@ -16271,7 +16678,7 @@ lodash.sortby@^4.7.0: resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA== -lodash.template@^4.0.2, lodash.template@^4.5.0: +lodash.template@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== @@ -16301,7 +16708,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.2.1, 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.17.5, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -16416,10 +16823,10 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -lru-cache@^7.10.1, lru-cache@^7.5.1: - version "7.14.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.14.0.tgz#21be64954a4680e303a09e9468f880b98a0b3c7f" - integrity sha512-EIRtP1GrSJny0dqb50QXRUNBxHJhcpxHC++M5tD7RYbvLLn5KVWKsbyswSSqDuU15UFi3bgTQIY8nhDMeF6aDQ== +lru-cache@^7.10.1, lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: + version "7.14.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.14.1.tgz#8da8d2f5f59827edb388e63e459ac23d6d408fea" + integrity sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA== lru_map@^0.3.3: version "0.3.3" @@ -16436,11 +16843,6 @@ lz-string@^1.4.4: resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY= -macos-release@^2.2.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.4.1.tgz#64033d0ec6a5e6375155a74b1a1eba8e509820ac" - integrity sha512-H/QHeBIN1fIGJX517pvK8IEK53yQOW7YcEI55oYtgjDdoCQQz7eJS94qt5kNrscReEyuD/JcdFCm2XBEcGOITg== - madge@4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/madge/-/madge-4.0.2.tgz#56a3aff8021a5844f8713e0789f6ee94095f2f41" @@ -16497,13 +16899,6 @@ magic-string@^0.27.0: dependencies: "@jridgewell/sourcemap-codec" "^1.4.13" -make-dir@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" - integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== - dependencies: - pify "^3.0.0" - make-dir@^2.0.0, make-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" @@ -16524,22 +16919,27 @@ make-error@1.x, make-error@^1.1.1: resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== -make-fetch-happen@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-4.0.2.tgz#2d156b11696fb32bffbafe1ac1bc085dd6c78a79" - integrity sha512-YMJrAjHSb/BordlsDEcVcPyTbiJKkzqMf48N8dAJZT9Zjctrkb6Yg4TY9Sq2AwSIQJFn5qBBKVTYt3vP5FMIHA== +make-fetch-happen@^10.0.3, make-fetch-happen@^10.0.6: + version "10.2.1" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz#f5e3835c5e9817b617f2770870d9492d28678164" + integrity sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w== dependencies: - agentkeepalive "^3.4.1" - cacache "^11.3.3" - http-cache-semantics "^3.8.1" - http-proxy-agent "^2.1.0" - https-proxy-agent "^2.2.1" - lru-cache "^5.1.1" - mississippi "^3.0.0" - node-fetch-npm "^2.0.2" - promise-retry "^1.1.1" - socks-proxy-agent "^4.0.0" - ssri "^6.0.0" + agentkeepalive "^4.2.1" + cacache "^16.1.0" + http-cache-semantics "^4.1.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.0" + is-lambda "^1.0.1" + lru-cache "^7.7.1" + minipass "^3.1.6" + minipass-collect "^1.0.2" + minipass-fetch "^2.0.3" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^0.6.3" + promise-retry "^2.0.1" + socks-proxy-agent "^7.0.0" + ssri "^9.0.0" make-fetch-happen@^5.0.0: version "5.0.2" @@ -16565,13 +16965,6 @@ makeerror@1.0.x: dependencies: tmpl "1.0.x" -map-age-cleaner@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" - integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== - dependencies: - p-defer "^1.0.0" - map-cache@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" @@ -16582,11 +16975,6 @@ map-obj@^1.0.0, map-obj@^1.0.1: resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= -map-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-2.0.0.tgz#a65cd29087a92598b8791257a523e021222ac1f9" - integrity sha1-plzSkIepJZi4eRJXpSPgISIqwfk= - map-obj@^4.0.0: version "4.2.0" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.2.0.tgz#0e8bc823e2aaca8a0942567d12ed14f389eec153" @@ -16696,15 +17084,6 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= -mem@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178" - integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w== - dependencies: - map-age-cleaner "^0.1.1" - mimic-fn "^2.0.0" - p-is-promise "^2.0.0" - memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" @@ -16754,21 +17133,6 @@ meow@^3.3.0: redent "^1.0.0" trim-newlines "^1.0.0" -meow@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/meow/-/meow-4.0.1.tgz#d48598f6f4b1472f35bf6317a95945ace347f975" - integrity sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A== - dependencies: - camelcase-keys "^4.0.0" - decamelize-keys "^1.0.0" - loud-rejection "^1.0.0" - minimist "^1.1.3" - minimist-options "^3.0.1" - normalize-package-data "^2.3.4" - read-pkg-up "^3.0.0" - redent "^2.0.0" - trim-newlines "^2.0.0" - meow@^8.0.0: version "8.1.2" resolved "https://registry.yarnpkg.com/meow/-/meow-8.1.2.tgz#bcbe45bda0ee1729d350c03cffc8395a36c4e897" @@ -16892,7 +17256,7 @@ mimic-fn@^1.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== -mimic-fn@^2.0.0, mimic-fn@^2.1.0: +mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== @@ -16948,6 +17312,13 @@ minimatch@3.0.4: dependencies: brace-expansion "^1.1.7" +minimatch@3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.5.tgz#4da8f1290ee0f0f8e83d60ca69f8f134068604a3" + integrity sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw== + dependencies: + brace-expansion "^1.1.7" + minimatch@^5.0.1, minimatch@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.0.tgz#1717b464f4971b144f6aabe8f2d0b8e4511e09c7" @@ -16964,14 +17335,6 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist-options@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-3.0.2.tgz#fba4c8191339e13ecf4d61beb03f070103f3d954" - integrity sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ== - dependencies: - arrify "^1.0.1" - is-plain-obj "^1.1.0" - minimist@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" @@ -16994,6 +17357,17 @@ minipass-collect@^1.0.2: dependencies: minipass "^3.0.0" +minipass-fetch@^2.0.3: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-2.1.2.tgz#95560b50c472d81a3bc76f20ede80eaed76d8add" + integrity sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA== + dependencies: + minipass "^3.1.6" + minipass-sized "^1.0.3" + minizlib "^2.1.2" + optionalDependencies: + encoding "^0.1.13" + minipass-flush@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" @@ -17001,13 +17375,28 @@ minipass-flush@^1.0.5: dependencies: minipass "^3.0.0" -minipass-pipeline@^1.2.2: +minipass-json-stream@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz#7edbb92588fbfc2ff1db2fc10397acb7b6b44aa7" + integrity sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg== + dependencies: + jsonparse "^1.3.1" + minipass "^3.0.0" + +minipass-pipeline@^1.2.2, minipass-pipeline@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== dependencies: minipass "^3.0.0" +minipass-sized@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70" + integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== + dependencies: + minipass "^3.0.0" + minipass@^2.2.0, minipass@^2.3.5, minipass@^2.6.0, minipass@^2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" @@ -17016,10 +17405,10 @@ minipass@^2.2.0, minipass@^2.3.5, minipass@^2.6.0, minipass@^2.9.0: safe-buffer "^5.1.2" yallist "^3.0.0" -minipass@^3.0.0, minipass@^3.1.1: - version "3.1.6" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.6.tgz#3b8150aa688a711a1521af5e8779c1d3bb4f45ee" - integrity sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ== +minipass@^3.0.0, minipass@^3.1.1, minipass@^3.1.6: + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== dependencies: yallist "^4.0.0" @@ -17030,7 +17419,7 @@ minizlib@^1.3.3: dependencies: minipass "^2.9.0" -minizlib@^2.1.1: +minizlib@^2.1.1, minizlib@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== @@ -17080,6 +17469,15 @@ mkdirp-classic@^0.5.2: resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== +mkdirp-infer-owner@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz#55d3b368e7d89065c38f32fd38e638f0ab61d316" + integrity sha512-sdqtiFt3lkOaYvTXSRIUjkIdPTcxgv5+fgqYE/5qgwdw12cOrAuzzgzvVExIkH/ul1oeHN3bCLOWSG3XOqbKKw== + dependencies: + chownr "^2.0.0" + infer-owner "^1.0.4" + mkdirp "^1.0.3" + mkdirp@0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.4.tgz#fd01504a6797ec5c9be81ff43d204961ed64a512" @@ -17087,7 +17485,7 @@ mkdirp@0.5.4: dependencies: minimist "^1.2.5" -mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@^0.5.6, mkdirp@~0.5.0, mkdirp@~0.5.1: +mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@^0.5.6, mkdirp@~0.5.1: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== @@ -17273,15 +17671,16 @@ multicast-dns@^6.0.1: dns-packet "^1.3.1" thunky "^1.0.2" -multimatch@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-2.1.0.tgz#9c7906a22fb4c02919e2f5f75161b4cdbd4b2a2b" - integrity sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis= +multimatch@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-5.0.0.tgz#932b800963cea7a31a033328fa1e0c3a1874dbe6" + integrity sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA== dependencies: - array-differ "^1.0.0" - array-union "^1.0.1" - arrify "^1.0.0" - minimatch "^3.0.0" + "@types/minimatch" "^3.0.3" + array-differ "^3.0.0" + array-union "^2.1.0" + arrify "^2.0.1" + minimatch "^3.0.4" mustache@^4.2.0: version "4.2.0" @@ -17356,12 +17755,17 @@ native-url@0.3.4: dependencies: querystring "^0.2.0" +natural-compare-lite@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" + integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -negotiator@0.6.3: +negotiator@0.6.3, negotiator@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== @@ -17516,6 +17920,11 @@ nock@^13.0.4, nock@^13.0.5, nock@^13.1.0: lodash.set "^4.3.2" propagate "^2.0.0" +node-addon-api@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" + integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== + node-environment-flags@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.5.tgz#fa930275f5bf5dae188d6192b24b4c8bbac3d76a" @@ -17558,22 +17967,26 @@ node-forge@^0.10.0: resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== -node-gyp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-4.0.0.tgz#972654af4e5dd0cd2a19081b4b46fe0442ba6f45" - integrity sha512-2XiryJ8sICNo6ej8d0idXDEMKfVfFK7kekGCtJAuelGsYHQxhj13KTf95swTCN2dZ/4lTfZ84Fu31jqJEEgjWA== +node-gyp-build@^4.3.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.5.0.tgz#7a64eefa0b21112f89f58379da128ac177f20e40" + integrity sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg== + +node-gyp@^9.0.0: + version "9.3.0" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-9.3.0.tgz#f8eefe77f0ad8edb3b3b898409b53e697642b319" + integrity sha512-A6rJWfXFz7TQNjpldJ915WFb1LnhO4lIve3ANPbWreuEoLoKlFT3sxIepPBkLhM27crW8YmN+pjlgbasH6cH/Q== dependencies: - glob "^7.0.3" - graceful-fs "^4.1.2" - mkdirp "^0.5.0" - nopt "2 || 3" - npmlog "0 || 1 || 2 || 3 || 4" - osenv "0" - request "^2.87.0" - rimraf "2" - semver "~5.3.0" - tar "^4.4.8" - which "1" + env-paths "^2.2.0" + glob "^7.1.4" + graceful-fs "^4.2.6" + make-fetch-happen "^10.0.3" + nopt "^6.0.0" + npmlog "^6.0.0" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.2" + which "^2.0.2" node-html-parser@1.4.9: version "1.4.9" @@ -17678,13 +18091,27 @@ nodemon@^2.0.16: undefsafe "^2.0.5" update-notifier "^5.1.0" -"nopt@2 || 3", nopt@3.x, nopt@^3.0.6: +nopt@3.x, nopt@^3.0.6: version "3.0.6" resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k= dependencies: abbrev "1" +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + +nopt@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-6.0.0.tgz#245801d8ebf409c6df22ab9d95b65e1309cdb16d" + integrity sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g== + dependencies: + abbrev "^1.0.0" + nopt@~1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" @@ -17692,7 +18119,7 @@ nopt@~1.0.10: dependencies: abbrev "1" -normalize-package-data@^2.0.0, normalize-package-data@^2.3.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.3.5, normalize-package-data@^2.4.0, normalize-package-data@^2.5.0: +normalize-package-data@^2.0.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.4.0, normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== @@ -17712,6 +18139,16 @@ normalize-package-data@^3.0.0: semver "^7.3.4" validate-npm-package-license "^3.0.1" +normalize-package-data@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-4.0.1.tgz#b46b24e0616d06cadf9d5718b29b6d445a82a62c" + integrity sha512-EBk5QKKuocMJhB3BILuKhmaPjI8vNRSpIfO9woLC6NyHVkKKdVEdAO1mrT0ZfxNR1lKwCcTkuZfmGIFdizZ8Pg== + dependencies: + hosted-git-info "^5.0.0" + is-core-module "^2.8.1" + semver "^7.3.5" + validate-npm-package-license "^3.0.4" + normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" @@ -17748,7 +18185,7 @@ normalize-url@2.0.1: query-string "^5.0.1" sort-keys "^2.0.0" -normalize-url@^3.0.0, normalize-url@^3.3.0: +normalize-url@^3.0.0: version "3.3.0" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== @@ -17765,6 +18202,13 @@ npm-bundled@^1.0.1, npm-bundled@^1.1.1: dependencies: npm-normalize-package-bin "^1.0.1" +npm-bundled@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-2.0.1.tgz#94113f7eb342cd7a67de1e789f896b04d2c600f4" + integrity sha512-gZLxXdjEzE/+mOstGDqR6b0EkhJ+kM6fxM6vUuckuctuVPh80Q6pw/rSZj9s4Gex9GxWtIicO1pc8DB9KZWudw== + dependencies: + npm-normalize-package-bin "^2.0.0" + npm-install-checks@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/npm-install-checks/-/npm-install-checks-4.0.0.tgz#a37facc763a2fde0497ef2c6d0ac7c3fbe00d7b4" @@ -17772,25 +18216,23 @@ npm-install-checks@^4.0.0: dependencies: semver "^7.1.1" -npm-lifecycle@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/npm-lifecycle/-/npm-lifecycle-2.1.1.tgz#0027c09646f0fd346c5c93377bdaba59c6748fdf" - integrity sha512-+Vg6I60Z75V/09pdcH5iUo/99Q/vop35PaI99elvxk56azSVVsdsSsS/sXqKDNwbRRNN1qSxkcO45ZOu0yOWew== +npm-install-checks@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/npm-install-checks/-/npm-install-checks-5.0.0.tgz#5ff27d209a4e3542b8ac6b0c1db6063506248234" + integrity sha512-65lUsMI8ztHCxFz5ckCEC44DRvEGdZX5usQFriauxHEwt7upv1FKaQEmAtU0YnOAdwuNWCmk64xYiQABNrEyLA== dependencies: - byline "^5.0.0" - graceful-fs "^4.1.15" - node-gyp "^4.0.0" - resolve-from "^4.0.0" - slide "^1.1.6" - uid-number "0.0.6" - umask "^1.1.0" - which "^1.3.1" + semver "^7.1.1" npm-normalize-package-bin@^1.0.0, npm-normalize-package-bin@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== +npm-normalize-package-bin@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-2.0.0.tgz#9447a1adaaf89d8ad0abe24c6c84ad614a675fff" + integrity sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ== + npm-package-arg@8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-8.0.1.tgz#9d76f8d7667b2373ffda60bb801a27ef71e3e270" @@ -17800,7 +18242,16 @@ npm-package-arg@8.0.1: semver "^7.0.0" validate-npm-package-name "^3.0.0" -"npm-package-arg@^4.0.0 || ^5.0.0 || ^6.0.0", npm-package-arg@^6.0.0, npm-package-arg@^6.1.0: +npm-package-arg@8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-8.1.1.tgz#00ebf16ac395c63318e67ce66780a06db6df1b04" + integrity sha512-CsP95FhWQDwNqiYS+Q0mZ7FAEDytDZAkNxQqea6IaAFJTAY9Lhhqyl0irU/6PMc7BGfUmnsbHcqxJD7XuVM/rg== + dependencies: + hosted-git-info "^3.0.6" + semver "^7.0.0" + validate-npm-package-name "^3.0.0" + +npm-package-arg@^6.0.0, npm-package-arg@^6.1.0: version "6.1.1" resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-6.1.1.tgz#02168cb0a49a2b75bf988a28698de7b529df5cb7" integrity sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg== @@ -17819,7 +18270,7 @@ npm-package-arg@^8.0.0: semver "^7.3.4" validate-npm-package-name "^3.0.0" -npm-package-arg@^9.1.0: +npm-package-arg@^9.0.0, npm-package-arg@^9.0.1, npm-package-arg@^9.1.0: version "9.1.2" resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-9.1.2.tgz#fc8acecb00235f42270dda446f36926ddd9ac2bc" integrity sha512-pzd9rLEx4TfNJkovvlBSLGhq31gGu2QDexFPWT19yCDh0JgnRhlBLNo5759N0AJmBk+kQ9Y/hXoLnlgFD+ukmg== @@ -17829,7 +18280,7 @@ npm-package-arg@^9.1.0: semver "^7.3.5" validate-npm-package-name "^4.0.0" -npm-packlist@^1.1.12, npm-packlist@^1.4.1: +npm-packlist@^1.1.12: version "1.4.8" resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e" integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A== @@ -17848,6 +18299,16 @@ npm-packlist@^2.1.4: npm-bundled "^1.1.1" npm-normalize-package-bin "^1.0.1" +npm-packlist@^5.1.0, npm-packlist@^5.1.1: + version "5.1.3" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-5.1.3.tgz#69d253e6fd664b9058b85005905012e00e69274b" + integrity sha512-263/0NGrn32YFYi4J533qzrQ/krmmrWwhKkzwTuM4f/07ug51odoaNjUexxO4vxlzURHcmYMH1QjvHjsNDKLVg== + dependencies: + glob "^8.0.1" + ignore-walk "^5.0.1" + npm-bundled "^2.0.0" + npm-normalize-package-bin "^2.0.0" + npm-pick-manifest@6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-6.1.0.tgz#2befed87b0fce956790f62d32afb56d7539c022a" @@ -17866,17 +18327,28 @@ npm-pick-manifest@^3.0.0: npm-package-arg "^6.0.0" semver "^5.4.1" -npm-registry-fetch@^3.9.0: - version "3.9.1" - resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-3.9.1.tgz#00ff6e4e35d3f75a172b332440b53e93f4cb67de" - integrity sha512-VQCEZlydXw4AwLROAXWUR7QDfe2Y8Id/vpAgp6TI1/H78a4SiQ1kQrKZALm5/zxM5n4HIi+aYb+idUAV/RuY0Q== +npm-pick-manifest@^7.0.0: + version "7.0.2" + resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-7.0.2.tgz#1d372b4e7ea7c6712316c0e99388a73ed3496e84" + integrity sha512-gk37SyRmlIjvTfcYl6RzDbSmS9Y4TOBXfsPnoYqTHARNgWbyDiCSMLUpmALDj4jjcTZpURiEfsSHJj9k7EV4Rw== dependencies: - JSONStream "^1.3.4" - bluebird "^3.5.1" - figgy-pudding "^3.4.1" - lru-cache "^5.1.1" - make-fetch-happen "^4.0.2" - npm-package-arg "^6.1.0" + npm-install-checks "^5.0.0" + npm-normalize-package-bin "^2.0.0" + npm-package-arg "^9.0.0" + semver "^7.3.5" + +npm-registry-fetch@^13.0.0, npm-registry-fetch@^13.0.1, npm-registry-fetch@^13.3.0: + version "13.3.1" + resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-13.3.1.tgz#bb078b5fa6c52774116ae501ba1af2a33166af7e" + integrity sha512-eukJPi++DKRTjSBRcDZSDDsGqRK3ehbxfFUcgaRd0Yp6kRwOwh2WVn0r+8rMB4nnuzvAk6rQVzl6K5CkYOmnvw== + dependencies: + make-fetch-happen "^10.0.6" + minipass "^3.1.6" + minipass-fetch "^2.0.3" + minipass-json-stream "^1.0.1" + minizlib "^2.1.2" + npm-package-arg "^9.0.1" + proc-log "^2.0.0" npm-registry-fetch@^4.0.0: version "4.0.7" @@ -17927,7 +18399,7 @@ npm-run-path@^4.0.0, npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" -"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.1.2: +npmlog@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== @@ -17937,7 +18409,7 @@ npm-run-path@^4.0.0, npm-run-path@^4.0.1: gauge "~2.7.3" set-blocking "~2.0.0" -npmlog@^6.0.0, npmlog@^6.0.1: +npmlog@^6.0.0, npmlog@^6.0.1, npmlog@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" integrity sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg== @@ -17981,6 +18453,47 @@ nwsapi@^2.2.0: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== +nx@15.2.1, "nx@>=14.8.6 < 16": + version "15.2.1" + resolved "https://registry.yarnpkg.com/nx/-/nx-15.2.1.tgz#d51962c24180383d9af1880f57f29312c8311da7" + integrity sha512-vVeT5D02cDDiSmS3P2Bos/waYQa3m0yl/rouzsKpusVSmzAQGQbKXhxPb4WnNIj8Iz/8KjFeBM/RZO021BtGNg== + dependencies: + "@nrwl/cli" "15.2.1" + "@nrwl/tao" "15.2.1" + "@parcel/watcher" "2.0.4" + "@yarnpkg/lockfile" "^1.1.0" + "@yarnpkg/parsers" "^3.0.0-rc.18" + "@zkochan/js-yaml" "0.0.6" + axios "^1.0.0" + chalk "4.1.0" + chokidar "^3.5.1" + cli-cursor "3.1.0" + cli-spinners "2.6.1" + cliui "^7.0.2" + dotenv "~10.0.0" + enquirer "~2.3.6" + fast-glob "3.2.7" + figures "3.2.0" + flat "^5.0.2" + fs-extra "^10.1.0" + glob "7.1.4" + ignore "^5.0.4" + js-yaml "4.1.0" + jsonc-parser "3.2.0" + minimatch "3.0.5" + npm-run-path "^4.0.1" + open "^8.4.0" + semver "7.3.4" + string-width "^4.2.3" + strong-log-transformer "^2.1.0" + tar-stream "~2.2.0" + tmp "~0.2.1" + tsconfig-paths "^3.9.0" + tslib "^2.3.0" + v8-compile-cache "2.3.0" + yargs "^17.6.2" + yargs-parser "21.1.1" + oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" @@ -18106,11 +18619,6 @@ obuf@^1.0.0, obuf@^1.1.2: resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== -octokit-pagination-methods@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz#cf472edc9d551055f9ef73f6e42b4dbb4c80bea4" - integrity sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ== - on-finished@2.4.1, on-finished@^2.3.0: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" @@ -18167,6 +18675,15 @@ open@^7.4.2: is-docker "^2.0.0" is-wsl "^2.1.1" +open@^8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" + integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== + dependencies: + define-lazy-prop "^2.0.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" @@ -18244,7 +18761,7 @@ ora@^3.4.0: strip-ansi "^5.2.0" wcwidth "^1.0.1" -ora@^5.1.0, ora@^5.3.0, ora@^5.4.0: +ora@^5.1.0, ora@^5.3.0, ora@^5.4.0, ora@^5.4.1: version "5.4.1" resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== @@ -18276,29 +18793,12 @@ os-homedir@^1.0.0, os-homedir@^1.0.1: resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= -os-locale@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" - integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q== - dependencies: - execa "^1.0.0" - lcid "^2.0.0" - mem "^4.0.0" - -os-name@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/os-name/-/os-name-3.1.0.tgz#dec19d966296e1cd62d701a5a66ee1ddeae70801" - integrity sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg== - dependencies: - macos-release "^2.2.0" - windows-release "^3.1.0" - os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= -osenv@0, osenv@^0.1.3, osenv@^0.1.5: +osenv@^0.1.3, osenv@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== @@ -18316,11 +18816,6 @@ p-cancelable@^1.0.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== -p-defer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" - integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= - p-defer@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-3.0.0.tgz#d1dceb4ee9b2b604b1d94ffec83760175d4e6f83" @@ -18348,11 +18843,6 @@ p-is-promise@^1.1.0: resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e" integrity sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4= -p-is-promise@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e" - integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg== - p-limit@3.1.0, p-limit@^3.0.1, p-limit@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" @@ -18416,17 +18906,10 @@ p-locate@^6.0.0: dependencies: p-limit "^4.0.0" -p-map-series@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-map-series/-/p-map-series-1.0.0.tgz#bf98fe575705658a9e1351befb85ae4c1f07bdca" - integrity sha1-v5j+V1cFZYqeE1G++4WuTB8Hvco= - dependencies: - p-reduce "^1.0.0" - -p-map@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" - integrity sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA== +p-map-series@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-map-series/-/p-map-series-2.1.0.tgz#7560d4c452d9da0c07e692fdbfe6e2c81a2a91f2" + integrity sha512-RpYIIK1zXSNEOdwxcfe7FdvGcs7+y5n8rifMhMNWvaxRNMPINJHF5GDeuVxWqnfrcHPSCnp7Oo5yNXHId9Av2Q== p-map@^2.0.0: version "2.1.0" @@ -18440,15 +18923,23 @@ p-map@^4.0.0: dependencies: aggregate-error "^3.0.0" -p-pipe@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-1.2.0.tgz#4b1a11399a11520a67790ee5a0c1d5881d6befe9" - integrity sha1-SxoROZoRUgpneQ7loMHViB1r7+k= +p-pipe@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-3.1.0.tgz#48b57c922aa2e1af6a6404cb7c6bf0eb9cc8e60e" + integrity sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw== -p-reduce@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa" - integrity sha1-GMKw3ZNqRpClKfgjH1ig/bakffo= +p-queue@^6.6.2: + version "6.6.2" + resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" + integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== + dependencies: + eventemitter3 "^4.0.4" + p-timeout "^3.2.0" + +p-reduce@^2.0.0, p-reduce@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-2.1.0.tgz#09408da49507c6c274faa31f28df334bc712b64a" + integrity sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw== p-retry@^3.0.1: version "3.0.1" @@ -18464,7 +18955,7 @@ p-timeout@^2.0.1: dependencies: p-finally "^1.0.0" -p-timeout@^3.1.0: +p-timeout@^3.1.0, p-timeout@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== @@ -18481,12 +18972,12 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -p-waterfall@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-waterfall/-/p-waterfall-1.0.0.tgz#7ed94b3ceb3332782353af6aae11aa9fc235bb00" - integrity sha1-ftlLPOszMngjU69qrhGqn8I1uwA= +p-waterfall@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/p-waterfall/-/p-waterfall-2.1.1.tgz#63153a774f472ccdc4eb281cdb2967fcf158b2ee" + integrity sha512-RRTnDb2TBG/epPRI2yYXsimO0v3BXC8Yd3ogr1545IaqKK17VGhbWVeGGN+XfCm/08OK8635nH31c8bATkHuSw== dependencies: - p-reduce "^1.0.0" + p-reduce "^2.0.0" package-json@^6.3.0, package-json@^6.5.0: version "6.5.0" @@ -18508,7 +18999,7 @@ packet-reader@1.0.0: resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74" integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ== -pacote@9.5.12, pacote@^9.5.0: +pacote@9.5.12: version "9.5.12" resolved "https://registry.yarnpkg.com/pacote/-/pacote-9.5.12.tgz#1e11dd7a8d736bcc36b375a9804d41bb0377bf66" integrity sha512-BUIj/4kKbwWg4RtnBncXPJd15piFSVNpTzY0rysSr3VnMowTYgkGKcaHrbReepAkjTr8lH2CVWRi58Spg2CicQ== @@ -18544,6 +19035,33 @@ pacote@9.5.12, pacote@^9.5.0: unique-filename "^1.1.1" which "^1.3.1" +pacote@^13.0.3, pacote@^13.6.1: + version "13.6.2" + resolved "https://registry.yarnpkg.com/pacote/-/pacote-13.6.2.tgz#0d444ba3618ab3e5cd330b451c22967bbd0ca48a" + integrity sha512-Gu8fU3GsvOPkak2CkbojR7vjs3k3P9cA6uazKTHdsdV0gpCEQq2opelnEv30KRQWgVzP5Vd/5umjcedma3MKtg== + dependencies: + "@npmcli/git" "^3.0.0" + "@npmcli/installed-package-contents" "^1.0.7" + "@npmcli/promise-spawn" "^3.0.0" + "@npmcli/run-script" "^4.1.0" + cacache "^16.0.0" + chownr "^2.0.0" + fs-minipass "^2.1.0" + infer-owner "^1.0.4" + minipass "^3.1.6" + mkdirp "^1.0.4" + npm-package-arg "^9.0.0" + npm-packlist "^5.1.0" + npm-pick-manifest "^7.0.0" + npm-registry-fetch "^13.0.1" + proc-log "^2.0.0" + promise-retry "^2.0.1" + read-package-json "^5.0.0" + read-package-json-fast "^2.0.3" + rimraf "^3.0.2" + ssri "^9.0.0" + tar "^6.1.11" + pad@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/pad/-/pad-3.2.0.tgz#be7a1d1cb6757049b4ad5b70e71977158fea95d1" @@ -18596,10 +19114,14 @@ parse-asn1@^5.0.0, parse-asn1@^5.1.5: pbkdf2 "^3.0.3" safe-buffer "^5.1.1" -parse-github-repo-url@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz#9e7d8bb252a6cb6ba42595060b7bf6df3dbc1f50" - integrity sha1-nn2LslKmy2ukJZUGC3v23z28H1A= +parse-conflict-json@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/parse-conflict-json/-/parse-conflict-json-2.0.2.tgz#3d05bc8ffe07d39600dc6436c6aefe382033d323" + integrity sha512-jDbRGb00TAPFsKWCpZZOT93SxVP9nONOSgES3AevqRq/CHvavEBvKAjxX9p5Y5F0RZLxH9Ufd9+RwtCsa+lFDA== + dependencies: + json-parse-even-better-errors "^2.3.1" + just-diff "^5.0.1" + just-diff-apply "^5.2.0" parse-json@^2.2.0: version "2.2.0" @@ -18636,30 +19158,24 @@ parse-passwd@^1.0.0: resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= -parse-path@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-4.0.3.tgz#82d81ec3e071dcc4ab49aa9f2c9c0b8966bb22bf" - integrity sha512-9Cepbp2asKnWTJ9x2kpw6Fe8y9JDbqwahGCTvklzd/cEq5C5JC59x2Xb0Kx+x0QZ8bvNquGO8/BWP0cwBHzSAA== +parse-path@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-7.0.0.tgz#605a2d58d0a749c8594405d8cc3a2bf76d16099b" + integrity sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog== dependencies: - is-ssh "^1.3.0" - protocols "^1.4.0" - qs "^6.9.4" - query-string "^6.13.8" + protocols "^2.0.0" parse-static-imports@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/parse-static-imports/-/parse-static-imports-1.1.0.tgz#ae2f18f18da1a993080ae406a5219455c0bbad5d" integrity sha512-HlxrZcISCblEV0lzXmAHheH/8qEkKgmqkdxyHTPbSqsTUV8GzqmN1L+SSti+VbNPfbBO3bYLPHDiUs2avbAdbA== -parse-url@^5.0.0: - version "5.0.2" - resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-5.0.2.tgz#856a3be1fcdf78dc93fc8b3791f169072d898b59" - integrity sha512-Czj+GIit4cdWtxo3ISZCvLiUjErSo0iI3wJ+q9Oi3QuMYTI6OZu+7cewMWZ+C1YAnKhYTk6/TLuhIgCypLthPA== +parse-url@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-8.1.0.tgz#972e0827ed4b57fc85f0ea6b0d839f0d8a57a57d" + integrity sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w== dependencies: - is-ssh "^1.3.0" - normalize-url "^3.3.0" - parse-path "^4.0.0" - protocols "^1.4.0" + parse-path "^7.0.0" parse5-htmlparser2-tree-adapter@6.0.1: version "6.0.1" @@ -18968,6 +19484,11 @@ pify@^4.0.1: resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== +pify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f" + integrity sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA== + pinkie-promise@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" @@ -19032,17 +19553,17 @@ platform@1.3.6: resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.6.tgz#48b4ce983164b209c2d45a107adb31f473a6e7a7" integrity sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg== -playwright-core@1.28.1: - version "1.28.1" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.28.1.tgz#8400be9f4a8d1c0489abdb9e75a4cc0ffc3c00cb" - integrity sha512-3PixLnGPno0E8rSBJjtwqTwJe3Yw72QwBBBxNoukIj3lEeBNXwbNiKrNuB1oyQgTBw5QHUhNO3SteEtHaMK6ag== +playwright-core@1.29.2: + version "1.29.2" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.29.2.tgz#2e8347e7e8522409f22b244e600e703b64022406" + integrity sha512-94QXm4PMgFoHAhlCuoWyaBYKb92yOcGVHdQLoxQ7Wjlc7Flg4aC/jbFW7xMR52OfXMVkWicue4WXE7QEegbIRA== -playwright@^1.27.1: - version "1.28.1" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.28.1.tgz#f23247f1de466ff73d7230d94df96271e5da6583" - integrity sha512-92Sz6XBlfHlb9tK5UCDzIFAuIkHHpemA9zwUaqvo+w7sFMSmVMGmvKcbptof/eJObq63PGnMhM75x7qxhTR78Q== +playwright@^1.27.1, playwright@^1.29.2: + version "1.29.2" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.29.2.tgz#d6a0a3e8e44f023f7956ed19ffa8af915a042769" + integrity sha512-hKBYJUtdmYzcjdhYDkP9WGtORwwZBBKAW8+Lz7sr0ZMxtJr04ASXVzH5eBWtDkdb0c3LLFsehfPBTRfvlfKJOA== dependencies: - playwright-core "1.28.1" + playwright-core "1.29.2" plugin-error@^1.0.1: version "1.0.1" @@ -19608,7 +20129,7 @@ private@^0.1.6, private@^0.1.8: resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== -proc-log@^2.0.1: +proc-log@^2.0.0, proc-log@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-2.0.1.tgz#8f3f69a1f608de27878f91f5c688b225391cb685" integrity sha512-Kcmo2FhfDTXdcbfDH76N7uBYHINxc/8GW7UAVuVP9I+Va3uHSerrnKV6dLooga/gh7GlgzuCCr/eoldnL1muGw== @@ -19635,6 +20156,16 @@ progress@^2.0.0, progress@^2.0.1, progress@^2.0.3: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== +promise-all-reject-late@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz#f8ebf13483e5ca91ad809ccc2fcf25f26f8643c2" + integrity sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw== + +promise-call-limit@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-call-limit/-/promise-call-limit-1.0.1.tgz#4bdee03aeb85674385ca934da7114e9bcd3c6e24" + integrity sha512-3+hgaa19jzCGLuSCbieeRsu5C2joKfYn8pY6JAuXFRVfF4IO+L7UPpFWNTeWT9pM7uhskvbPPd/oEOktCn317Q== + promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" @@ -19660,6 +20191,14 @@ promise-retry@^1.1.1: err-code "^1.0.0" retry "^0.10.0" +promise-retry@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" + integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== + dependencies: + err-code "^2.0.2" + retry "^0.12.0" + promise.hash.helper@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/promise.hash.helper/-/promise.hash.helper-1.0.8.tgz#8c5fa0570f6f96821f52364fd72292b2c5a114f7" @@ -19743,10 +20282,10 @@ protobufjs@^6.10.2, protobufjs@^6.8.6: "@types/node" ">=13.7.0" long "^4.0.0" -protocols@^1.1.0, protocols@^1.4.0: - version "1.4.8" - resolved "https://registry.yarnpkg.com/protocols/-/protocols-1.4.8.tgz#48eea2d8f58d9644a4a32caae5d5db290a075ce8" - integrity sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg== +protocols@^2.0.0, protocols@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/protocols/-/protocols-2.0.1.tgz#8f155da3fc0f32644e83c5782c8e8212ccf70a86" + integrity sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q== protoduck@^5.0.1: version "5.0.1" @@ -19877,7 +20416,7 @@ qjobs@^1.2.0: resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071" integrity sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg== -qs@6.11.0, qs@^6.4.0, qs@^6.9.4: +qs@6.11.0, qs@^6.4.0: version "6.11.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== @@ -19906,16 +20445,6 @@ query-string@^5.0.1: object-assign "^4.1.0" strict-uri-encode "^1.0.0" -query-string@^6.13.8: - version "6.14.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.14.1.tgz#7ac2dca46da7f309449ba0f86b1fd28255b0c86a" - integrity sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw== - dependencies: - decode-uri-component "^0.2.0" - filter-obj "^1.1.0" - split-on-first "^1.0.0" - strict-uri-encode "^2.0.0" - querystring-es3@0.2.1, querystring-es3@^0.2.0, querystring-es3@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" @@ -19941,11 +20470,6 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -quick-lru@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8" - integrity sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g= - quick-lru@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" @@ -20154,14 +20678,20 @@ read-cache@^1.0.0: dependencies: pify "^2.3.0" -read-cmd-shim@^1.0.1: - version "1.0.5" - resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-1.0.5.tgz#87e43eba50098ba5a32d0ceb583ab8e43b961c16" - integrity sha512-v5yCqQ/7okKoZZkBQUAfTsQ3sVJtXdNfbPnI5cceppoxEVLYA3k+VtV2omkeo8MS94JCy4fSiUwlRBAwCVRPUA== +read-cmd-shim@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-3.0.1.tgz#868c235ec59d1de2db69e11aec885bc095aea087" + integrity sha512-kEmDUoYf/CDy8yZbLTmhB1X9kkjf9Q80PCNsDMb7ufrGd6zZSQA1+UyjrO+pZm5K/S4OXCWJeiIt1JA8kAsa6g== + +read-package-json-fast@^2.0.2, read-package-json-fast@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz#323ca529630da82cb34b36cc0b996693c98c2b83" + integrity sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ== dependencies: - graceful-fs "^4.1.2" + json-parse-even-better-errors "^2.3.0" + npm-normalize-package-bin "^1.0.1" -"read-package-json@1 || 2", read-package-json@^2.0.0, read-package-json@^2.0.13: +read-package-json@^2.0.0: version "2.1.2" resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-2.1.2.tgz#6992b2b66c7177259feb8eaac73c3acd28b9222a" integrity sha512-D1KmuLQr6ZSJS0tW8hf3WGpRlwszJOXZ3E8Yd/DNRaM5d+1wVRZdHlpGBLAuovjr28LbWvjpWkBHMxpRGGjzNA== @@ -20171,7 +20701,17 @@ read-cmd-shim@^1.0.1: normalize-package-data "^2.0.0" npm-normalize-package-bin "^1.0.0" -read-package-tree@5.3.1, read-package-tree@^5.1.6: +read-package-json@^5.0.0, read-package-json@^5.0.1: + version "5.0.2" + resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-5.0.2.tgz#b8779ccfd169f523b67208a89cc912e3f663f3fa" + integrity sha512-BSzugrt4kQ/Z0krro8zhTwV1Kd79ue25IhNN/VtHFy1mG/6Tluyi+msc0UpwaoQzxSHa28mntAjIZY6kEgfR9Q== + dependencies: + glob "^8.0.1" + json-parse-even-better-errors "^2.3.1" + normalize-package-data "^4.0.0" + npm-normalize-package-bin "^2.0.0" + +read-package-tree@5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/read-package-tree/-/read-package-tree-5.3.1.tgz#a32cb64c7f31eb8a6f31ef06f9cedf74068fe636" integrity sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw== @@ -20266,7 +20806,7 @@ read-pkg@^5.0.0, read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" -read@1, read@~1.0.1: +read@1, read@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" integrity sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ= @@ -20305,7 +20845,7 @@ readable-stream@~1.0.2: isarray "0.0.1" string_decoder "~0.10.x" -readdir-scoped-modules@^1.0.0: +readdir-scoped-modules@^1.0.0, readdir-scoped-modules@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309" integrity sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw== @@ -20380,14 +20920,6 @@ redent@^1.0.0: indent-string "^2.1.0" strip-indent "^1.0.1" -redent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-2.0.0.tgz#c1b2007b42d57eb1389079b3c8333639d5e1ccaa" - integrity sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo= - dependencies: - indent-string "^3.0.0" - strip-indent "^2.0.0" - redent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" @@ -20481,10 +21013,10 @@ regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.4.3: define-properties "^1.1.3" functions-have-names "^1.2.2" -regexpp@^3.0.0, regexpp@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" - integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== +regexpp@^3.0.0, regexpp@^3.1.0, regexpp@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== regexpu-core@^2.0.0: version "2.0.0" @@ -20623,7 +21155,7 @@ replace-in-file@^4.0.0: glob "^7.1.6" yargs "^15.0.2" -request@^2.87.0, request@^2.88.0, request@^2.88.2: +request@^2.88.0, request@^2.88.2: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -20664,11 +21196,6 @@ require-from-string@^2.0.2: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== -require-main-filename@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" - integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= - require-main-filename@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" @@ -20942,13 +21469,6 @@ rgba-regex@^1.0.0: resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= -rimraf@2, rimraf@^2.2.8, rimraf@^2.3.4, rimraf@^2.4.3, rimraf@^2.5.3, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.1, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -20956,6 +21476,13 @@ rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.1, rimraf@^3.0.2: dependencies: glob "^7.1.3" +rimraf@^2.2.8, rimraf@^2.3.4, rimraf@^2.4.3, rimraf@^2.5.3, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + rimraf@~2.5.2: version "2.5.4" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.5.4.tgz#96800093cbf1a0c86bd95b4625467535c29dfa04" @@ -21122,6 +21649,13 @@ rxjs@^6.4.0, rxjs@^6.5.0, rxjs@^6.6.0: dependencies: tslib "^1.9.0" +rxjs@^7.5.5: + version "7.5.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.7.tgz#2ec0d57fdc89ece220d2e702730ae8f1e49def39" + integrity sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA== + dependencies: + tslib "^2.1.0" + safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -21321,7 +21855,7 @@ semver-intersect@1.4.0: dependencies: semver "^5.0.0" -"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.0.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: +"semver@2 || 3 || 4 || 5", semver@^5.0.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -21350,11 +21884,6 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semve resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@~5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" - integrity sha1-myzl094C0XxgEq0yaqa00M9U+U8= - send@0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" @@ -21469,6 +21998,13 @@ shallow-clone@^0.1.2: lazy-cache "^0.2.3" mixin-object "^2.0.1" +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -21632,15 +22168,10 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" -slide@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" - integrity sha1-VusCfWW00tzmyy4tMsTUr8nh1wc= - -smart-buffer@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.1.0.tgz#91605c25d91652f4661ea69ccf45f1b331ca21ba" - integrity sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw== +smart-buffer@^4.1.0, smart-buffer@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== snake-case@^3.0.3: version "3.0.4" @@ -21734,6 +22265,23 @@ socks-proxy-agent@^4.0.0: agent-base "~4.2.1" socks "~2.3.2" +socks-proxy-agent@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz#dc069ecf34436621acb41e3efa66ca1b5fed15b6" + integrity sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww== + dependencies: + agent-base "^6.0.2" + debug "^4.3.3" + socks "^2.6.2" + +socks@^2.6.2: + version "2.7.1" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" + integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== + dependencies: + ip "^2.0.0" + smart-buffer "^4.2.0" + socks@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/socks/-/socks-2.3.3.tgz#01129f0a5d534d2b897712ed8aceab7ee65d78e3" @@ -21756,6 +22304,13 @@ sort-keys@^2.0.0: dependencies: is-plain-obj "^1.0.0" +sort-keys@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-4.2.0.tgz#6b7638cee42c506fff8c1cecde7376d21315be18" + integrity sha512-aUYIEU/UviqPgc8mHR6IW1EGxkAXpeRETYcrzg8cLAvUPZcpAlleSXHV2mY7G12GphSH6Gzv+4MMVSSkbdteHg== + dependencies: + is-plain-obj "^2.0.0" + sort-object-keys@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/sort-object-keys/-/sort-object-keys-1.1.3.tgz#bff833fe85cab147b34742e45863453c1e190b45" @@ -22012,11 +22567,6 @@ speed-measure-webpack-plugin@1.3.3: dependencies: chalk "^2.0.1" -split-on-first@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" - integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== - split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" @@ -22024,13 +22574,6 @@ split-string@^3.0.1, split-string@^3.0.2: dependencies: extend-shallow "^3.0.0" -split2@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/split2/-/split2-2.2.0.tgz#186b2575bcf83e85b7d18465756238ee4ee42493" - integrity sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw== - dependencies: - through2 "^2.0.2" - split2@^3.0.0: version "3.2.2" resolved "https://registry.yarnpkg.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" @@ -22106,6 +22649,13 @@ ssri@^8.0.0, ssri@^8.0.1: dependencies: minipass "^3.1.1" +ssri@^9.0.0, ssri@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-9.0.1.tgz#544d4c357a8d7b71a19700074b6883fcb4eae057" + integrity sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q== + dependencies: + minipass "^3.1.1" + stable@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" @@ -22258,11 +22808,6 @@ strict-uri-encode@^1.0.0: resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= -strict-uri-encode@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" - integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY= - string-hash@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b" @@ -22290,7 +22835,7 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: +"string-width@^1.0.2 || 2", string-width@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== @@ -22454,11 +22999,6 @@ strip-indent@^1.0.1: dependencies: get-stdin "^4.0.1" -strip-indent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68" - integrity sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g= - strip-indent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" @@ -22476,7 +23016,7 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -strong-log-transformer@^2.0.0: +strong-log-transformer@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz#0f5ed78d325e0421ac6f90f7f10e691d6ae3ae10" integrity sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA== @@ -22761,7 +23301,7 @@ tar-fs@^2.0.0: pump "^3.0.0" tar-stream "^2.1.4" -tar-stream@^2.1.4: +tar-stream@^2.1.4, tar-stream@~2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== @@ -22772,7 +23312,7 @@ tar-stream@^2.1.4: inherits "^2.0.3" readable-stream "^3.1.1" -tar@^4.4.10, tar@^4.4.8: +tar@^4.4.10: version "4.4.19" resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== @@ -22785,10 +23325,10 @@ tar@^4.4.10, tar@^4.4.8: safe-buffer "^5.2.1" yallist "^3.1.1" -tar@^6.0.2: - version "6.1.11" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" - integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== +tar@^6.0.2, tar@^6.1.0, tar@^6.1.11, tar@^6.1.2: + version "6.1.12" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.12.tgz#3b742fb05669b55671fb769ab67a7791ea1a62e6" + integrity sha512-jU4TdemS31uABHd+Lt5WEYJuzn+TJTCBLljvIAHZOz6M9Os5pJ4dD+vRFLxPa/n3T0iEFzpi+0x1UfuDZYbRMw== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" @@ -22831,18 +23371,6 @@ temp-fs@^0.9.9: dependencies: rimraf "~2.5.2" -temp-write@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/temp-write/-/temp-write-3.4.0.tgz#8cff630fb7e9da05f047c74ce4ce4d685457d492" - integrity sha1-jP9jD7fp2gXwR8dM5M5NaFRX1JI= - dependencies: - graceful-fs "^4.1.2" - is-stream "^1.1.0" - make-dir "^1.0.0" - pify "^3.0.0" - temp-dir "^1.0.0" - uuid "^3.0.1" - temp@0.9.4: version "0.9.4" resolved "https://registry.yarnpkg.com/temp/-/temp-0.9.4.tgz#cd20a8580cb63635d0e4e9d4bd989d44286e7620" @@ -23028,7 +23556,7 @@ through2@3.0.0: readable-stream "2 || 3" xtend "~4.0.1" -through2@^2.0.0, through2@^2.0.2: +through2@^2.0.0: version "2.0.5" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== @@ -23036,7 +23564,7 @@ through2@^2.0.0, through2@^2.0.2: readable-stream "~2.3.6" xtend "~4.0.1" -through2@^3.0.0, through2@^3.0.1: +through2@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/through2/-/through2-3.0.2.tgz#99f88931cfc761ec7678b41d5d7336b5b6a07bf4" integrity sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ== @@ -23129,7 +23657,7 @@ tmp@^0.1.0: dependencies: rimraf "^2.6.3" -tmp@^0.2.1: +tmp@^0.2.1, tmp@~0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== @@ -23285,26 +23813,21 @@ tree-sync@^2.0.0, tree-sync@^2.1.0: quick-temp "^0.1.5" walk-sync "^0.3.3" +treeverse@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/treeverse/-/treeverse-2.0.0.tgz#036dcef04bc3fd79a9b79a68d4da03e882d8a9ca" + integrity sha512-N5gJCkLu1aXccpOTtqV6ddSEi6ZmGkh3hjmbu1IjcavJK4qyOVQmi0myQKM7z5jVGmD68SJoliaVrMmVObhj6A== + trim-newlines@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= -trim-newlines@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-2.0.0.tgz#b403d0b91be50c331dfc4b82eeceb22c3de16d20" - integrity sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA= - trim-newlines@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.0.tgz#79726304a6a898aa8373427298d54c2ee8b1cb30" integrity sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA== -trim-off-newlines@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.3.tgz#8df24847fcb821b0ab27d58ab6efec9f2fe961a1" - integrity sha512-kh6Tu6GbeSNMGfrrZh6Bb/4ZEHV1QlB4xNDBeog8Y9/QwFlKTRyWvY3Fs9tRDAMZliVUwieMgEdIeL/FtqjkJg== - trim-right@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" @@ -23386,7 +23909,7 @@ tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3 resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== -tsutils@^3.0.0, tsutils@^3.17.1: +tsutils@^3.0.0, tsutils@^3.17.1, tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== @@ -23454,6 +23977,11 @@ type-fest@^0.21.3: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== +type-fest@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.4.1.tgz#8bdf77743385d8a4f13ba95f610f5ccd68c728f8" + integrity sha512-IwzA/LSfD2vC1/YDYMv/zHP4rDF1usCwllsDpbolT3D4fUepIO7f9K70jjmUewU/LmGUKJcwcVtDCpnKk4BPMw== + type-fest@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" @@ -23542,21 +24070,26 @@ typescript@4.0.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.2.tgz#7ea7c88777c723c681e33bf7988be5d008d05ac2" integrity sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ== +"typescript@^3 || ^4", typescript@^4.5.2: + version "4.9.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.3.tgz#3aea307c1746b8c384435d8ac36b8a2e580d85db" + integrity sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA== + typescript@^3.9.5, typescript@^3.9.7: version "3.9.9" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.9.tgz#e69905c54bc0681d0518bd4d587cc6f2d0b1a674" integrity sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w== -typescript@^4.5.2, typescript@~4.5.2: - version "4.5.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3" - integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA== - typescript@~4.0.2: version "4.0.8" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.8.tgz#5739105541db80a971fdbd0d56511d1a6f17d37f" integrity sha512-oz1765PN+imfz1MlZzSZPtC/tqcwsCyIYA8L47EkRnRW97ztRk83SzMiWLrnChC0vqoYxSU1fcFUDA5gV/ZiPg== +typescript@~4.5.2: + version "4.5.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3" + integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA== + ua-parser-js@^0.7.18, ua-parser-js@^0.7.30: version "0.7.31" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.31.tgz#649a656b191dffab4f21d5e053e27ca17cbff5c6" @@ -23572,16 +24105,6 @@ uglify-js@^3.1.4: resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.13.3.tgz#ce72a1ad154348ea2af61f50933c76cc8802276e" integrity sha512-otIc7O9LyxpUcQoXzj2hL4LPWKklO6LJWoJUzNa8A17Xgi4fOeDC8FBDOLHnC/Slo1CQgsZMcM6as0M76BZaig== -uid-number@0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" - integrity sha1-DqEOgDXo61uOREnwbaHHMGY7qoE= - -umask@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d" - integrity sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0= - unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" @@ -23668,6 +24191,13 @@ unique-filename@^1.1.1: dependencies: unique-slug "^2.0.0" +unique-filename@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-2.0.1.tgz#e785f8675a9a7589e0ac77e0b5c34d2eaeac6da2" + integrity sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A== + dependencies: + unique-slug "^3.0.0" + unique-slug@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" @@ -23675,6 +24205,13 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" +unique-slug@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-3.0.0.tgz#6d347cf57c8a7a7a6044aabd0e2d74e4d76dc7c9" + integrity sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w== + dependencies: + imurmurhash "^0.1.4" + unique-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" @@ -23691,13 +24228,6 @@ universal-analytics@0.4.23: request "^2.88.2" uuid "^3.0.0" -universal-user-agent@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-4.0.1.tgz#fd8d6cb773a679a709e967ef8288a31fcc03e557" - integrity sha512-LnST3ebHwVL2aNe4mejI9IQh2HfZ1RLo8Io2HugSif8ekzD1TlWpHpColOB/eh8JHMLkGH3Akqf040I+4ylNxg== - dependencies: - os-name "^3.1.0" - universal-user-agent@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" @@ -23951,7 +24481,7 @@ uuid@8.3.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea" integrity sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ== -uuid@^3.0.0, uuid@^3.0.1, uuid@^3.3.2, uuid@^3.4.0: +uuid@^3.0.0, uuid@^3.3.2, uuid@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== @@ -23966,7 +24496,7 @@ v8-compile-cache-lib@^3.0.1: resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== -v8-compile-cache@^2.0.3, v8-compile-cache@^2.3.0: +v8-compile-cache@2.3.0, v8-compile-cache@^2.0.3, v8-compile-cache@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== @@ -23980,7 +24510,7 @@ v8-to-istanbul@^8.1.0: convert-source-map "^1.6.0" source-map "^0.7.3" -validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.3: +validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== @@ -24169,6 +24699,11 @@ walk-sync@^3.0.0: matcher-collection "^2.0.1" minimatch "^3.0.4" +walk-up-path@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/walk-up-path/-/walk-up-path-1.0.0.tgz#d4745e893dd5fd0dbb58dd0a4c6a33d9c9fec53e" + integrity sha512-hwj/qMDUEjCU5h0xr90KGCf0tg0/LgJbmOWgrWKYlcJZM7XvquvUJZ0G/HMGr7F7OQMOUuPHWP9JpriinkAlkg== + walkdir@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/walkdir/-/walkdir-0.4.1.tgz#dc119f83f4421df52e3061e514228a2db20afa39" @@ -24601,7 +25136,7 @@ which-typed-array@^1.1.2: has-symbols "^1.0.1" is-typed-array "^1.1.3" -which@1, which@1.3.1, which@^1.1.1, which@^1.2.1, which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1.3.1: +which@1.3.1, which@^1.1.1, which@^1.2.1, which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -24636,13 +25171,6 @@ widest-line@^3.1.0: dependencies: string-width "^4.0.0" -windows-release@^3.1.0: - version "3.3.3" - resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.3.3.tgz#1c10027c7225743eec6b89df160d64c2e0293999" - integrity sha512-OSOGH1QYiW5yVor9TtmXKQvt2vjQqbYS+DqmsZw+r7xDwLXEeT3JGW0ZppFmHx4diyXmxt238KFR3N9jzevBRg== - dependencies: - execa "^1.0.0" - word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" @@ -24691,14 +25219,6 @@ workerpool@^6.1.5, workerpool@^6.2.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== -wrap-ansi@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" - integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - wrap-ansi@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" @@ -24740,7 +25260,7 @@ write-file-atomic@2.4.1: imurmurhash "^0.1.4" signal-exit "^3.0.2" -write-file-atomic@^2.0.0, write-file-atomic@^2.3.0: +write-file-atomic@^2.4.2: version "2.4.3" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481" integrity sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ== @@ -24759,25 +25279,46 @@ write-file-atomic@^3.0.0: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -write-json-file@^2.2.0, write-json-file@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-2.3.0.tgz#2b64c8a33004d54b8698c76d585a77ceb61da32f" - integrity sha1-K2TIozAE1UuGmMdtWFp3zrYdoy8= +write-file-atomic@^4.0.0, write-file-atomic@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +write-json-file@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-3.2.0.tgz#65bbdc9ecd8a1458e15952770ccbadfcff5fe62a" + integrity sha512-3xZqT7Byc2uORAatYiP3DHUUAVEkNOswEWNs9H5KXiicRTvzYzYqKjYc4G7p+8pltvAw641lVByKVtMpf+4sYQ== dependencies: detect-indent "^5.0.0" - graceful-fs "^4.1.2" - make-dir "^1.0.0" - pify "^3.0.0" + graceful-fs "^4.1.15" + make-dir "^2.1.0" + pify "^4.0.1" sort-keys "^2.0.0" - write-file-atomic "^2.0.0" + write-file-atomic "^2.4.2" -write-pkg@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/write-pkg/-/write-pkg-3.2.0.tgz#0e178fe97820d389a8928bc79535dbe68c2cff21" - integrity sha512-tX2ifZ0YqEFOF1wjRW2Pk93NLsj02+n1UP5RvO6rCs0K6R2g1padvf006cY74PQJKMGS2r42NK7FD0dG6Y6paw== +write-json-file@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-4.3.0.tgz#908493d6fd23225344af324016e4ca8f702dd12d" + integrity sha512-PxiShnxf0IlnQuMYOPPhPkhExoCQuTUNPOa/2JWCYTmBquU9njyyDuwRKN26IZBlp4yn1nt+Agh2HOOBl+55HQ== + dependencies: + detect-indent "^6.0.0" + graceful-fs "^4.1.15" + is-plain-obj "^2.0.0" + make-dir "^3.0.0" + sort-keys "^4.0.0" + write-file-atomic "^3.0.0" + +write-pkg@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/write-pkg/-/write-pkg-4.0.0.tgz#675cc04ef6c11faacbbc7771b24c0abbf2a20039" + integrity sha512-v2UQ+50TNf2rNHJ8NyWttfm/EJUBWMJcx6ZTYZr6Qp52uuegWw/lBkCtCbnYZEmPRNL61m+u67dAmGxo+HTULA== dependencies: sort-keys "^2.0.0" - write-json-file "^2.2.0" + type-fest "^0.4.1" + write-json-file "^3.2.0" ws@^6.2.1: version "6.2.2" @@ -24854,7 +25395,7 @@ xxhashjs@^0.2.1: dependencies: cuint "^0.2.2" -"y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0: +y18n@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4" integrity sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ== @@ -24887,7 +25428,7 @@ yaml@2.1.1: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.1.1.tgz#1e06fb4ca46e60d9da07e4f786ea370ed3c3cfec" integrity sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw== -yaml@^1.10.2: +yaml@^1.10.0, yaml@^1.10.2: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== @@ -24900,18 +25441,20 @@ yargs-parser@13.1.2, yargs-parser@^13.1.2: camelcase "^5.0.0" decamelize "^1.2.0" +yargs-parser@20.2.4: + version "20.2.4" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" + integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== + yargs-parser@20.x, yargs-parser@^20.2.2, yargs-parser@^20.2.3: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs-parser@^11.1.1: - version "11.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4" - integrity sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" +yargs-parser@21.1.1, yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== yargs-parser@^18.1.2: version "18.1.3" @@ -24921,11 +25464,6 @@ yargs-parser@^18.1.2: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^21.0.0: - version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - yargs-unparser@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f" @@ -24951,24 +25489,6 @@ yargs@13.3.2, yargs@^13.3.0, yargs@^13.3.2: y18n "^4.0.0" yargs-parser "^13.1.2" -yargs@^12.0.1: - version "12.0.5" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13" - integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw== - dependencies: - cliui "^4.0.0" - decamelize "^1.2.0" - find-up "^3.0.0" - get-caller-file "^1.0.1" - os-locale "^3.0.0" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^2.0.0" - which-module "^2.0.0" - y18n "^3.2.1 || ^4.0.0" - yargs-parser "^11.1.1" - yargs@^15.0.2: version "15.4.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" @@ -24999,10 +25519,10 @@ yargs@^16.1.1, yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" -yargs@^17.5.1, yargs@^17.6.0: - version "17.6.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.6.0.tgz#e134900fc1f218bc230192bdec06a0a5f973e46c" - integrity sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g== +yargs@^17.5.1, yargs@^17.6.0, yargs@^17.6.2: + version "17.6.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.6.2.tgz#2e23f2944e976339a1ee00f18c77fedee8332541" + integrity sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw== dependencies: cliui "^8.0.1" escalade "^3.1.1" @@ -25010,7 +25530,7 @@ yargs@^17.5.1, yargs@^17.6.0: require-directory "^2.1.1" string-width "^4.2.3" y18n "^5.0.5" - yargs-parser "^21.0.0" + yargs-parser "^21.1.1" yauzl@^2.10.0: version "2.10.0"