@error-explorer/react 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +465 -0
- package/dist/index.cjs +424 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +458 -0
- package/dist/index.d.ts +458 -0
- package/dist/index.js +376 -0
- package/dist/index.js.map +1 -0
- package/dist/router.cjs +125 -0
- package/dist/router.cjs.map +1 -0
- package/dist/router.d.cts +106 -0
- package/dist/router.d.ts +106 -0
- package/dist/router.js +98 -0
- package/dist/router.js.map +1 -0
- package/package.json +85 -0
package/dist/router.d.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* React Router integration for Error Explorer
|
|
5
|
+
*
|
|
6
|
+
* Tracks navigation events as breadcrumbs.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Router integration options
|
|
11
|
+
*/
|
|
12
|
+
interface RouterIntegrationOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Track navigation events as breadcrumbs
|
|
15
|
+
* @default true
|
|
16
|
+
*/
|
|
17
|
+
trackNavigation?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Include URL parameters in breadcrumbs
|
|
20
|
+
* @default false (for privacy)
|
|
21
|
+
*/
|
|
22
|
+
trackParams?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Include query string in breadcrumbs
|
|
25
|
+
* @default false (for privacy)
|
|
26
|
+
*/
|
|
27
|
+
trackQuery?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Include hash in breadcrumbs
|
|
30
|
+
* @default false
|
|
31
|
+
*/
|
|
32
|
+
trackHash?: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Hook to track React Router navigation
|
|
36
|
+
*
|
|
37
|
+
* Compatible with React Router v6+
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```tsx
|
|
41
|
+
* import { useLocation } from 'react-router-dom';
|
|
42
|
+
* import { useRouterBreadcrumbs } from '@error-explorer/react/router';
|
|
43
|
+
*
|
|
44
|
+
* function App() {
|
|
45
|
+
* const location = useLocation();
|
|
46
|
+
* useRouterBreadcrumbs(location);
|
|
47
|
+
*
|
|
48
|
+
* return <Routes>...</Routes>;
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
declare function useRouterBreadcrumbs(location: {
|
|
53
|
+
pathname: string;
|
|
54
|
+
search?: string;
|
|
55
|
+
hash?: string;
|
|
56
|
+
}, options?: RouterIntegrationOptions): void;
|
|
57
|
+
/**
|
|
58
|
+
* Create a navigation listener for React Router
|
|
59
|
+
*
|
|
60
|
+
* Use this with the router's subscribe/listen functionality.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```tsx
|
|
64
|
+
* import { createBrowserRouter } from 'react-router-dom';
|
|
65
|
+
* import { createNavigationListener } from '@error-explorer/react/router';
|
|
66
|
+
*
|
|
67
|
+
* const router = createBrowserRouter([...routes]);
|
|
68
|
+
* const unsubscribe = createNavigationListener(router);
|
|
69
|
+
*
|
|
70
|
+
* // Cleanup when needed
|
|
71
|
+
* // unsubscribe();
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
declare function createNavigationListener(router: {
|
|
75
|
+
subscribe: (callback: (state: {
|
|
76
|
+
location: {
|
|
77
|
+
pathname: string;
|
|
78
|
+
search: string;
|
|
79
|
+
hash: string;
|
|
80
|
+
};
|
|
81
|
+
}) => void) => () => void;
|
|
82
|
+
}, options?: RouterIntegrationOptions): () => void;
|
|
83
|
+
/**
|
|
84
|
+
* Higher-order component to wrap router with breadcrumb tracking
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```tsx
|
|
88
|
+
* import { BrowserRouter } from 'react-router-dom';
|
|
89
|
+
* import { withRouterTracking } from '@error-explorer/react/router';
|
|
90
|
+
*
|
|
91
|
+
* const TrackedRouter = withRouterTracking(BrowserRouter);
|
|
92
|
+
*
|
|
93
|
+
* function App() {
|
|
94
|
+
* return (
|
|
95
|
+
* <TrackedRouter>
|
|
96
|
+
* <Routes>...</Routes>
|
|
97
|
+
* </TrackedRouter>
|
|
98
|
+
* );
|
|
99
|
+
* }
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
declare function withRouterTracking<P extends object>(RouterComponent: React.ComponentType<P>, options?: RouterIntegrationOptions): React.FC<P & {
|
|
103
|
+
children?: React.ReactNode;
|
|
104
|
+
}>;
|
|
105
|
+
|
|
106
|
+
export { type RouterIntegrationOptions, createNavigationListener, useRouterBreadcrumbs, withRouterTracking };
|
package/dist/router.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// src/router.tsx
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
import { ErrorExplorer } from "@error-explorer/browser";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
var defaultOptions = {
|
|
6
|
+
trackNavigation: true,
|
|
7
|
+
trackParams: false,
|
|
8
|
+
trackQuery: false,
|
|
9
|
+
trackHash: false
|
|
10
|
+
};
|
|
11
|
+
function useRouterBreadcrumbs(location, options = {}) {
|
|
12
|
+
const opts = { ...defaultOptions, ...options };
|
|
13
|
+
const previousLocation = useRef(null);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (!opts.trackNavigation) return;
|
|
16
|
+
let url = location.pathname;
|
|
17
|
+
if (opts.trackQuery && location.search) {
|
|
18
|
+
url += location.search;
|
|
19
|
+
}
|
|
20
|
+
if (opts.trackHash && location.hash) {
|
|
21
|
+
url += location.hash;
|
|
22
|
+
}
|
|
23
|
+
if (previousLocation.current === url) return;
|
|
24
|
+
const from = previousLocation.current;
|
|
25
|
+
previousLocation.current = url;
|
|
26
|
+
ErrorExplorer.addBreadcrumb({
|
|
27
|
+
type: "navigation",
|
|
28
|
+
category: "navigation",
|
|
29
|
+
message: from ? `Navigated to ${url}` : `Initial page: ${url}`,
|
|
30
|
+
level: "info",
|
|
31
|
+
data: {
|
|
32
|
+
from: from || void 0,
|
|
33
|
+
to: url
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}, [location.pathname, location.search, location.hash, opts]);
|
|
37
|
+
}
|
|
38
|
+
function createNavigationListener(router, options = {}) {
|
|
39
|
+
const opts = { ...defaultOptions, ...options };
|
|
40
|
+
let previousPath = null;
|
|
41
|
+
return router.subscribe((state) => {
|
|
42
|
+
if (!opts.trackNavigation) return;
|
|
43
|
+
const { pathname, search, hash } = state.location;
|
|
44
|
+
let url = pathname;
|
|
45
|
+
if (opts.trackQuery && search) {
|
|
46
|
+
url += search;
|
|
47
|
+
}
|
|
48
|
+
if (opts.trackHash && hash) {
|
|
49
|
+
url += hash;
|
|
50
|
+
}
|
|
51
|
+
if (previousPath === url) return;
|
|
52
|
+
const from = previousPath;
|
|
53
|
+
previousPath = url;
|
|
54
|
+
ErrorExplorer.addBreadcrumb({
|
|
55
|
+
type: "navigation",
|
|
56
|
+
category: "navigation",
|
|
57
|
+
message: from ? `Navigated to ${url}` : `Initial page: ${url}`,
|
|
58
|
+
level: "info",
|
|
59
|
+
data: {
|
|
60
|
+
from: from || void 0,
|
|
61
|
+
to: url
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function withRouterTracking(RouterComponent, options = {}) {
|
|
67
|
+
const Wrapped = (props) => {
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (options.trackNavigation !== false) {
|
|
70
|
+
let url = window.location.pathname;
|
|
71
|
+
if (options.trackQuery) {
|
|
72
|
+
url += window.location.search;
|
|
73
|
+
}
|
|
74
|
+
if (options.trackHash) {
|
|
75
|
+
url += window.location.hash;
|
|
76
|
+
}
|
|
77
|
+
ErrorExplorer.addBreadcrumb({
|
|
78
|
+
type: "navigation",
|
|
79
|
+
category: "navigation",
|
|
80
|
+
message: `Initial page: ${url}`,
|
|
81
|
+
level: "info",
|
|
82
|
+
data: {
|
|
83
|
+
to: url
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}, []);
|
|
88
|
+
return /* @__PURE__ */ jsx(RouterComponent, { ...props });
|
|
89
|
+
};
|
|
90
|
+
Wrapped.displayName = `withRouterTracking(${RouterComponent.displayName || RouterComponent.name || "Router"})`;
|
|
91
|
+
return Wrapped;
|
|
92
|
+
}
|
|
93
|
+
export {
|
|
94
|
+
createNavigationListener,
|
|
95
|
+
useRouterBreadcrumbs,
|
|
96
|
+
withRouterTracking
|
|
97
|
+
};
|
|
98
|
+
//# sourceMappingURL=router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/router.tsx"],"sourcesContent":["/**\n * React Router integration for Error Explorer\n *\n * Tracks navigation events as breadcrumbs.\n */\n\nimport React, { useEffect, useRef } from 'react';\nimport { ErrorExplorer } from '@error-explorer/browser';\n\n/**\n * Router integration options\n */\nexport interface RouterIntegrationOptions {\n /**\n * Track navigation events as breadcrumbs\n * @default true\n */\n trackNavigation?: boolean;\n\n /**\n * Include URL parameters in breadcrumbs\n * @default false (for privacy)\n */\n trackParams?: boolean;\n\n /**\n * Include query string in breadcrumbs\n * @default false (for privacy)\n */\n trackQuery?: boolean;\n\n /**\n * Include hash in breadcrumbs\n * @default false\n */\n trackHash?: boolean;\n}\n\nconst defaultOptions: Required<RouterIntegrationOptions> = {\n trackNavigation: true,\n trackParams: false,\n trackQuery: false,\n trackHash: false,\n};\n\n/**\n * Hook to track React Router navigation\n *\n * Compatible with React Router v6+\n *\n * @example\n * ```tsx\n * import { useLocation } from 'react-router-dom';\n * import { useRouterBreadcrumbs } from '@error-explorer/react/router';\n *\n * function App() {\n * const location = useLocation();\n * useRouterBreadcrumbs(location);\n *\n * return <Routes>...</Routes>;\n * }\n * ```\n */\nexport function useRouterBreadcrumbs(\n location: { pathname: string; search?: string; hash?: string },\n options: RouterIntegrationOptions = {}\n) {\n const opts = { ...defaultOptions, ...options };\n const previousLocation = useRef<string | null>(null);\n\n useEffect(() => {\n if (!opts.trackNavigation) return;\n\n // Build the URL to track\n let url = location.pathname;\n if (opts.trackQuery && location.search) {\n url += location.search;\n }\n if (opts.trackHash && location.hash) {\n url += location.hash;\n }\n\n // Skip if same as previous\n if (previousLocation.current === url) return;\n\n const from = previousLocation.current;\n previousLocation.current = url;\n\n // Add breadcrumb\n ErrorExplorer.addBreadcrumb({\n type: 'navigation',\n category: 'navigation',\n message: from ? `Navigated to ${url}` : `Initial page: ${url}`,\n level: 'info',\n data: {\n from: from || undefined,\n to: url,\n },\n });\n }, [location.pathname, location.search, location.hash, opts]);\n}\n\n/**\n * Create a navigation listener for React Router\n *\n * Use this with the router's subscribe/listen functionality.\n *\n * @example\n * ```tsx\n * import { createBrowserRouter } from 'react-router-dom';\n * import { createNavigationListener } from '@error-explorer/react/router';\n *\n * const router = createBrowserRouter([...routes]);\n * const unsubscribe = createNavigationListener(router);\n *\n * // Cleanup when needed\n * // unsubscribe();\n * ```\n */\nexport function createNavigationListener(\n router: { subscribe: (callback: (state: { location: { pathname: string; search: string; hash: string } }) => void) => () => void },\n options: RouterIntegrationOptions = {}\n): () => void {\n const opts = { ...defaultOptions, ...options };\n let previousPath: string | null = null;\n\n return router.subscribe((state) => {\n if (!opts.trackNavigation) return;\n\n const { pathname, search, hash } = state.location;\n\n // Build the URL to track\n let url = pathname;\n if (opts.trackQuery && search) {\n url += search;\n }\n if (opts.trackHash && hash) {\n url += hash;\n }\n\n // Skip if same as previous\n if (previousPath === url) return;\n\n const from = previousPath;\n previousPath = url;\n\n ErrorExplorer.addBreadcrumb({\n type: 'navigation',\n category: 'navigation',\n message: from ? `Navigated to ${url}` : `Initial page: ${url}`,\n level: 'info',\n data: {\n from: from || undefined,\n to: url,\n },\n });\n });\n}\n\n/**\n * Higher-order component to wrap router with breadcrumb tracking\n *\n * @example\n * ```tsx\n * import { BrowserRouter } from 'react-router-dom';\n * import { withRouterTracking } from '@error-explorer/react/router';\n *\n * const TrackedRouter = withRouterTracking(BrowserRouter);\n *\n * function App() {\n * return (\n * <TrackedRouter>\n * <Routes>...</Routes>\n * </TrackedRouter>\n * );\n * }\n * ```\n */\nexport function withRouterTracking<P extends object>(\n RouterComponent: React.ComponentType<P>,\n options: RouterIntegrationOptions = {}\n): React.FC<P & { children?: React.ReactNode }> {\n const Wrapped: React.FC<P & { children?: React.ReactNode }> = (props) => {\n // Track initial page load\n useEffect(() => {\n if (options.trackNavigation !== false) {\n let url = window.location.pathname;\n if (options.trackQuery) {\n url += window.location.search;\n }\n if (options.trackHash) {\n url += window.location.hash;\n }\n\n ErrorExplorer.addBreadcrumb({\n type: 'navigation',\n category: 'navigation',\n message: `Initial page: ${url}`,\n level: 'info',\n data: {\n to: url,\n },\n });\n }\n }, []);\n\n return <RouterComponent {...props} />;\n };\n\n Wrapped.displayName = `withRouterTracking(${RouterComponent.displayName || RouterComponent.name || 'Router'})`;\n\n return Wrapped;\n}\n"],"mappings":";AAMA,SAAgB,WAAW,cAAc;AACzC,SAAS,qBAAqB;AAuMnB;AAxKX,IAAM,iBAAqD;AAAA,EACzD,iBAAiB;AAAA,EACjB,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,WAAW;AACb;AAoBO,SAAS,qBACd,UACA,UAAoC,CAAC,GACrC;AACA,QAAM,OAAO,EAAE,GAAG,gBAAgB,GAAG,QAAQ;AAC7C,QAAM,mBAAmB,OAAsB,IAAI;AAEnD,YAAU,MAAM;AACd,QAAI,CAAC,KAAK,gBAAiB;AAG3B,QAAI,MAAM,SAAS;AACnB,QAAI,KAAK,cAAc,SAAS,QAAQ;AACtC,aAAO,SAAS;AAAA,IAClB;AACA,QAAI,KAAK,aAAa,SAAS,MAAM;AACnC,aAAO,SAAS;AAAA,IAClB;AAGA,QAAI,iBAAiB,YAAY,IAAK;AAEtC,UAAM,OAAO,iBAAiB;AAC9B,qBAAiB,UAAU;AAG3B,kBAAc,cAAc;AAAA,MAC1B,MAAM;AAAA,MACN,UAAU;AAAA,MACV,SAAS,OAAO,gBAAgB,GAAG,KAAK,iBAAiB,GAAG;AAAA,MAC5D,OAAO;AAAA,MACP,MAAM;AAAA,QACJ,MAAM,QAAQ;AAAA,QACd,IAAI;AAAA,MACN;AAAA,IACF,CAAC;AAAA,EACH,GAAG,CAAC,SAAS,UAAU,SAAS,QAAQ,SAAS,MAAM,IAAI,CAAC;AAC9D;AAmBO,SAAS,yBACd,QACA,UAAoC,CAAC,GACzB;AACZ,QAAM,OAAO,EAAE,GAAG,gBAAgB,GAAG,QAAQ;AAC7C,MAAI,eAA8B;AAElC,SAAO,OAAO,UAAU,CAAC,UAAU;AACjC,QAAI,CAAC,KAAK,gBAAiB;AAE3B,UAAM,EAAE,UAAU,QAAQ,KAAK,IAAI,MAAM;AAGzC,QAAI,MAAM;AACV,QAAI,KAAK,cAAc,QAAQ;AAC7B,aAAO;AAAA,IACT;AACA,QAAI,KAAK,aAAa,MAAM;AAC1B,aAAO;AAAA,IACT;AAGA,QAAI,iBAAiB,IAAK;AAE1B,UAAM,OAAO;AACb,mBAAe;AAEf,kBAAc,cAAc;AAAA,MAC1B,MAAM;AAAA,MACN,UAAU;AAAA,MACV,SAAS,OAAO,gBAAgB,GAAG,KAAK,iBAAiB,GAAG;AAAA,MAC5D,OAAO;AAAA,MACP,MAAM;AAAA,QACJ,MAAM,QAAQ;AAAA,QACd,IAAI;AAAA,MACN;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;AAqBO,SAAS,mBACd,iBACA,UAAoC,CAAC,GACS;AAC9C,QAAM,UAAwD,CAAC,UAAU;AAEvE,cAAU,MAAM;AACd,UAAI,QAAQ,oBAAoB,OAAO;AACrC,YAAI,MAAM,OAAO,SAAS;AAC1B,YAAI,QAAQ,YAAY;AACtB,iBAAO,OAAO,SAAS;AAAA,QACzB;AACA,YAAI,QAAQ,WAAW;AACrB,iBAAO,OAAO,SAAS;AAAA,QACzB;AAEA,sBAAc,cAAc;AAAA,UAC1B,MAAM;AAAA,UACN,UAAU;AAAA,UACV,SAAS,iBAAiB,GAAG;AAAA,UAC7B,OAAO;AAAA,UACP,MAAM;AAAA,YACJ,IAAI;AAAA,UACN;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,GAAG,CAAC,CAAC;AAEL,WAAO,oBAAC,mBAAiB,GAAG,OAAO;AAAA,EACrC;AAEA,UAAQ,cAAc,sBAAsB,gBAAgB,eAAe,gBAAgB,QAAQ,QAAQ;AAE3G,SAAO;AACT;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@error-explorer/react",
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "Error Explorer SDK for React - Automatic error tracking with React integration",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./router": {
|
|
16
|
+
"types": "./dist/router.d.ts",
|
|
17
|
+
"import": "./dist/router.js",
|
|
18
|
+
"require": "./dist/router.cjs"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsup",
|
|
27
|
+
"dev": "tsup --watch",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"test:watch": "vitest",
|
|
30
|
+
"test:coverage": "vitest run --coverage",
|
|
31
|
+
"typecheck": "tsc --noEmit",
|
|
32
|
+
"lint": "eslint src --ext .ts,.tsx",
|
|
33
|
+
"clean": "rm -rf dist"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
37
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
38
|
+
"react-router-dom": "^6.0.0 || ^7.0.0"
|
|
39
|
+
},
|
|
40
|
+
"peerDependenciesMeta": {
|
|
41
|
+
"react-router-dom": {
|
|
42
|
+
"optional": true
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@error-explorer/browser": "file:../../core/browser"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@testing-library/jest-dom": "^6.1.0",
|
|
50
|
+
"@testing-library/react": "^14.0.0",
|
|
51
|
+
"@types/node": "^20.10.0",
|
|
52
|
+
"@types/react": "^18.2.0",
|
|
53
|
+
"@types/react-dom": "^18.2.0",
|
|
54
|
+
"happy-dom": "^12.10.0",
|
|
55
|
+
"react": "^18.2.0",
|
|
56
|
+
"react-dom": "^18.2.0",
|
|
57
|
+
"react-router-dom": "^6.20.0",
|
|
58
|
+
"tsup": "^8.0.0",
|
|
59
|
+
"typescript": "^5.3.0",
|
|
60
|
+
"vitest": "^1.0.0"
|
|
61
|
+
},
|
|
62
|
+
"keywords": [
|
|
63
|
+
"error-explorer",
|
|
64
|
+
"react",
|
|
65
|
+
"error-tracking",
|
|
66
|
+
"error-monitoring",
|
|
67
|
+
"error-boundary",
|
|
68
|
+
"breadcrumbs",
|
|
69
|
+
"debugging"
|
|
70
|
+
],
|
|
71
|
+
"author": "Error Explorer",
|
|
72
|
+
"license": "MIT",
|
|
73
|
+
"repository": {
|
|
74
|
+
"type": "git",
|
|
75
|
+
"url": "https://github.com/Error-Explorer/SDKs.git",
|
|
76
|
+
"directory": "frameworks/react"
|
|
77
|
+
},
|
|
78
|
+
"bugs": {
|
|
79
|
+
"url": "https://github.com/Error-Explorer/SDKs/issues"
|
|
80
|
+
},
|
|
81
|
+
"homepage": "https://github.com/Error-Explorer/SDKs/tree/main/frameworks/react#readme",
|
|
82
|
+
"engines": {
|
|
83
|
+
"node": ">=18.0.0"
|
|
84
|
+
}
|
|
85
|
+
}
|