@rn-ave/core 0.1.0
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 +31 -0
- package/dist/appRegistry.d.ts +11 -0
- package/dist/appRegistry.js +52 -0
- package/dist/auto.d.ts +15 -0
- package/dist/auto.js +35 -0
- package/dist/components/InspectorButton.d.ts +7 -0
- package/dist/components/InspectorButton.js +125 -0
- package/dist/components/InspectorOverlay.d.ts +11 -0
- package/dist/components/InspectorOverlay.js +60 -0
- package/dist/context/extractor.d.ts +7 -0
- package/dist/context/extractor.js +235 -0
- package/dist/context/formatter.d.ts +13 -0
- package/dist/context/formatter.js +95 -0
- package/dist/devMenu.d.ts +26 -0
- package/dist/devMenu.js +80 -0
- package/dist/editor.d.ts +20 -0
- package/dist/editor.js +93 -0
- package/dist/fiber/hook.d.ts +18 -0
- package/dist/fiber/hook.js +103 -0
- package/dist/fiber/source.d.ts +21 -0
- package/dist/fiber/source.js +94 -0
- package/dist/fiber/traversal.d.ts +27 -0
- package/dist/fiber/traversal.js +163 -0
- package/dist/gesture/detector.d.ts +17 -0
- package/dist/gesture/detector.js +84 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +8 -0
- package/dist/overlay/highlight.d.ts +14 -0
- package/dist/overlay/highlight.js +177 -0
- package/dist/overlay/tooltip.d.ts +15 -0
- package/dist/overlay/tooltip.js +467 -0
- package/dist/provider.d.ts +10 -0
- package/dist/provider.js +264 -0
- package/dist/types.d.ts +94 -0
- package/dist/types.js +2 -0
- package/package.json +58 -0
package/dist/provider.js
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.RNAVEProvider = RNAVEProvider;
|
|
37
|
+
const react_1 = __importStar(require("react"));
|
|
38
|
+
const react_native_1 = require("react-native");
|
|
39
|
+
const hook_1 = require("./fiber/hook");
|
|
40
|
+
const traversal_1 = require("./fiber/traversal");
|
|
41
|
+
const extractor_1 = require("./context/extractor");
|
|
42
|
+
const highlight_1 = require("./overlay/highlight");
|
|
43
|
+
const InspectorButton_1 = require("./components/InspectorButton");
|
|
44
|
+
const InspectorOverlay_1 = require("./components/InspectorOverlay");
|
|
45
|
+
const editor_1 = require("./editor");
|
|
46
|
+
const devMenu_1 = require("./devMenu");
|
|
47
|
+
/**
|
|
48
|
+
* Internal provider component for rn-ave
|
|
49
|
+
* This is used by auto-initialization only - not meant to be used directly
|
|
50
|
+
*/
|
|
51
|
+
function RNAVEProvider({ children }) {
|
|
52
|
+
const [isInspecting, setIsInspecting] = (0, react_1.useState)(false);
|
|
53
|
+
const [selectedElement, setSelectedElement] = (0, react_1.useState)(null);
|
|
54
|
+
const [lastContext, setLastContext] = (0, react_1.useState)(null);
|
|
55
|
+
const containerRef = (0, react_1.useRef)(null);
|
|
56
|
+
// Install DevTools hook on mount
|
|
57
|
+
(0, react_1.useEffect)(() => {
|
|
58
|
+
(0, hook_1.installHook)();
|
|
59
|
+
// Debug: log hook state after a short delay
|
|
60
|
+
setTimeout(() => {
|
|
61
|
+
(0, hook_1.debugHookState)();
|
|
62
|
+
}, 1000);
|
|
63
|
+
}, []);
|
|
64
|
+
const enableInspector = (0, react_1.useCallback)(() => {
|
|
65
|
+
setIsInspecting(true);
|
|
66
|
+
(0, hook_1.debugHookState)();
|
|
67
|
+
}, []);
|
|
68
|
+
const disableInspector = (0, react_1.useCallback)(() => {
|
|
69
|
+
setIsInspecting(false);
|
|
70
|
+
setSelectedElement(null);
|
|
71
|
+
}, []);
|
|
72
|
+
const toggleInspecting = (0, react_1.useCallback)(() => {
|
|
73
|
+
if (isInspecting) {
|
|
74
|
+
disableInspector();
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
enableInspector();
|
|
78
|
+
}
|
|
79
|
+
}, [isInspecting, enableInspector, disableInspector]);
|
|
80
|
+
// Register dev menu toggle
|
|
81
|
+
(0, react_1.useEffect)(() => {
|
|
82
|
+
if (typeof __DEV__ !== 'undefined' && !__DEV__)
|
|
83
|
+
return;
|
|
84
|
+
(0, devMenu_1.registerDevMenuToggle)(toggleInspecting);
|
|
85
|
+
}, [toggleInspecting]);
|
|
86
|
+
// Open in editor function - always uses Cursor
|
|
87
|
+
const openInEditor = (0, react_1.useCallback)(async (sourceOverride) => {
|
|
88
|
+
console.log('\n📂 [rn-ave] openInEditor called');
|
|
89
|
+
const source = sourceOverride || lastContext?.source;
|
|
90
|
+
console.log(' source to open:', source);
|
|
91
|
+
if (!source) {
|
|
92
|
+
console.log(' ERROR: No source location available');
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
return (0, editor_1.launchEditor)({
|
|
96
|
+
source,
|
|
97
|
+
editor: 'cursor',
|
|
98
|
+
});
|
|
99
|
+
}, [lastContext]);
|
|
100
|
+
/**
|
|
101
|
+
* Find the element at tap coordinates using UIManager.measure
|
|
102
|
+
*/
|
|
103
|
+
const findElementAtPosition = (0, react_1.useCallback)(async (tapX, tapY) => {
|
|
104
|
+
const fiberRoot = (0, hook_1.getFiberRoot)();
|
|
105
|
+
if (!fiberRoot) {
|
|
106
|
+
(0, hook_1.debugHookState)();
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
// Collect all host fibers
|
|
110
|
+
const hostFibers = (0, traversal_1.collectAllHostFibers)(fiberRoot);
|
|
111
|
+
if (hostFibers.length === 0) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
// Measure each fiber and find the best match
|
|
115
|
+
const measurePromises = hostFibers.map((fiber) => new Promise((resolve) => {
|
|
116
|
+
if (!fiber.stateNode) {
|
|
117
|
+
resolve(null);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
// Try to get native tag from different locations
|
|
121
|
+
const nativeTag = fiber.stateNode._nativeTag ||
|
|
122
|
+
fiber.stateNode.canonical?._nativeTag ||
|
|
123
|
+
(0, react_native_1.findNodeHandle)(fiber.stateNode);
|
|
124
|
+
if (!nativeTag) {
|
|
125
|
+
resolve(null);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
react_native_1.UIManager.measure(nativeTag, (x, y, width, height, pageX, pageY) => {
|
|
130
|
+
if (width > 0 && height > 0) {
|
|
131
|
+
resolve({
|
|
132
|
+
fiber,
|
|
133
|
+
bounds: { x: pageX, y: pageY, width, height },
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
resolve(null);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
catch (e) {
|
|
142
|
+
resolve(null);
|
|
143
|
+
}
|
|
144
|
+
}));
|
|
145
|
+
const measured = (await Promise.all(measurePromises)).filter((m) => m !== null);
|
|
146
|
+
// Find all fibers that contain the tap point
|
|
147
|
+
const hits = measured.filter(({ bounds }) => {
|
|
148
|
+
return (tapX >= bounds.x &&
|
|
149
|
+
tapX <= bounds.x + bounds.width &&
|
|
150
|
+
tapY >= bounds.y &&
|
|
151
|
+
tapY <= bounds.y + bounds.height);
|
|
152
|
+
});
|
|
153
|
+
if (hits.length === 0) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
// Return the smallest (most specific) element
|
|
157
|
+
hits.sort((a, b) => {
|
|
158
|
+
const areaA = a.bounds.width * a.bounds.height;
|
|
159
|
+
const areaB = b.bounds.width * b.bounds.height;
|
|
160
|
+
return areaA - areaB;
|
|
161
|
+
});
|
|
162
|
+
return hits[0].fiber;
|
|
163
|
+
}, []);
|
|
164
|
+
const handleTapAtPosition = (0, react_1.useCallback)(async (x, y) => {
|
|
165
|
+
console.log('\n👆 [rn-ave] handleTapAtPosition called');
|
|
166
|
+
console.log(' position:', { x, y });
|
|
167
|
+
console.log(' isInspecting:', isInspecting);
|
|
168
|
+
if (!isInspecting) {
|
|
169
|
+
console.log(' Not in inspecting mode, returning');
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const fiber = await findElementAtPosition(x, y);
|
|
173
|
+
console.log(' fiber found:', !!fiber);
|
|
174
|
+
if (!fiber) {
|
|
175
|
+
console.log(' No fiber found at position');
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
// Find nearest composite component for context (skip node_modules)
|
|
179
|
+
const compositeFiber = (0, traversal_1.findNearestComposite)(fiber, true) || fiber;
|
|
180
|
+
console.log(' compositeFiber:', compositeFiber?.type?.name || compositeFiber?.type);
|
|
181
|
+
// Pass both fibers: composite for name/hierarchy, host for source (__rnAve prop)
|
|
182
|
+
const context = (0, extractor_1.extractContext)(compositeFiber, fiber);
|
|
183
|
+
console.log(' extracted context:', {
|
|
184
|
+
componentName: context.componentName,
|
|
185
|
+
source: context.source,
|
|
186
|
+
});
|
|
187
|
+
setLastContext(context);
|
|
188
|
+
// Get native tag from the host fiber (not composite)
|
|
189
|
+
const nativeTag = fiber.stateNode?._nativeTag ||
|
|
190
|
+
fiber.stateNode?.canonical?._nativeTag ||
|
|
191
|
+
(0, react_native_1.findNodeHandle)(fiber.stateNode) ||
|
|
192
|
+
0;
|
|
193
|
+
const element = {
|
|
194
|
+
nativeTag,
|
|
195
|
+
componentInfo: context,
|
|
196
|
+
};
|
|
197
|
+
console.log(' setting selectedElement with source:', element.componentInfo.source);
|
|
198
|
+
setSelectedElement(element);
|
|
199
|
+
// Auto-disable inspecting after selection
|
|
200
|
+
setIsInspecting(false);
|
|
201
|
+
}, [isInspecting, findElementAtPosition]);
|
|
202
|
+
const handleDismiss = (0, react_1.useCallback)(() => {
|
|
203
|
+
setSelectedElement(null);
|
|
204
|
+
}, []);
|
|
205
|
+
const handleSendToAgent = (0, react_1.useCallback)(async (agentType, prompt, contextOverride) => {
|
|
206
|
+
if (!selectedElement) {
|
|
207
|
+
return { success: false, error: 'No element selected' };
|
|
208
|
+
}
|
|
209
|
+
if (!prompt?.trim()) {
|
|
210
|
+
return { success: false, error: 'Prompt is required' };
|
|
211
|
+
}
|
|
212
|
+
const baseContext = contextOverride || selectedElement.componentInfo;
|
|
213
|
+
const payload = {
|
|
214
|
+
...baseContext,
|
|
215
|
+
userPrompt: prompt.trim(),
|
|
216
|
+
};
|
|
217
|
+
// Determine port based on agent type
|
|
218
|
+
const portMap = {
|
|
219
|
+
'claude-code': 4568,
|
|
220
|
+
'opencode': 4569,
|
|
221
|
+
'cursor': 5568,
|
|
222
|
+
};
|
|
223
|
+
const port = portMap[agentType] || 4568;
|
|
224
|
+
console.log(`\n📤 [rn-ave] Sending to ${agentType}...`);
|
|
225
|
+
console.log(`💬 Prompt: "${prompt.trim()}"`);
|
|
226
|
+
console.log(`📦 Component: ${selectedElement.componentInfo.componentName}`);
|
|
227
|
+
if (selectedElement.componentInfo.source) {
|
|
228
|
+
console.log(`📁 File: ${selectedElement.componentInfo.source.file}:${selectedElement.componentInfo.source.line}`);
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
const response = await fetch(`http://localhost:${port}/context`, {
|
|
232
|
+
method: 'POST',
|
|
233
|
+
headers: { 'Content-Type': 'application/json' },
|
|
234
|
+
body: JSON.stringify(payload),
|
|
235
|
+
});
|
|
236
|
+
if (response.ok) {
|
|
237
|
+
const data = await response.json();
|
|
238
|
+
console.log(`✅ [rn-ave] Sent successfully to ${agentType}!`);
|
|
239
|
+
handleDismiss();
|
|
240
|
+
return { success: true };
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
console.log(`⚠️ [rn-ave] Server responded with ${response.status}`);
|
|
244
|
+
return { success: false, error: `Server responded with ${response.status}` };
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
catch (e) {
|
|
248
|
+
console.log(`⚠️ [rn-ave] Could not connect to ${agentType} server. Is it running?`);
|
|
249
|
+
return { success: false, error: `Could not connect to ${agentType} server. Is it running?` };
|
|
250
|
+
}
|
|
251
|
+
}, [selectedElement, handleDismiss]);
|
|
252
|
+
return (<react_native_1.View ref={containerRef} style={react_native_1.StyleSheet.absoluteFill} collapsable={false}>
|
|
253
|
+
{children}
|
|
254
|
+
|
|
255
|
+
{/* Inspector overlay - captures taps when active */}
|
|
256
|
+
<InspectorOverlay_1.InspectorOverlay isActive={isInspecting} onTapAtPosition={handleTapAtPosition}/>
|
|
257
|
+
|
|
258
|
+
{/* Highlight overlay - shows selected element */}
|
|
259
|
+
<highlight_1.HighlightOverlay selectedElement={selectedElement} onDismiss={handleDismiss} onSendToAgent={handleSendToAgent} onOpenInEditor={openInEditor}/>
|
|
260
|
+
|
|
261
|
+
{/* Floating inspector button */}
|
|
262
|
+
<InspectorButton_1.InspectorButton isInspecting={isInspecting} onToggle={toggleInspecting}/>
|
|
263
|
+
</react_native_1.View>);
|
|
264
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export interface Fiber {
|
|
2
|
+
tag: number;
|
|
3
|
+
type: string | Function | null;
|
|
4
|
+
stateNode: any;
|
|
5
|
+
child: Fiber | null;
|
|
6
|
+
sibling: Fiber | null;
|
|
7
|
+
return: Fiber | null;
|
|
8
|
+
memoizedProps: Record<string, any>;
|
|
9
|
+
memoizedState: any;
|
|
10
|
+
_debugSource?: DebugSource;
|
|
11
|
+
_debugOwner?: Fiber;
|
|
12
|
+
}
|
|
13
|
+
export interface DebugSource {
|
|
14
|
+
fileName: string;
|
|
15
|
+
lineNumber: number;
|
|
16
|
+
columnNumber?: number;
|
|
17
|
+
}
|
|
18
|
+
export interface FiberRoot {
|
|
19
|
+
current: Fiber;
|
|
20
|
+
containerInfo: any;
|
|
21
|
+
}
|
|
22
|
+
export interface ReactRenderer {
|
|
23
|
+
findFiberByHostInstance?: (instance: any) => Fiber | null;
|
|
24
|
+
bundleType?: number;
|
|
25
|
+
version?: string;
|
|
26
|
+
rendererPackageName?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface RNDevToolsHook {
|
|
29
|
+
renderers: Map<number, ReactRenderer>;
|
|
30
|
+
inject: (renderer: ReactRenderer) => number;
|
|
31
|
+
onCommitFiberRoot: (rendererID: number, root: FiberRoot) => void;
|
|
32
|
+
onCommitFiberUnmount: (rendererID: number, fiber: Fiber) => void;
|
|
33
|
+
getFiberRoots?: (rendererID: number) => Set<FiberRoot>;
|
|
34
|
+
}
|
|
35
|
+
export interface SourceLocation {
|
|
36
|
+
file: string;
|
|
37
|
+
line: number;
|
|
38
|
+
column?: number;
|
|
39
|
+
}
|
|
40
|
+
export interface RNAveMeta {
|
|
41
|
+
file: string;
|
|
42
|
+
line: number;
|
|
43
|
+
column: number;
|
|
44
|
+
}
|
|
45
|
+
export interface ComponentInfo {
|
|
46
|
+
name: string;
|
|
47
|
+
props: Record<string, any>;
|
|
48
|
+
source: SourceLocation | null;
|
|
49
|
+
}
|
|
50
|
+
export interface ExtractedContext {
|
|
51
|
+
componentName: string;
|
|
52
|
+
componentHierarchy: ComponentInfo[];
|
|
53
|
+
props: Record<string, any>;
|
|
54
|
+
source: SourceLocation | null;
|
|
55
|
+
jsxPreview: string;
|
|
56
|
+
timestamp: number;
|
|
57
|
+
boxModel?: BoxModelValues;
|
|
58
|
+
}
|
|
59
|
+
export interface Position {
|
|
60
|
+
x: number;
|
|
61
|
+
y: number;
|
|
62
|
+
}
|
|
63
|
+
export interface Bounds {
|
|
64
|
+
x: number;
|
|
65
|
+
y: number;
|
|
66
|
+
width: number;
|
|
67
|
+
height: number;
|
|
68
|
+
}
|
|
69
|
+
export interface BoxModelEdges {
|
|
70
|
+
top: number;
|
|
71
|
+
right: number;
|
|
72
|
+
bottom: number;
|
|
73
|
+
left: number;
|
|
74
|
+
}
|
|
75
|
+
export interface BoxModelValues {
|
|
76
|
+
margin: BoxModelEdges;
|
|
77
|
+
padding: BoxModelEdges;
|
|
78
|
+
border: BoxModelEdges;
|
|
79
|
+
width: number;
|
|
80
|
+
height: number;
|
|
81
|
+
}
|
|
82
|
+
export interface SelectedElement {
|
|
83
|
+
nativeTag: number;
|
|
84
|
+
componentInfo: ExtractedContext;
|
|
85
|
+
bounds?: Bounds;
|
|
86
|
+
}
|
|
87
|
+
export type AgentType = 'claude-code' | 'cursor' | 'opencode';
|
|
88
|
+
export interface GestureConfig {
|
|
89
|
+
longPressDuration: number;
|
|
90
|
+
onElementSelected: (nativeTag: number, position: Position) => void;
|
|
91
|
+
}
|
|
92
|
+
declare global {
|
|
93
|
+
var __REACT_DEVTOOLS_GLOBAL_HOOK__: RNDevToolsHook | undefined;
|
|
94
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rn-ave/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React Native Agent Visual Edit - core library for visual component selection and AI agent integration",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts"
|
|
15
|
+
},
|
|
16
|
+
"./auto": {
|
|
17
|
+
"import": "./dist/auto.js",
|
|
18
|
+
"require": "./dist/auto.js",
|
|
19
|
+
"types": "./dist/auto.d.ts"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"typesVersions": {
|
|
23
|
+
"*": {
|
|
24
|
+
"auto": ["./dist/auto.d.ts"]
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc",
|
|
32
|
+
"dev": "tsc --watch"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"react-native",
|
|
36
|
+
"ai-agent",
|
|
37
|
+
"visual-edit",
|
|
38
|
+
"claude-code",
|
|
39
|
+
"opencode",
|
|
40
|
+
"cursor"
|
|
41
|
+
],
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"react": ">=18.0.0",
|
|
45
|
+
"react-native": ">=0.60.0"
|
|
46
|
+
},
|
|
47
|
+
"peerDependenciesMeta": {
|
|
48
|
+
"react-native-gesture-handler": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/node": "^20.0.0",
|
|
54
|
+
"@types/react": "^18.0.0",
|
|
55
|
+
"@types/react-native": "^0.72.0",
|
|
56
|
+
"typescript": "^5.0.0"
|
|
57
|
+
}
|
|
58
|
+
}
|