react-native-ai-debugger 1.0.24 → 1.0.26
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 +63 -7
- package/build/core/executor.d.ts +42 -0
- package/build/core/executor.d.ts.map +1 -1
- package/build/core/executor.js +724 -0
- package/build/core/executor.js.map +1 -1
- package/build/core/index.d.ts +1 -1
- package/build/core/index.d.ts.map +1 -1
- package/build/core/index.js +3 -1
- package/build/core/index.js.map +1 -1
- package/build/index.js +228 -9
- package/build/index.js.map +1 -1
- package/package.json +1 -1
package/build/core/executor.js
CHANGED
|
@@ -415,4 +415,728 @@ export async function reloadApp() {
|
|
|
415
415
|
};
|
|
416
416
|
}
|
|
417
417
|
}
|
|
418
|
+
function formatTreeToTonl(node, indent = 0) {
|
|
419
|
+
const prefix = ' '.repeat(indent);
|
|
420
|
+
let result = `${prefix}${node.component}`;
|
|
421
|
+
// Add props inline if present
|
|
422
|
+
if (node.props && Object.keys(node.props).length > 0) {
|
|
423
|
+
const propsStr = Object.entries(node.props)
|
|
424
|
+
.map(([k, v]) => `${k}=${typeof v === 'string' ? v : JSON.stringify(v)}`)
|
|
425
|
+
.join(',');
|
|
426
|
+
result += ` (${propsStr})`;
|
|
427
|
+
}
|
|
428
|
+
// Add layout inline if present
|
|
429
|
+
if (node.layout && Object.keys(node.layout).length > 0) {
|
|
430
|
+
const layoutStr = Object.entries(node.layout)
|
|
431
|
+
.map(([k, v]) => `${k}:${v}`)
|
|
432
|
+
.join(',');
|
|
433
|
+
result += ` [${layoutStr}]`;
|
|
434
|
+
}
|
|
435
|
+
result += '\n';
|
|
436
|
+
// Recurse children
|
|
437
|
+
if (node.children && node.children.length > 0) {
|
|
438
|
+
for (const child of node.children) {
|
|
439
|
+
result += formatTreeToTonl(child, indent + 1);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return result;
|
|
443
|
+
}
|
|
444
|
+
function formatScreenLayoutToTonl(elements) {
|
|
445
|
+
const lines = ['#elements{component,path,depth,layout,id}'];
|
|
446
|
+
for (const el of elements) {
|
|
447
|
+
const layout = el.layout ? Object.entries(el.layout).map(([k, v]) => `${k}:${v}`).join(';') : '';
|
|
448
|
+
const id = el.identifiers?.testID || el.identifiers?.accessibilityLabel || '';
|
|
449
|
+
lines.push(`${el.component}|${el.path}|${el.depth}|${layout}|${id}`);
|
|
450
|
+
}
|
|
451
|
+
return lines.join('\n');
|
|
452
|
+
}
|
|
453
|
+
function formatFoundComponentsToTonl(components) {
|
|
454
|
+
const lines = ['#found{component,path,depth,key,layout}'];
|
|
455
|
+
for (const c of components) {
|
|
456
|
+
const layout = c.layout ? Object.entries(c.layout).map(([k, v]) => `${k}:${v}`).join(';') : '';
|
|
457
|
+
lines.push(`${c.component}|${c.path}|${c.depth}|${c.key || ''}|${layout}`);
|
|
458
|
+
}
|
|
459
|
+
return lines.join('\n');
|
|
460
|
+
}
|
|
461
|
+
function formatSummaryToTonl(components, total) {
|
|
462
|
+
const lines = [`#summary total=${total}`];
|
|
463
|
+
for (const c of components) {
|
|
464
|
+
lines.push(`${c.component}:${c.count}`);
|
|
465
|
+
}
|
|
466
|
+
return lines.join('\n');
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Get the React component tree from the running app.
|
|
470
|
+
* This traverses the fiber tree to extract component hierarchy with names.
|
|
471
|
+
*/
|
|
472
|
+
export async function getComponentTree(options = {}) {
|
|
473
|
+
const { maxDepth = 50, includeProps = false, includeStyles = false, hideInternals = true, format = 'tonl' } = options;
|
|
474
|
+
const expression = `
|
|
475
|
+
(function() {
|
|
476
|
+
const hook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
477
|
+
if (!hook) return { error: 'React DevTools hook not found. Make sure you are running a development build.' };
|
|
478
|
+
|
|
479
|
+
// Try to get fiber roots (renderer ID is usually 1)
|
|
480
|
+
let roots = [];
|
|
481
|
+
if (hook.getFiberRoots) {
|
|
482
|
+
roots = [...(hook.getFiberRoots(1) || [])];
|
|
483
|
+
}
|
|
484
|
+
if (roots.length === 0 && hook.renderers) {
|
|
485
|
+
// Try all renderers
|
|
486
|
+
for (const [id] of hook.renderers) {
|
|
487
|
+
const r = hook.getFiberRoots ? [...(hook.getFiberRoots(id) || [])] : [];
|
|
488
|
+
if (r.length > 0) {
|
|
489
|
+
roots = r;
|
|
490
|
+
break;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
if (roots.length === 0) return { error: 'No fiber roots found. The app may not have rendered yet.' };
|
|
495
|
+
|
|
496
|
+
const maxDepth = ${maxDepth};
|
|
497
|
+
const includeProps = ${includeProps};
|
|
498
|
+
const includeStyles = ${includeStyles};
|
|
499
|
+
const hideInternals = ${hideInternals};
|
|
500
|
+
|
|
501
|
+
// Internal RN components to hide
|
|
502
|
+
const internalPatterns = /^(RCT|RNS|Animated\\(|AnimatedComponent|VirtualizedList|CellRenderer|ScrollViewContext|PerformanceLoggerContext|RootTagContext|HeaderShownContext|HeaderHeightContext|HeaderBackContext|SafeAreaFrameContext|SafeAreaInsetsContext|VirtualizedListContext|VirtualizedListCellContextProvider|StaticContainer|DelayedFreeze|Freeze|Suspender|DebugContainer|MaybeNestedStack|SceneView|NavigationContent|PreventRemoveProvider|EnsureSingleNavigator)/;
|
|
503
|
+
|
|
504
|
+
function getComponentName(fiber) {
|
|
505
|
+
if (!fiber || !fiber.type) return null;
|
|
506
|
+
if (typeof fiber.type === 'string') return fiber.type; // Host component (View, Text, etc.)
|
|
507
|
+
return fiber.type.displayName || fiber.type.name || null;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function shouldHide(name) {
|
|
511
|
+
if (!hideInternals || !name) return false;
|
|
512
|
+
return internalPatterns.test(name);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function extractLayoutStyles(style) {
|
|
516
|
+
if (!style) return null;
|
|
517
|
+
const merged = Array.isArray(style)
|
|
518
|
+
? Object.assign({}, ...style.filter(Boolean).map(s => typeof s === 'object' ? s : {}))
|
|
519
|
+
: (typeof style === 'object' ? style : {});
|
|
520
|
+
|
|
521
|
+
const layout = {};
|
|
522
|
+
const layoutKeys = [
|
|
523
|
+
'padding', 'paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight',
|
|
524
|
+
'paddingHorizontal', 'paddingVertical',
|
|
525
|
+
'margin', 'marginTop', 'marginBottom', 'marginLeft', 'marginRight',
|
|
526
|
+
'marginHorizontal', 'marginVertical',
|
|
527
|
+
'width', 'height', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight',
|
|
528
|
+
'flex', 'flexDirection', 'flexWrap', 'flexGrow', 'flexShrink',
|
|
529
|
+
'justifyContent', 'alignItems', 'alignSelf', 'alignContent',
|
|
530
|
+
'position', 'top', 'bottom', 'left', 'right',
|
|
531
|
+
'gap', 'rowGap', 'columnGap',
|
|
532
|
+
'borderWidth', 'borderTopWidth', 'borderBottomWidth', 'borderLeftWidth', 'borderRightWidth'
|
|
533
|
+
];
|
|
534
|
+
|
|
535
|
+
for (const key of layoutKeys) {
|
|
536
|
+
if (merged[key] !== undefined) layout[key] = merged[key];
|
|
537
|
+
}
|
|
538
|
+
return Object.keys(layout).length > 0 ? layout : null;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function walkFiber(fiber, depth) {
|
|
542
|
+
if (!fiber || depth > maxDepth) return null;
|
|
543
|
+
|
|
544
|
+
const name = getComponentName(fiber);
|
|
545
|
+
|
|
546
|
+
// Skip anonymous/internal components unless they have meaningful children
|
|
547
|
+
if (!name || shouldHide(name)) {
|
|
548
|
+
// Still traverse children
|
|
549
|
+
let child = fiber.child;
|
|
550
|
+
const children = [];
|
|
551
|
+
while (child) {
|
|
552
|
+
const childResult = walkFiber(child, depth);
|
|
553
|
+
if (childResult) children.push(childResult);
|
|
554
|
+
child = child.sibling;
|
|
555
|
+
}
|
|
556
|
+
// Return first meaningful child or null
|
|
557
|
+
return children.length === 1 ? children[0] : (children.length > 1 ? { component: '(Fragment)', children } : null);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const node = { component: name };
|
|
561
|
+
|
|
562
|
+
// Include props if requested (excluding children and style for cleaner output)
|
|
563
|
+
if (includeProps && fiber.memoizedProps) {
|
|
564
|
+
const props = {};
|
|
565
|
+
for (const key of Object.keys(fiber.memoizedProps)) {
|
|
566
|
+
if (key === 'children' || key === 'style') continue;
|
|
567
|
+
const val = fiber.memoizedProps[key];
|
|
568
|
+
if (typeof val === 'function') {
|
|
569
|
+
props[key] = '[Function]';
|
|
570
|
+
} else if (typeof val === 'object' && val !== null) {
|
|
571
|
+
props[key] = Array.isArray(val) ? '[Array]' : '[Object]';
|
|
572
|
+
} else {
|
|
573
|
+
props[key] = val;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
if (Object.keys(props).length > 0) node.props = props;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Include layout styles if requested
|
|
580
|
+
if (includeStyles && fiber.memoizedProps?.style) {
|
|
581
|
+
const layout = extractLayoutStyles(fiber.memoizedProps.style);
|
|
582
|
+
if (layout) node.layout = layout;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Traverse children
|
|
586
|
+
let child = fiber.child;
|
|
587
|
+
const children = [];
|
|
588
|
+
while (child) {
|
|
589
|
+
const childResult = walkFiber(child, depth + 1);
|
|
590
|
+
if (childResult) children.push(childResult);
|
|
591
|
+
child = child.sibling;
|
|
592
|
+
}
|
|
593
|
+
if (children.length > 0) node.children = children;
|
|
594
|
+
|
|
595
|
+
return node;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const tree = walkFiber(roots[0].current, 0);
|
|
599
|
+
return { tree };
|
|
600
|
+
})()
|
|
601
|
+
`;
|
|
602
|
+
const result = await executeInApp(expression, false);
|
|
603
|
+
// Apply TONL formatting if requested
|
|
604
|
+
if (format === 'tonl' && result.success && result.result) {
|
|
605
|
+
try {
|
|
606
|
+
const parsed = JSON.parse(result.result);
|
|
607
|
+
if (parsed.tree) {
|
|
608
|
+
const tonl = formatTreeToTonl(parsed.tree);
|
|
609
|
+
return { success: true, result: tonl };
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
catch {
|
|
613
|
+
// If parsing fails, return original result
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return result;
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Get layout styles for all components on the current screen.
|
|
620
|
+
* Useful for verifying layout without screenshots.
|
|
621
|
+
*/
|
|
622
|
+
export async function getScreenLayout(options = {}) {
|
|
623
|
+
const { maxDepth = 60, componentsOnly = false, shortPath = true, summary = false, format = 'tonl' } = options;
|
|
624
|
+
const expression = `
|
|
625
|
+
(function() {
|
|
626
|
+
const hook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
627
|
+
if (!hook) return { error: 'React DevTools hook not found.' };
|
|
628
|
+
|
|
629
|
+
let roots = [];
|
|
630
|
+
if (hook.getFiberRoots) {
|
|
631
|
+
roots = [...(hook.getFiberRoots(1) || [])];
|
|
632
|
+
}
|
|
633
|
+
if (roots.length === 0 && hook.renderers) {
|
|
634
|
+
for (const [id] of hook.renderers) {
|
|
635
|
+
const r = hook.getFiberRoots ? [...(hook.getFiberRoots(id) || [])] : [];
|
|
636
|
+
if (r.length > 0) { roots = r; break; }
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
if (roots.length === 0) return { error: 'No fiber roots found.' };
|
|
640
|
+
|
|
641
|
+
const maxDepth = ${maxDepth};
|
|
642
|
+
const componentsOnly = ${componentsOnly};
|
|
643
|
+
const shortPath = ${shortPath};
|
|
644
|
+
const summaryMode = ${summary};
|
|
645
|
+
const pathSegments = 3; // Number of path segments to show in shortPath mode
|
|
646
|
+
|
|
647
|
+
function getComponentName(fiber) {
|
|
648
|
+
if (!fiber || !fiber.type) return null;
|
|
649
|
+
if (typeof fiber.type === 'string') return fiber.type;
|
|
650
|
+
return fiber.type.displayName || fiber.type.name || null;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function isHostComponent(fiber) {
|
|
654
|
+
return typeof fiber?.type === 'string';
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function formatPath(pathArray) {
|
|
658
|
+
if (!shortPath || pathArray.length <= pathSegments) {
|
|
659
|
+
return pathArray.join(' > ');
|
|
660
|
+
}
|
|
661
|
+
return '... > ' + pathArray.slice(-pathSegments).join(' > ');
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function extractAllStyles(style) {
|
|
665
|
+
if (!style) return null;
|
|
666
|
+
const merged = Array.isArray(style)
|
|
667
|
+
? Object.assign({}, ...style.filter(Boolean).map(s => typeof s === 'object' ? s : {}))
|
|
668
|
+
: (typeof style === 'object' ? style : {});
|
|
669
|
+
return Object.keys(merged).length > 0 ? merged : null;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function extractLayoutStyles(style) {
|
|
673
|
+
if (!style) return null;
|
|
674
|
+
const merged = Array.isArray(style)
|
|
675
|
+
? Object.assign({}, ...style.filter(Boolean).map(s => typeof s === 'object' ? s : {}))
|
|
676
|
+
: (typeof style === 'object' ? style : {});
|
|
677
|
+
|
|
678
|
+
const layout = {};
|
|
679
|
+
const layoutKeys = [
|
|
680
|
+
'padding', 'paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight',
|
|
681
|
+
'paddingHorizontal', 'paddingVertical',
|
|
682
|
+
'margin', 'marginTop', 'marginBottom', 'marginLeft', 'marginRight',
|
|
683
|
+
'marginHorizontal', 'marginVertical',
|
|
684
|
+
'width', 'height', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight',
|
|
685
|
+
'flex', 'flexDirection', 'flexWrap', 'flexGrow', 'flexShrink',
|
|
686
|
+
'justifyContent', 'alignItems', 'alignSelf', 'alignContent',
|
|
687
|
+
'position', 'top', 'bottom', 'left', 'right',
|
|
688
|
+
'gap', 'rowGap', 'columnGap',
|
|
689
|
+
'borderWidth', 'borderTopWidth', 'borderBottomWidth', 'borderLeftWidth', 'borderRightWidth',
|
|
690
|
+
'backgroundColor', 'borderColor', 'borderRadius'
|
|
691
|
+
];
|
|
692
|
+
|
|
693
|
+
for (const key of layoutKeys) {
|
|
694
|
+
if (merged[key] !== undefined) layout[key] = merged[key];
|
|
695
|
+
}
|
|
696
|
+
return Object.keys(layout).length > 0 ? layout : null;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const elements = [];
|
|
700
|
+
|
|
701
|
+
function walkFiber(fiber, depth, path) {
|
|
702
|
+
if (!fiber || depth > maxDepth) return;
|
|
703
|
+
|
|
704
|
+
const name = getComponentName(fiber);
|
|
705
|
+
const isHost = isHostComponent(fiber);
|
|
706
|
+
|
|
707
|
+
// Include host components (View, Text, etc.) or named components
|
|
708
|
+
if (name && (!componentsOnly || !isHost)) {
|
|
709
|
+
const style = fiber.memoizedProps?.style;
|
|
710
|
+
const layout = extractLayoutStyles(style);
|
|
711
|
+
|
|
712
|
+
// Get text content if it's a Text component
|
|
713
|
+
let textContent = null;
|
|
714
|
+
if (name === 'Text' || name === 'RCTText') {
|
|
715
|
+
const children = fiber.memoizedProps?.children;
|
|
716
|
+
if (typeof children === 'string') textContent = children;
|
|
717
|
+
else if (typeof children === 'number') textContent = String(children);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
const element = {
|
|
721
|
+
component: name,
|
|
722
|
+
path: formatPath(path),
|
|
723
|
+
depth
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
if (layout) element.layout = layout;
|
|
727
|
+
if (textContent) element.text = textContent.slice(0, 100);
|
|
728
|
+
|
|
729
|
+
// Include key props for identification
|
|
730
|
+
if (fiber.memoizedProps) {
|
|
731
|
+
const identifiers = {};
|
|
732
|
+
if (fiber.memoizedProps.testID) identifiers.testID = fiber.memoizedProps.testID;
|
|
733
|
+
if (fiber.memoizedProps.accessibilityLabel) identifiers.accessibilityLabel = fiber.memoizedProps.accessibilityLabel;
|
|
734
|
+
if (fiber.memoizedProps.nativeID) identifiers.nativeID = fiber.memoizedProps.nativeID;
|
|
735
|
+
if (fiber.key) identifiers.key = fiber.key;
|
|
736
|
+
if (Object.keys(identifiers).length > 0) element.identifiers = identifiers;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
elements.push(element);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// Traverse children
|
|
743
|
+
let child = fiber.child;
|
|
744
|
+
while (child) {
|
|
745
|
+
const childName = getComponentName(child);
|
|
746
|
+
walkFiber(child, depth + 1, childName ? [...path, childName] : path);
|
|
747
|
+
child = child.sibling;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
walkFiber(roots[0].current, 0, []);
|
|
752
|
+
|
|
753
|
+
// Summary mode: return counts by component name
|
|
754
|
+
if (summaryMode) {
|
|
755
|
+
const counts = {};
|
|
756
|
+
for (const el of elements) {
|
|
757
|
+
counts[el.component] = (counts[el.component] || 0) + 1;
|
|
758
|
+
}
|
|
759
|
+
// Sort by count descending
|
|
760
|
+
const sorted = Object.entries(counts)
|
|
761
|
+
.sort((a, b) => b[1] - a[1])
|
|
762
|
+
.map(([name, count]) => ({ component: name, count }));
|
|
763
|
+
return {
|
|
764
|
+
totalElements: elements.length,
|
|
765
|
+
uniqueComponents: sorted.length,
|
|
766
|
+
components: sorted
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
return {
|
|
771
|
+
totalElements: elements.length,
|
|
772
|
+
elements: elements
|
|
773
|
+
};
|
|
774
|
+
})()
|
|
775
|
+
`;
|
|
776
|
+
const result = await executeInApp(expression, false);
|
|
777
|
+
// Apply TONL formatting if requested
|
|
778
|
+
if (format === 'tonl' && result.success && result.result) {
|
|
779
|
+
try {
|
|
780
|
+
const parsed = JSON.parse(result.result);
|
|
781
|
+
if (parsed.components) {
|
|
782
|
+
// Summary mode
|
|
783
|
+
const tonl = formatSummaryToTonl(parsed.components, parsed.totalElements);
|
|
784
|
+
return { success: true, result: tonl };
|
|
785
|
+
}
|
|
786
|
+
else if (parsed.elements) {
|
|
787
|
+
// Full element list
|
|
788
|
+
const tonl = formatScreenLayoutToTonl(parsed.elements);
|
|
789
|
+
return { success: true, result: tonl };
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
catch {
|
|
793
|
+
// If parsing fails, return original result
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
return result;
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Inspect a specific component by name, returning its props, state, and layout.
|
|
800
|
+
*/
|
|
801
|
+
export async function inspectComponent(componentName, options = {}) {
|
|
802
|
+
const { index = 0, includeState = true, includeChildren = false, shortPath = true, simplifyHooks = true } = options;
|
|
803
|
+
const escapedName = componentName.replace(/'/g, "\\'");
|
|
804
|
+
const expression = `
|
|
805
|
+
(function() {
|
|
806
|
+
const hook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
807
|
+
if (!hook) return { error: 'React DevTools hook not found.' };
|
|
808
|
+
|
|
809
|
+
let roots = [];
|
|
810
|
+
if (hook.getFiberRoots) {
|
|
811
|
+
roots = [...(hook.getFiberRoots(1) || [])];
|
|
812
|
+
}
|
|
813
|
+
if (roots.length === 0 && hook.renderers) {
|
|
814
|
+
for (const [id] of hook.renderers) {
|
|
815
|
+
const r = hook.getFiberRoots ? [...(hook.getFiberRoots(id) || [])] : [];
|
|
816
|
+
if (r.length > 0) { roots = r; break; }
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
if (roots.length === 0) return { error: 'No fiber roots found.' };
|
|
820
|
+
|
|
821
|
+
const targetName = '${escapedName}';
|
|
822
|
+
const targetIndex = ${index};
|
|
823
|
+
const includeState = ${includeState};
|
|
824
|
+
const includeChildren = ${includeChildren};
|
|
825
|
+
const shortPath = ${shortPath};
|
|
826
|
+
const simplifyHooks = ${simplifyHooks};
|
|
827
|
+
const pathSegments = 3;
|
|
828
|
+
|
|
829
|
+
function getComponentName(fiber) {
|
|
830
|
+
if (!fiber || !fiber.type) return null;
|
|
831
|
+
if (typeof fiber.type === 'string') return fiber.type;
|
|
832
|
+
return fiber.type.displayName || fiber.type.name || null;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
function formatPath(pathArray) {
|
|
836
|
+
if (!shortPath || pathArray.length <= pathSegments) {
|
|
837
|
+
return pathArray.join(' > ');
|
|
838
|
+
}
|
|
839
|
+
return '... > ' + pathArray.slice(-pathSegments).join(' > ');
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
function extractStyles(style) {
|
|
843
|
+
if (!style) return null;
|
|
844
|
+
const merged = Array.isArray(style)
|
|
845
|
+
? Object.assign({}, ...style.filter(Boolean).map(s => typeof s === 'object' ? s : {}))
|
|
846
|
+
: (typeof style === 'object' ? style : {});
|
|
847
|
+
return Object.keys(merged).length > 0 ? merged : null;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function serializeValue(val, depth = 0) {
|
|
851
|
+
if (depth > 3) return '[Max depth]';
|
|
852
|
+
if (val === null) return null;
|
|
853
|
+
if (val === undefined) return undefined;
|
|
854
|
+
if (typeof val === 'function') return '[Function]';
|
|
855
|
+
if (typeof val !== 'object') return val;
|
|
856
|
+
if (Array.isArray(val)) {
|
|
857
|
+
if (val.length > 10) return '[Array(' + val.length + ')]';
|
|
858
|
+
return val.map(v => serializeValue(v, depth + 1));
|
|
859
|
+
}
|
|
860
|
+
// Object
|
|
861
|
+
const keys = Object.keys(val);
|
|
862
|
+
if (keys.length > 20) return '[Object(' + keys.length + ' keys)]';
|
|
863
|
+
const result = {};
|
|
864
|
+
for (const k of keys) {
|
|
865
|
+
result[k] = serializeValue(val[k], depth + 1);
|
|
866
|
+
}
|
|
867
|
+
return result;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
function getChildNames(fiber) {
|
|
871
|
+
const names = [];
|
|
872
|
+
let child = fiber?.child;
|
|
873
|
+
while (child && names.length < 20) {
|
|
874
|
+
const name = getComponentName(child);
|
|
875
|
+
if (name) names.push(name);
|
|
876
|
+
child = child.sibling;
|
|
877
|
+
}
|
|
878
|
+
return names;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
const matches = [];
|
|
882
|
+
|
|
883
|
+
function findComponent(fiber, path) {
|
|
884
|
+
if (!fiber) return;
|
|
885
|
+
|
|
886
|
+
const name = getComponentName(fiber);
|
|
887
|
+
if (name === targetName) {
|
|
888
|
+
matches.push({ fiber, path: [...path, name] });
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
let child = fiber.child;
|
|
892
|
+
while (child) {
|
|
893
|
+
const childName = getComponentName(child);
|
|
894
|
+
findComponent(child, childName ? [...path, childName] : path);
|
|
895
|
+
child = child.sibling;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
findComponent(roots[0].current, []);
|
|
900
|
+
|
|
901
|
+
if (matches.length === 0) {
|
|
902
|
+
return { error: 'Component "' + targetName + '" not found in the component tree.' };
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
if (targetIndex >= matches.length) {
|
|
906
|
+
return { error: 'Component "' + targetName + '" found ' + matches.length + ' times, but index ' + targetIndex + ' requested.' };
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
const { fiber, path } = matches[targetIndex];
|
|
910
|
+
|
|
911
|
+
const result = {
|
|
912
|
+
component: targetName,
|
|
913
|
+
path: formatPath(path),
|
|
914
|
+
instancesFound: matches.length,
|
|
915
|
+
instanceIndex: targetIndex
|
|
916
|
+
};
|
|
917
|
+
|
|
918
|
+
// Props (excluding children)
|
|
919
|
+
if (fiber.memoizedProps) {
|
|
920
|
+
const props = {};
|
|
921
|
+
for (const key of Object.keys(fiber.memoizedProps)) {
|
|
922
|
+
if (key === 'children') continue;
|
|
923
|
+
props[key] = serializeValue(fiber.memoizedProps[key]);
|
|
924
|
+
}
|
|
925
|
+
result.props = props;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// Style separately for clarity
|
|
929
|
+
if (fiber.memoizedProps?.style) {
|
|
930
|
+
result.style = extractStyles(fiber.memoizedProps.style);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// State (for hooks, this is a linked list)
|
|
934
|
+
if (includeState && fiber.memoizedState) {
|
|
935
|
+
// Simplified hook value serialization
|
|
936
|
+
function serializeHookValue(val, depth = 0) {
|
|
937
|
+
if (depth > 2) return '[...]';
|
|
938
|
+
if (val === null || val === undefined) return val;
|
|
939
|
+
if (typeof val === 'function') return '[Function]';
|
|
940
|
+
if (typeof val !== 'object') return val;
|
|
941
|
+
// Skip React internal structures (effects, refs with destroy/create)
|
|
942
|
+
if (val.create && val.destroy !== undefined) return '[Effect]';
|
|
943
|
+
if (val.inst && val.deps) return '[Effect]';
|
|
944
|
+
if (val.current !== undefined && Object.keys(val).length === 1) {
|
|
945
|
+
// Ref object - just show current value
|
|
946
|
+
return { current: serializeHookValue(val.current, depth + 1) };
|
|
947
|
+
}
|
|
948
|
+
if (Array.isArray(val)) {
|
|
949
|
+
if (val.length > 5) return '[Array(' + val.length + ')]';
|
|
950
|
+
return val.slice(0, 5).map(v => serializeHookValue(v, depth + 1));
|
|
951
|
+
}
|
|
952
|
+
const keys = Object.keys(val);
|
|
953
|
+
if (keys.length > 10) return '[Object(' + keys.length + ' keys)]';
|
|
954
|
+
const result = {};
|
|
955
|
+
for (const k of keys.slice(0, 10)) {
|
|
956
|
+
result[k] = serializeHookValue(val[k], depth + 1);
|
|
957
|
+
}
|
|
958
|
+
return result;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// For function components with hooks
|
|
962
|
+
const states = [];
|
|
963
|
+
let state = fiber.memoizedState;
|
|
964
|
+
let hookIndex = 0;
|
|
965
|
+
while (state && hookIndex < 20) {
|
|
966
|
+
if (state.memoizedState !== undefined) {
|
|
967
|
+
const hookVal = simplifyHooks
|
|
968
|
+
? serializeHookValue(state.memoizedState)
|
|
969
|
+
: serializeValue(state.memoizedState);
|
|
970
|
+
// Skip effect hooks in simplified mode
|
|
971
|
+
if (!simplifyHooks || (hookVal !== '[Effect]' && hookVal !== undefined)) {
|
|
972
|
+
states.push({
|
|
973
|
+
hookIndex,
|
|
974
|
+
value: hookVal
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
state = state.next;
|
|
979
|
+
hookIndex++;
|
|
980
|
+
}
|
|
981
|
+
if (states.length > 0) result.hooks = states;
|
|
982
|
+
|
|
983
|
+
// For class components, memoizedState is the state object directly
|
|
984
|
+
if (states.length === 0 && typeof fiber.memoizedState === 'object') {
|
|
985
|
+
result.state = serializeValue(fiber.memoizedState);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// Direct children names
|
|
990
|
+
if (includeChildren) {
|
|
991
|
+
result.children = getChildNames(fiber);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
return result;
|
|
995
|
+
})()
|
|
996
|
+
`;
|
|
997
|
+
return executeInApp(expression, false);
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* Find all components matching a name pattern and return summary info.
|
|
1001
|
+
*/
|
|
1002
|
+
export async function findComponents(pattern, options = {}) {
|
|
1003
|
+
const { maxResults = 20, includeLayout = false, shortPath = true, summary = false, format = 'tonl' } = options;
|
|
1004
|
+
const escapedPattern = pattern.replace(/'/g, "\\'").replace(/\\/g, "\\\\");
|
|
1005
|
+
const expression = `
|
|
1006
|
+
(function() {
|
|
1007
|
+
const hook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
1008
|
+
if (!hook) return { error: 'React DevTools hook not found.' };
|
|
1009
|
+
|
|
1010
|
+
let roots = [];
|
|
1011
|
+
if (hook.getFiberRoots) {
|
|
1012
|
+
roots = [...(hook.getFiberRoots(1) || [])];
|
|
1013
|
+
}
|
|
1014
|
+
if (roots.length === 0 && hook.renderers) {
|
|
1015
|
+
for (const [id] of hook.renderers) {
|
|
1016
|
+
const r = hook.getFiberRoots ? [...(hook.getFiberRoots(id) || [])] : [];
|
|
1017
|
+
if (r.length > 0) { roots = r; break; }
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
if (roots.length === 0) return { error: 'No fiber roots found.' };
|
|
1021
|
+
|
|
1022
|
+
const pattern = '${escapedPattern}';
|
|
1023
|
+
const regex = new RegExp(pattern, 'i');
|
|
1024
|
+
const maxResults = ${maxResults};
|
|
1025
|
+
const includeLayout = ${includeLayout};
|
|
1026
|
+
const shortPath = ${shortPath};
|
|
1027
|
+
const summaryMode = ${summary};
|
|
1028
|
+
const pathSegments = 3;
|
|
1029
|
+
|
|
1030
|
+
function getComponentName(fiber) {
|
|
1031
|
+
if (!fiber || !fiber.type) return null;
|
|
1032
|
+
if (typeof fiber.type === 'string') return fiber.type;
|
|
1033
|
+
return fiber.type.displayName || fiber.type.name || null;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
function formatPath(pathArray) {
|
|
1037
|
+
if (!shortPath || pathArray.length <= pathSegments) {
|
|
1038
|
+
return pathArray.join(' > ');
|
|
1039
|
+
}
|
|
1040
|
+
return '... > ' + pathArray.slice(-pathSegments).join(' > ');
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
function extractLayoutStyles(style) {
|
|
1044
|
+
if (!style) return null;
|
|
1045
|
+
const merged = Array.isArray(style)
|
|
1046
|
+
? Object.assign({}, ...style.filter(Boolean).map(s => typeof s === 'object' ? s : {}))
|
|
1047
|
+
: (typeof style === 'object' ? style : {});
|
|
1048
|
+
|
|
1049
|
+
const layout = {};
|
|
1050
|
+
const keys = ['padding', 'paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight',
|
|
1051
|
+
'paddingHorizontal', 'paddingVertical', 'margin', 'marginTop', 'marginBottom',
|
|
1052
|
+
'marginLeft', 'marginRight', 'marginHorizontal', 'marginVertical',
|
|
1053
|
+
'width', 'height', 'flex', 'flexDirection', 'justifyContent', 'alignItems'];
|
|
1054
|
+
for (const k of keys) {
|
|
1055
|
+
if (merged[k] !== undefined) layout[k] = merged[k];
|
|
1056
|
+
}
|
|
1057
|
+
return Object.keys(layout).length > 0 ? layout : null;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
const results = [];
|
|
1061
|
+
|
|
1062
|
+
function search(fiber, path, depth) {
|
|
1063
|
+
if (!fiber || results.length >= maxResults) return;
|
|
1064
|
+
|
|
1065
|
+
const name = getComponentName(fiber);
|
|
1066
|
+
if (name && regex.test(name)) {
|
|
1067
|
+
const entry = {
|
|
1068
|
+
component: name,
|
|
1069
|
+
path: formatPath(path),
|
|
1070
|
+
depth
|
|
1071
|
+
};
|
|
1072
|
+
|
|
1073
|
+
if (fiber.memoizedProps?.testID) entry.testID = fiber.memoizedProps.testID;
|
|
1074
|
+
if (fiber.key) entry.key = fiber.key;
|
|
1075
|
+
|
|
1076
|
+
if (includeLayout && fiber.memoizedProps?.style) {
|
|
1077
|
+
const layout = extractLayoutStyles(fiber.memoizedProps.style);
|
|
1078
|
+
if (layout) entry.layout = layout;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
results.push(entry);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
let child = fiber.child;
|
|
1085
|
+
while (child && results.length < maxResults) {
|
|
1086
|
+
const childName = getComponentName(child);
|
|
1087
|
+
search(child, childName ? [...path, childName] : path, depth + 1);
|
|
1088
|
+
child = child.sibling;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
search(roots[0].current, [], 0);
|
|
1093
|
+
|
|
1094
|
+
// Summary mode: just return counts by component name
|
|
1095
|
+
if (summaryMode) {
|
|
1096
|
+
const counts = {};
|
|
1097
|
+
for (const r of results) {
|
|
1098
|
+
counts[r.component] = (counts[r.component] || 0) + 1;
|
|
1099
|
+
}
|
|
1100
|
+
const sorted = Object.entries(counts)
|
|
1101
|
+
.sort((a, b) => b[1] - a[1])
|
|
1102
|
+
.map(([name, count]) => ({ component: name, count }));
|
|
1103
|
+
return {
|
|
1104
|
+
pattern,
|
|
1105
|
+
totalMatches: results.length,
|
|
1106
|
+
uniqueComponents: sorted.length,
|
|
1107
|
+
components: sorted
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
return {
|
|
1112
|
+
pattern,
|
|
1113
|
+
found: results.length,
|
|
1114
|
+
components: results
|
|
1115
|
+
};
|
|
1116
|
+
})()
|
|
1117
|
+
`;
|
|
1118
|
+
const result = await executeInApp(expression, false);
|
|
1119
|
+
// Apply TONL formatting if requested
|
|
1120
|
+
if (format === 'tonl' && result.success && result.result) {
|
|
1121
|
+
try {
|
|
1122
|
+
const parsed = JSON.parse(result.result);
|
|
1123
|
+
if (parsed.components) {
|
|
1124
|
+
if (parsed.totalMatches !== undefined) {
|
|
1125
|
+
// Summary mode
|
|
1126
|
+
const tonl = formatSummaryToTonl(parsed.components, parsed.totalMatches);
|
|
1127
|
+
return { success: true, result: `pattern: ${parsed.pattern}\n${tonl}` };
|
|
1128
|
+
}
|
|
1129
|
+
else {
|
|
1130
|
+
// Full list mode
|
|
1131
|
+
const tonl = formatFoundComponentsToTonl(parsed.components);
|
|
1132
|
+
return { success: true, result: `pattern: ${parsed.pattern}\nfound: ${parsed.found}\n${tonl}` };
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
catch {
|
|
1137
|
+
// If parsing fails, return original result
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
return result;
|
|
1141
|
+
}
|
|
418
1142
|
//# sourceMappingURL=executor.js.map
|