diff --git a/dev-packages/e2e-tests/verdaccio-config/config.yaml b/dev-packages/e2e-tests/verdaccio-config/config.yaml index ee65c5218d4a..cbb73201eebf 100644 --- a/dev-packages/e2e-tests/verdaccio-config/config.yaml +++ b/dev-packages/e2e-tests/verdaccio-config/config.yaml @@ -122,6 +122,12 @@ packages: unpublish: $all # proxy: npmjs # Don't proxy for E2E tests! + '@sentry/react-router': + access: $all + publish: $all + unpublish: $all + # proxy: npmjs # Don't proxy for E2E tests! + '@sentry/remix': access: $all publish: $all diff --git a/package.json b/package.json index 4eaa5eac73ca..a57893dde86c 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "packages/opentelemetry", "packages/profiling-node", "packages/react", + "packages/react-router", "packages/remix", "packages/replay-internal", "packages/replay-canvas", diff --git a/packages/react-router/.eslintrc.js b/packages/react-router/.eslintrc.js new file mode 100644 index 000000000000..a22f9710cf6b --- /dev/null +++ b/packages/react-router/.eslintrc.js @@ -0,0 +1,15 @@ +module.exports = { + env: { + browser: true, + node: true, + }, + overrides: [ + { + files: ['vite.config.ts'], + parserOptions: { + project: ['tsconfig.test.json'], + }, + }, + ], + extends: ['../../.eslintrc.js'], +}; diff --git a/packages/react-router/LICENSE b/packages/react-router/LICENSE new file mode 100644 index 000000000000..5251db3eaaca --- /dev/null +++ b/packages/react-router/LICENSE @@ -0,0 +1,16 @@ +MIT License + +Copyright (c) 2025 Functional Software, Inc. dba Sentry + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/react-router/README.md b/packages/react-router/README.md new file mode 100644 index 000000000000..368f3ee4b717 --- /dev/null +++ b/packages/react-router/README.md @@ -0,0 +1,146 @@ +

+ + Sentry + +

+ +# Official Sentry SDK for React Router (Framework) (EXPERIMENTAL) + +[![npm version](https://img.shields.io/npm/v/@sentry/react-router.svg)](https://www.npmjs.com/package/@sentry/react-router) +[![npm dm](https://img.shields.io/npm/dm/@sentry/react-router.svg)](https://www.npmjs.com/package/@sentry/react-router) +[![npm dt](https://img.shields.io/npm/dt/@sentry/react-router.svg)](https://www.npmjs.com/package/@sentry/react-router) + +> [!WARNING] +> This SDK is considered ⚠️ **experimental and in an alpha state**. It may experience breaking changes. Please reach out +> on [GitHub](https://github.com/getsentry/sentry-javascript/issues/) if you have any feedback or concerns. This +> SDK is for [React Router (framework)](https://reactrouter.com/start/framework/installation). If you're using [React Router (library)](https://reactrouter.com/start/library/installation) see our +> [React SDK here](https://docs.sentry.io/platforms/javascript/guides/react/features/react-router/v7/). + +## Links + +- [Official SDK Docs](https://docs.sentry.io/platforms/javascript/guides/react-router/) + +## General + +This package is a wrapper around `@sentry/node` for the server and `@sentry/browser` for the client side. + +## Manual Setup + +### Expose Hooks + +React Router exposes two hooks in your `app` folder (`entry.client.tsx` and `entry.server.tsx`). +If you do not see these two files, expose them with the following command: + +```bash +npx react-router reveal +``` + +### Client-Side Setup + +Initialize the SDK in your `entry.client.tsx` file: + +```tsx +import * as Sentry from '@sentry/react-router'; +import { startTransition, StrictMode } from 'react'; +import { hydrateRoot } from 'react-dom/client'; +import { HydratedRouter } from 'react-router/dom'; + +Sentry.init({ + dsn: '___PUBLIC_DSN___', + integrations: [Sentry.browserTracingIntegration()], + + tracesSampleRate: 1.0, // Capture 100% of the transactions + + // Set `tracePropagationTargets` to declare which URL(s) should have trace propagation enabled + tracePropagationTargets: [/^\//, /^https:\/\/yourserver\.io\/api/], +}); + +startTransition(() => { + hydrateRoot( + document, + + + , + ); +}); +``` + +Now, update your `app/root.tsx` file to report any unhandled errors from your global error boundary: + +```tsx +import * as Sentry from '@sentry/react-router'; + +export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { + let message = 'Oops!'; + let details = 'An unexpected error occurred.'; + let stack: string | undefined; + + if (isRouteErrorResponse(error)) { + message = error.status === 404 ? '404' : 'Error'; + details = error.status === 404 ? 'The requested page could not be found.' : error.statusText || details; + } else if (error && error instanceof Error) { + // you only want to capture non 404-errors that reach the boundary + Sentry.captureException(error); + if (import.meta.env.DEV) { + details = error.message; + stack = error.stack; + } + } + + return ( +
+

{message}

+

{details}

+ {stack && ( +
+          {stack}
+        
+ )} +
+ ); +} +// ... +``` + +### Server-Side Setup + +Create an `instrument.server.mjs` file in the root of your app: + +```js +import * as Sentry from '@sentry/node'; + +Sentry.init({ + dsn: '___PUBLIC_DSN___', + tracesSampleRate: 1.0, // Capture 100% of the transactions +}); +``` + +In your `entry.server.tsx` file, export the `handleError` function: + +```tsx +import * as Sentry from '@sentry/node'; +import { type HandleErrorFunction } from 'react-router'; + +export const handleError: HandleErrorFunction = (error, { request }) => { + // React Router may abort some interrupted requests, report those + if (!request.signal.aborted) { + Sentry.captureException(error); + + // make sure to still log the error so you can see it + console.error(error); + } +}; +// ... rest of your server entry +``` + +### Update Scripts + +Since React Router is running in ESM mode, you need to use the `--import` command line options to load our server-side instrumentation module before the application starts. +Update the `start` and `dev` script to include the instrumentation file: + +```json +"scripts": { + "dev": "NODE_OPTIONS='--import ./instrument.server.mjs' react-router dev", + "start": "NODE_OPTIONS='--import ./instrument.server.mjs' react-router-serve ./build/server/index.js", +} +``` diff --git a/packages/react-router/package.json b/packages/react-router/package.json new file mode 100644 index 000000000000..8db3107ecffb --- /dev/null +++ b/packages/react-router/package.json @@ -0,0 +1,73 @@ +{ + "name": "@sentry/react-router", + "version": "9.0.0-alpha.2", + "description": "Official Sentry SDK for React Router (Framework)", + "repository": "git://github.com/getsentry/sentry-javascript.git", + "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/react-router", + "author": "Sentry", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "files": [ + "/build" + ], + "main": "build/cjs/index.server.js", + "module": "build/esm/index.server.js", + "browser": "build/esm/index.client.js", + "types": "build/types/index.types.d.ts", + "exports": { + "./package.json": "./package.json", + ".": { + "types": "./build/types/index.types.d.ts", + "browser": { + "import": "./build/esm/index.client.js", + "require": "./build/cjs/index.client.js" + }, + "node": { + "import": "./build/esm/index.server.js", + "require": "./build/cjs/index.server.js" + } + } + }, + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@sentry/core": "9.0.0-alpha.2", + "@sentry/browser": "9.0.0-alpha.2", + "@sentry/node": "9.0.0-alpha.2" + }, + "devDependencies": { + "@react-router/node": "^7.1.5", + "react-router": "^7.1.5" + }, + "peerDependencies": { + "@react-router/node": "7.x", + "react-router": "7.x", + "react": ">=18" + }, + "scripts": { + "build": "run-p build:transpile build:types", + "build:dev": "yarn build", + "build:transpile": "rollup -c rollup.npm.config.mjs", + "build:types": "run-s build:types:core", + "build:types:core": "tsc -p tsconfig.types.json", + "build:watch": "run-p build:transpile:watch build:types:watch", + "build:dev:watch": "yarn build:watch", + "build:transpile:watch": "rollup -c rollup.npm.config.mjs --watch", + "build:types:watch": "tsc -p tsconfig.types.json --watch", + "build:tarball": "npm pack", + "circularDepCheck": "madge --circular src/index.client.ts && madge --circular src/index.server.ts && madge --circular src/index.types.ts", + "clean": "rimraf build coverage sentry-react-router-*.tgz", + "fix": "eslint . --format stylish --fix", + "lint": "eslint . --format stylish", + "test": "yarn test:unit", + "test:unit": "vitest run", + "test:watch": "vitest --watch", + "yalc:publish": "yalc publish --push --sig" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/packages/react-router/rollup.npm.config.mjs b/packages/react-router/rollup.npm.config.mjs new file mode 100644 index 000000000000..65e6b0a0dd9a --- /dev/null +++ b/packages/react-router/rollup.npm.config.mjs @@ -0,0 +1,22 @@ +import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils'; + +export default [ + ...makeNPMConfigVariants( + makeBaseNPMConfig({ + entrypoints: ['src/index.server.ts', 'src/index.client.ts'], + packageSpecificConfig: { + external: ['react-router', 'react-router-dom', 'react', 'react/jsx-runtime'], + output: { + // make it so Rollup calms down about the fact that we're combining default and named exports + exports: 'named', + }, + }, + sucrase: { + // React 19 emits a warning if we don't use the newer jsx transform: https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html + // but this breaks react 17, so we keep it at `classic` for now + jsxRuntime: 'classic', + production: true, // This is needed so that sucrase uses the production jsx runtime (ie `import { jsx } from 'react/jsx-runtime'` instead of `import { jsxDEV as _jsxDEV } from 'react/jsx-dev-runtime'`) + }, + }), + ), +]; diff --git a/packages/react-router/src/client/index.ts b/packages/react-router/src/client/index.ts new file mode 100644 index 000000000000..8e25b84c4a0c --- /dev/null +++ b/packages/react-router/src/client/index.ts @@ -0,0 +1,3 @@ +export * from '@sentry/browser'; + +export { init } from './sdk'; diff --git a/packages/react-router/src/client/sdk.ts b/packages/react-router/src/client/sdk.ts new file mode 100644 index 000000000000..688a8ba460f1 --- /dev/null +++ b/packages/react-router/src/client/sdk.ts @@ -0,0 +1,21 @@ +import type { BrowserOptions } from '@sentry/browser'; +import { init as browserInit } from '@sentry/browser'; +import type { Client } from '@sentry/core'; +import { applySdkMetadata, setTag } from '@sentry/core'; + +/** + * Initializes the client side of the React Router SDK. + */ +export function init(options: BrowserOptions): Client | undefined { + const opts = { + ...options, + }; + + applySdkMetadata(opts, 'react-router', ['react-router', 'browser']); + + const client = browserInit(opts); + + setTag('runtime', 'browser'); + + return client; +} diff --git a/packages/react-router/src/common/debug-build.ts b/packages/react-router/src/common/debug-build.ts new file mode 100644 index 000000000000..60aa50940582 --- /dev/null +++ b/packages/react-router/src/common/debug-build.ts @@ -0,0 +1,8 @@ +declare const __DEBUG_BUILD__: boolean; + +/** + * This serves as a build time flag that will be true by default, but false in non-debug builds or if users replace `__SENTRY_DEBUG__` in their generated code. + * + * ATTENTION: This constant must never cross package boundaries (i.e. be exported) to guarantee that it can be used for tree shaking. + */ +export const DEBUG_BUILD = __DEBUG_BUILD__; diff --git a/packages/react-router/src/index.client.ts b/packages/react-router/src/index.client.ts new file mode 100644 index 000000000000..4f1cce44fa36 --- /dev/null +++ b/packages/react-router/src/index.client.ts @@ -0,0 +1 @@ +export * from './client'; diff --git a/packages/react-router/src/index.server.ts b/packages/react-router/src/index.server.ts new file mode 100644 index 000000000000..0ce5251aa327 --- /dev/null +++ b/packages/react-router/src/index.server.ts @@ -0,0 +1 @@ +export * from './server'; diff --git a/packages/react-router/src/index.types.ts b/packages/react-router/src/index.types.ts new file mode 100644 index 000000000000..8d246451eb7d --- /dev/null +++ b/packages/react-router/src/index.types.ts @@ -0,0 +1,16 @@ +// re-define colliding type exports below + +export * from './client'; +export * from './server'; + +import type { Integration, Options, StackParser } from '@sentry/core'; +import type * as clientSdk from './client'; +import type * as serverSdk from './server'; + +/** Initializes Sentry React Router SDK */ +export declare function init(options: Options | clientSdk.BrowserOptions | serverSdk.NodeOptions): void; + +export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration; +export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration; +export declare const defaultStackParser: StackParser; +export declare const getDefaultIntegrations: (options: Options) => Integration[]; diff --git a/packages/react-router/src/server/index.ts b/packages/react-router/src/server/index.ts new file mode 100644 index 000000000000..6ac8d97b4241 --- /dev/null +++ b/packages/react-router/src/server/index.ts @@ -0,0 +1,3 @@ +export * from '@sentry/node'; + +export { init } from './sdk'; diff --git a/packages/react-router/src/server/sdk.ts b/packages/react-router/src/server/sdk.ts new file mode 100644 index 000000000000..bae99dee4983 --- /dev/null +++ b/packages/react-router/src/server/sdk.ts @@ -0,0 +1,20 @@ +import { applySdkMetadata, setTag } from '@sentry/core'; +import type { NodeClient, NodeOptions } from '@sentry/node'; +import { init as initNodeSdk } from '@sentry/node'; + +/** + * Initializes the server side of the React Router SDK + */ +export function init(options: NodeOptions): NodeClient | undefined { + const opts = { + ...options, + }; + + applySdkMetadata(opts, 'react-router', ['react-router', 'node']); + + const client = initNodeSdk(opts); + + setTag('runtime', 'node'); + + return client; +} diff --git a/packages/react-router/test/client/sdk.test.ts b/packages/react-router/test/client/sdk.test.ts new file mode 100644 index 000000000000..e63fe0cf51cf --- /dev/null +++ b/packages/react-router/test/client/sdk.test.ts @@ -0,0 +1,47 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import * as SentryBrowser from '@sentry/browser'; +import { SDK_VERSION, getCurrentScope, getGlobalScope, getIsolationScope } from '@sentry/browser'; + +import { init as reactRouterInit } from '../../src/client'; + +const browserInit = vi.spyOn(SentryBrowser, 'init'); + +describe('React Router client SDK', () => { + describe('init', () => { + afterEach(() => { + vi.clearAllMocks(); + + getGlobalScope().clear(); + getIsolationScope().clear(); + getCurrentScope().clear(); + getCurrentScope().setClient(undefined); + }); + + it('adds React Router metadata to the SDK options', () => { + expect(browserInit).not.toHaveBeenCalled(); + + reactRouterInit({}); + + const expectedMetadata = { + _metadata: { + sdk: { + name: 'sentry.javascript.react-router', + version: SDK_VERSION, + packages: [ + { name: 'npm:@sentry/react-router', version: SDK_VERSION }, + { name: 'npm:@sentry/browser', version: SDK_VERSION }, + ], + }, + }, + }; + + expect(browserInit).toHaveBeenCalledTimes(1); + expect(browserInit).toHaveBeenCalledWith(expect.objectContaining(expectedMetadata)); + }); + + it('returns client from init', () => { + expect(reactRouterInit({})).not.toBeUndefined(); + }); + }); +}); diff --git a/packages/react-router/test/server/sdk.test.ts b/packages/react-router/test/server/sdk.test.ts new file mode 100644 index 000000000000..06ff871d07f9 --- /dev/null +++ b/packages/react-router/test/server/sdk.test.ts @@ -0,0 +1,48 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import * as SentryNode from '@sentry/node'; + +import { SDK_VERSION } from '@sentry/node'; + +import { init as reactRouterInit } from '../../src/server/sdk'; + +const nodeInit = vi.spyOn(SentryNode, 'init'); + +describe('React Router server SDK', () => { + describe('init', () => { + afterEach(() => { + vi.clearAllMocks(); + + SentryNode.getGlobalScope().clear(); + SentryNode.getIsolationScope().clear(); + SentryNode.getCurrentScope().clear(); + SentryNode.getCurrentScope().setClient(undefined); + }); + + it('adds React Router metadata to the SDK options', () => { + expect(nodeInit).not.toHaveBeenCalled(); + + reactRouterInit({}); + + expect(nodeInit).toHaveBeenCalledTimes(1); + expect(nodeInit).toHaveBeenCalledWith( + expect.objectContaining({ + _metadata: { + sdk: { + name: 'sentry.javascript.react-router', + version: SDK_VERSION, + packages: [ + { name: 'npm:@sentry/react-router', version: SDK_VERSION }, + { name: 'npm:@sentry/node', version: SDK_VERSION }, + ], + }, + }, + }), + ); + }); + + it('returns client from init', () => { + expect(reactRouterInit({})).not.toBeUndefined(); + }); + }); +}); diff --git a/packages/react-router/test/tsconfig.json b/packages/react-router/test/tsconfig.json new file mode 100644 index 000000000000..120c3aff3716 --- /dev/null +++ b/packages/react-router/test/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../tsconfig.test.json", +} diff --git a/packages/react-router/tsconfig.json b/packages/react-router/tsconfig.json new file mode 100644 index 000000000000..b0eb9ecb6476 --- /dev/null +++ b/packages/react-router/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + + "include": ["src/**/*"], + + "compilerOptions": {} +} diff --git a/packages/react-router/tsconfig.test.json b/packages/react-router/tsconfig.test.json new file mode 100644 index 000000000000..b3625cd0bd3f --- /dev/null +++ b/packages/react-router/tsconfig.test.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + + "include": ["test/**/*", "vite.config.ts"], + + "compilerOptions": { + "types": ["node", "vitest/globals"], + } +} diff --git a/packages/react-router/tsconfig.types.json b/packages/react-router/tsconfig.types.json new file mode 100644 index 000000000000..65455f66bd75 --- /dev/null +++ b/packages/react-router/tsconfig.types.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "build/types" + } +} diff --git a/packages/react-router/vite.config.ts b/packages/react-router/vite.config.ts new file mode 100644 index 000000000000..1094fe0d79da --- /dev/null +++ b/packages/react-router/vite.config.ts @@ -0,0 +1,9 @@ +import baseConfig from '../../vite/vite.config'; + +export default { + ...baseConfig, + test: { + ...baseConfig.test, + environment: 'jsdom', + }, +}; diff --git a/scripts/ci-unit-tests.ts b/scripts/ci-unit-tests.ts index 13cbc6957d5c..cd53df2dfd68 100644 --- a/scripts/ci-unit-tests.ts +++ b/scripts/ci-unit-tests.ts @@ -4,6 +4,7 @@ import * as path from 'path'; const UNIT_TEST_ENV = process.env.UNIT_TEST_ENV as 'node' | 'browser' | undefined; const RUN_AFFECTED = process.argv.includes('--affected'); +const NODE_VERSION = process.env.NODE_VERSION as '18' | '20' | '22'; // These packages are tested separately in CI, so no need to run them here const DEFAULT_SKIP_PACKAGES = ['@sentry/bun', '@sentry/deno']; @@ -25,6 +26,9 @@ const BROWSER_TEST_PACKAGES = [ '@sentry/wasm', ]; +// Packages that cannot run in Node 18 +const SKIP_NODE_18_PACKAGES = ['@sentry/react-router']; + function getAllPackages(): string[] { const { workspaces }: { workspaces: string[] } = JSON.parse( fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf-8'), @@ -55,6 +59,10 @@ function runTests(): void { }); } else if (UNIT_TEST_ENV === 'node') { BROWSER_TEST_PACKAGES.forEach(pkg => ignores.add(pkg)); + + if (NODE_VERSION === '18') { + SKIP_NODE_18_PACKAGES.forEach(pkg => ignores.add(pkg)); + } } if (RUN_AFFECTED) { diff --git a/yarn.lock b/yarn.lock index 21615559f6dc..f1e8c2068fdb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4759,6 +4759,11 @@ semver "^7.3.5" tar "^6.1.11" +"@mjackson/node-fetch-server@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@mjackson/node-fetch-server/-/node-fetch-server-0.2.0.tgz#577c0c25d8aae9f69a97738b7b0d03d1471cdc49" + integrity sha512-EMlH1e30yzmTpGLQjlFmaDAjyOeZhng1/XCd7DExR8PNAnG/G1tyruZxEoUe11ClnwGhGrtsdnyyUx1frSzjng== + "@nestjs/common@10.4.6": version "10.4.6" resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-10.4.6.tgz#952e8fd0ceafeffcc4eaf47effd67fb395844ae0" @@ -6162,6 +6167,16 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== +"@react-router/node@^7.1.5": + version "7.1.5" + resolved "https://registry.yarnpkg.com/@react-router/node/-/node-7.1.5.tgz#fe4bdb708bb574cbf21b359d1263f6accde737bd" + integrity sha512-Ga8xFHxO2yt5TpGwV5xYx4LC3eUDmhT6jYfTbMFb6F7hBA9sLdHxNfYZCe2WEfVZ4/BM7I8989Qzq6BWilV2LA== + dependencies: + "@mjackson/node-fetch-server" "^0.2.0" + source-map-support "^0.5.21" + stream-slice "^0.1.2" + undici "^6.19.2" + "@redis/bloom@1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@redis/bloom/-/bloom-1.2.0.tgz#d3fd6d3c0af3ef92f26767b56414a370c7b63b71" @@ -12604,6 +12619,11 @@ cookie@^0.6.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== +cookie@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.0.2.tgz#27360701532116bd3f1f9416929d176afe1e4610" + integrity sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA== + cookie@~0.7.2: version "0.7.2" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" @@ -25243,6 +25263,16 @@ react-router@6.28.1: dependencies: "@remix-run/router" "1.21.0" +react-router@^7.1.5: + version "7.1.5" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-7.1.5.tgz#c9e19d329d9ce2215fdae844ab6b023b911094db" + integrity sha512-8BUF+hZEU4/z/JD201yK6S+UYhsf58bzYIDq2NS1iGpwxSXDu7F+DeGSkIXMFBuHZB21FSiCzEcUb18cQNdRkA== + dependencies: + "@types/cookie" "^0.6.0" + cookie "^1.0.1" + set-cookie-parser "^2.6.0" + turbo-stream "2.4.0" + react@^18.0.0: version "18.0.0" resolved "https://registry.yarnpkg.com/react/-/react-18.0.0.tgz#b468736d1f4a5891f38585ba8e8fb29f91c3cb96" @@ -28908,6 +28938,11 @@ undici@^6.11.1: resolved "https://registry.yarnpkg.com/undici/-/undici-6.21.0.tgz#4b3d3afaef984e07b48e7620c34ed8a285ed4cd4" integrity sha512-BUgJXc752Kou3oOIuU1i+yZZypyZRqNPW0vqoMPl8VaoalSfeR0D8/t4iAS3yirs79SSMTxTag+ZC86uswv+Cw== +undici@^6.19.2: + version "6.21.1" + resolved "https://registry.yarnpkg.com/undici/-/undici-6.21.1.tgz#336025a14162e6837e44ad7b819b35b6c6af0e05" + integrity sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ== + unenv@^1.10.0, unenv@^1.9.0: version "1.10.0" resolved "https://registry.yarnpkg.com/unenv/-/unenv-1.10.0.tgz#c3394a6c6e4cfe68d699f87af456fe3f0db39571"