rn-studio 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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +116 -0
  3. package/babel-plugin/index.js +68 -0
  4. package/bin/rn-studio-server.js +98 -0
  5. package/dist/StudioProvider.d.ts +25 -0
  6. package/dist/StudioProvider.d.ts.map +1 -0
  7. package/dist/StudioProvider.js +131 -0
  8. package/dist/StudioProvider.js.map +1 -0
  9. package/dist/ast/AstEngine.d.ts +16 -0
  10. package/dist/ast/AstEngine.d.ts.map +1 -0
  11. package/dist/ast/AstEngine.js +153 -0
  12. package/dist/ast/AstEngine.js.map +1 -0
  13. package/dist/bridge/WebSocketBridge.d.ts +24 -0
  14. package/dist/bridge/WebSocketBridge.d.ts.map +1 -0
  15. package/dist/bridge/WebSocketBridge.js +94 -0
  16. package/dist/bridge/WebSocketBridge.js.map +1 -0
  17. package/dist/components/ComponentTree.d.ts +9 -0
  18. package/dist/components/ComponentTree.d.ts.map +1 -0
  19. package/dist/components/ComponentTree.js +65 -0
  20. package/dist/components/ComponentTree.js.map +1 -0
  21. package/dist/components/FloatingBubble.d.ts +8 -0
  22. package/dist/components/FloatingBubble.d.ts.map +1 -0
  23. package/dist/components/FloatingBubble.js +205 -0
  24. package/dist/components/FloatingBubble.js.map +1 -0
  25. package/dist/components/InspectorPanel.d.ts +9 -0
  26. package/dist/components/InspectorPanel.d.ts.map +1 -0
  27. package/dist/components/InspectorPanel.js +242 -0
  28. package/dist/components/InspectorPanel.js.map +1 -0
  29. package/dist/components/SelectionOverlay.d.ts +15 -0
  30. package/dist/components/SelectionOverlay.d.ts.map +1 -0
  31. package/dist/components/SelectionOverlay.js +152 -0
  32. package/dist/components/SelectionOverlay.js.map +1 -0
  33. package/dist/components/StyleEditor.d.ts +10 -0
  34. package/dist/components/StyleEditor.d.ts.map +1 -0
  35. package/dist/components/StyleEditor.js +144 -0
  36. package/dist/components/StyleEditor.js.map +1 -0
  37. package/dist/context/StudioContext.d.ts +7 -0
  38. package/dist/context/StudioContext.d.ts.map +1 -0
  39. package/dist/context/StudioContext.js +10 -0
  40. package/dist/context/StudioContext.js.map +1 -0
  41. package/dist/index.d.ts +24 -0
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +34 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/types.d.ts +95 -0
  46. package/dist/types.d.ts.map +1 -0
  47. package/dist/types.js +10 -0
  48. package/dist/types.js.map +1 -0
  49. package/dist/utils/fiberWalker.d.ts +36 -0
  50. package/dist/utils/fiberWalker.d.ts.map +1 -0
  51. package/dist/utils/fiberWalker.js +122 -0
  52. package/dist/utils/fiberWalker.js.map +1 -0
  53. package/dist/utils/measureComponent.d.ts +15 -0
  54. package/dist/utils/measureComponent.d.ts.map +1 -0
  55. package/dist/utils/measureComponent.js +28 -0
  56. package/dist/utils/measureComponent.js.map +1 -0
  57. package/package.json +72 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dgutierrezd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,116 @@
1
+ # rn-studio
2
+
3
+ > Live UI editor for React Native. Inspect and edit any component directly inside the iOS Simulator or Android Emulator — changes are written to your source code automatically.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/rn-studio.svg)](https://www.npmjs.com/package/rn-studio)
6
+ [![license](https://img.shields.io/npm/l/rn-studio.svg)](https://github.com/dgutierrezd/rn-studio/blob/main/LICENSE)
7
+ [![platform](https://img.shields.io/badge/platform-iOS%20%7C%20Android-blue.svg)](https://github.com/dgutierrezd/rn-studio)
8
+
9
+ ## What it does
10
+
11
+ rn-studio adds a floating bubble to your app in DEV mode. Tap it, tap any component, and a panel appears showing all its styles and the internal component tree. Edit a value — the change is written directly to your source file and Metro Fast Refresh updates the UI instantly. Zero impact on production.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm install rn-studio
17
+ ```
18
+
19
+ Peer dependencies:
20
+
21
+ ```bash
22
+ npm install react-native-gesture-handler react-native-reanimated react-native-haptic-feedback react-native-safe-area-context
23
+ ```
24
+
25
+ ## Setup
26
+
27
+ ### 1. Add the babel plugin
28
+
29
+ ```js
30
+ // babel.config.js
31
+ module.exports = {
32
+ presets: ['module:metro-react-native-babel-preset'],
33
+ plugins: [
34
+ ...(process.env.NODE_ENV !== 'production' ? ['rn-studio/babel-plugin'] : []),
35
+ ],
36
+ };
37
+ ```
38
+
39
+ ### 2. Start the server (alongside Metro)
40
+
41
+ ```bash
42
+ # Terminal 1
43
+ npx react-native start
44
+
45
+ # Terminal 2
46
+ npm run studio
47
+ ```
48
+
49
+ Add to your app's `package.json`:
50
+
51
+ ```json
52
+ {
53
+ "scripts": {
54
+ "studio": "rn-studio-server"
55
+ }
56
+ }
57
+ ```
58
+
59
+ ### 3. Wrap your App.tsx
60
+
61
+ ```tsx
62
+ import { StudioProvider } from 'rn-studio';
63
+
64
+ export default function App() {
65
+ return (
66
+ <StudioProvider enabled={__DEV__} bubblePosition="bottom-right">
67
+ <YourApp />
68
+ </StudioProvider>
69
+ );
70
+ }
71
+ ```
72
+
73
+ ## How it works
74
+
75
+ | Layer | What it does |
76
+ |---|---|
77
+ | Babel plugin | Annotates every JSX element with file + line metadata at compile time |
78
+ | Floating bubble | Toggles selection mode on tap |
79
+ | Selection overlay | Tap any component to inspect it |
80
+ | Inspector panel | Shows styles, component tree, and editable props |
81
+ | WebSocket bridge | Sends changes from the app to the local CLI server |
82
+ | AST engine | Locates and rewrites the exact prop in your source file |
83
+ | Metro Fast Refresh | Picks up the file change and updates the UI |
84
+
85
+ ## API
86
+
87
+ ### `<StudioProvider>` props
88
+
89
+ | Prop | Type | Default | Description |
90
+ |---|---|---|---|
91
+ | `enabled` | `boolean` | `false` | Show the studio UI |
92
+ | `serverPort` | `number` | `7878` | CLI server port |
93
+ | `bubblePosition` | `'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left'` | `'bottom-right'` | Bubble starting corner |
94
+ | `theme` | `'dark' \| 'light'` | `'dark'` | Panel theme |
95
+
96
+ ### `useStudio()`
97
+
98
+ ```ts
99
+ const {
100
+ isActive,
101
+ isSelecting,
102
+ selectedComponent,
103
+ toggleActive,
104
+ selectComponent,
105
+ clearSelection,
106
+ updateStyle,
107
+ } = useStudio();
108
+ ```
109
+
110
+ ## Contributing
111
+
112
+ Pull requests are welcome. For major changes, please open an issue first.
113
+
114
+ ## License
115
+
116
+ MIT © [dgutierrezd](https://github.com/dgutierrezd)
@@ -0,0 +1,68 @@
1
+ /**
2
+ * babel-plugin-rn-studio
3
+ *
4
+ * Instruments every JSX element at compile time by injecting a
5
+ * `__rnStudioSource` prop that carries source location metadata.
6
+ *
7
+ * The metadata is later used at runtime by the rn-studio overlay to
8
+ * resolve any on-screen component back to its exact location in the
9
+ * source file, so the AST engine can rewrite it.
10
+ *
11
+ * Usage (consumer app babel.config.js):
12
+ *
13
+ * plugins: [
14
+ * ...(process.env.NODE_ENV !== 'production' ? ['rn-studio/babel-plugin'] : [])
15
+ * ]
16
+ */
17
+ module.exports = function rnStudioBabelPlugin({ types: t }) {
18
+ return {
19
+ name: 'babel-plugin-rn-studio',
20
+ visitor: {
21
+ JSXOpeningElement(path, state) {
22
+ // Never instrument in production builds.
23
+ if (process.env.NODE_ENV === 'production') return;
24
+
25
+ const filename = state.filename || 'unknown';
26
+ const { line, column } = (path.node.loc && path.node.loc.start) || {
27
+ line: 0,
28
+ column: 0,
29
+ };
30
+
31
+ const nameNode = path.node.name;
32
+ let componentName = 'Unknown';
33
+ if (t.isJSXIdentifier(nameNode)) {
34
+ componentName = nameNode.name;
35
+ } else if (t.isJSXMemberExpression(nameNode)) {
36
+ const obj = nameNode.object && nameNode.object.name;
37
+ const prop = nameNode.property && nameNode.property.name;
38
+ componentName = `${obj || '?'}.${prop || '?'}`;
39
+ }
40
+
41
+ // Skip if already annotated (idempotent across re-runs).
42
+ const already = path.node.attributes.some(
43
+ (a) =>
44
+ t.isJSXAttribute(a) &&
45
+ t.isJSXIdentifier(a.name, { name: '__rnStudioSource' })
46
+ );
47
+ if (already) return;
48
+
49
+ const sourceProp = t.jsxAttribute(
50
+ t.jsxIdentifier('__rnStudioSource'),
51
+ t.jsxExpressionContainer(
52
+ t.objectExpression([
53
+ t.objectProperty(t.identifier('file'), t.stringLiteral(filename)),
54
+ t.objectProperty(t.identifier('line'), t.numericLiteral(line)),
55
+ t.objectProperty(t.identifier('column'), t.numericLiteral(column)),
56
+ t.objectProperty(
57
+ t.identifier('componentName'),
58
+ t.stringLiteral(componentName)
59
+ ),
60
+ ])
61
+ )
62
+ );
63
+
64
+ path.node.attributes.push(sourceProp);
65
+ },
66
+ },
67
+ };
68
+ };
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console */
3
+ /**
4
+ * rn-studio CLI server
5
+ *
6
+ * Run alongside Metro: npm run studio
7
+ *
8
+ * Responsibilities:
9
+ * 1. Listen on ws://localhost:7878 for messages from the rn-studio runtime.
10
+ * 2. On STYLE_CHANGE, dispatch to the AST engine which rewrites the
11
+ * source file. Metro's Fast Refresh instantly propagates the edit.
12
+ */
13
+ const { WebSocketServer } = require('ws');
14
+
15
+ let rewriteStyle;
16
+ try {
17
+ ({ rewriteStyle } = require('../dist/ast/AstEngine'));
18
+ } catch (err) {
19
+ console.error(
20
+ '[rn-studio] Unable to load dist/ast/AstEngine. Did you run `npm run build`?'
21
+ );
22
+ console.error(err.message);
23
+ process.exit(1);
24
+ }
25
+
26
+ const PORT = Number(process.env.RN_STUDIO_PORT) || 7878;
27
+ const wss = new WebSocketServer({ port: PORT });
28
+
29
+ console.log(`[rn-studio] Server running on ws://localhost:${PORT}`);
30
+ console.log('[rn-studio] Waiting for React Native runtime to connect...');
31
+
32
+ wss.on('connection', (ws) => {
33
+ console.log('[rn-studio] Client connected');
34
+
35
+ ws.on('message', async (raw) => {
36
+ let msg;
37
+ try {
38
+ msg = JSON.parse(raw.toString());
39
+ } catch {
40
+ ws.send(
41
+ JSON.stringify({
42
+ type: 'ERROR',
43
+ payload: { message: 'Invalid JSON payload' },
44
+ })
45
+ );
46
+ return;
47
+ }
48
+
49
+ try {
50
+ if (msg.type === 'PING') {
51
+ ws.send(JSON.stringify({ type: 'ACK', payload: { success: true } }));
52
+ return;
53
+ }
54
+
55
+ if (msg.type === 'STYLE_CHANGE') {
56
+ const { source, key, value } = msg.payload;
57
+ await rewriteStyle({
58
+ file: source.file,
59
+ line: source.line,
60
+ column: source.column,
61
+ key,
62
+ value,
63
+ });
64
+ ws.send(JSON.stringify({ type: 'ACK', payload: { success: true } }));
65
+ console.log(
66
+ `[rn-studio] ✓ ${source.componentName} → ${key}: ${value}`
67
+ );
68
+ return;
69
+ }
70
+
71
+ if (msg.type === 'PROP_CHANGE') {
72
+ // Reserved for future prop editing. Ack for now.
73
+ ws.send(
74
+ JSON.stringify({
75
+ type: 'ACK',
76
+ payload: { success: true, message: 'PROP_CHANGE not yet implemented' },
77
+ })
78
+ );
79
+ return;
80
+ }
81
+ } catch (err) {
82
+ console.error('[rn-studio] Error:', err);
83
+ ws.send(
84
+ JSON.stringify({
85
+ type: 'ERROR',
86
+ payload: { message: err && err.message ? err.message : String(err) },
87
+ })
88
+ );
89
+ }
90
+ });
91
+
92
+ ws.on('close', () => console.log('[rn-studio] Client disconnected'));
93
+ });
94
+
95
+ process.on('SIGINT', () => {
96
+ console.log('\n[rn-studio] Shutting down...');
97
+ wss.close(() => process.exit(0));
98
+ });
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import type { BubblePosition, StudioConfig, StudioContextValue } from './types';
3
+ interface Props extends Partial<StudioConfig> {
4
+ enabled?: boolean;
5
+ serverPort?: number;
6
+ bubblePosition?: BubblePosition;
7
+ theme?: 'dark' | 'light';
8
+ children: React.ReactNode;
9
+ }
10
+ /**
11
+ * <StudioProvider>
12
+ *
13
+ * Wrap your App.tsx with this provider. When `enabled` is false (the
14
+ * default, intended for production), it renders children verbatim and
15
+ * introduces zero overhead — no context, no bridge, no overlay.
16
+ *
17
+ * When enabled, it manages the studio state machine, opens a WebSocket
18
+ * connection to the CLI server, and renders the floating bubble,
19
+ * selection overlay, and inspector panel above your app.
20
+ */
21
+ export declare function StudioProvider({ children, enabled, serverPort, bubblePosition, }: Props): React.JSX.Element;
22
+ /** Hook for any descendant of `<StudioProvider>` to read studio state. */
23
+ export declare function useStudio(): StudioContextValue;
24
+ export {};
25
+ //# sourceMappingURL=StudioProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StudioProvider.d.ts","sourceRoot":"","sources":["../src/StudioProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkD,MAAM,OAAO,CAAC;AAMvE,OAAO,KAAK,EACV,cAAc,EAEd,YAAY,EACZ,kBAAkB,EAEnB,MAAM,SAAS,CAAC;AAEjB,UAAU,KAAM,SAAQ,OAAO,CAAC,YAAY,CAAC;IAC3C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,OAAe,EACf,UAAiB,EACjB,cAA+B,GAChC,EAAE,KAAK,qBAUP;AA+ED,0EAA0E;AAC1E,wBAAgB,SAAS,IAAI,kBAAkB,CAM9C"}
@@ -0,0 +1,131 @@
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.StudioProvider = StudioProvider;
37
+ exports.useStudio = useStudio;
38
+ const react_1 = __importStar(require("react"));
39
+ const StudioContext_1 = require("./context/StudioContext");
40
+ const WebSocketBridge_1 = require("./bridge/WebSocketBridge");
41
+ const FloatingBubble_1 = require("./components/FloatingBubble");
42
+ const SelectionOverlay_1 = require("./components/SelectionOverlay");
43
+ const InspectorPanel_1 = require("./components/InspectorPanel");
44
+ /**
45
+ * <StudioProvider>
46
+ *
47
+ * Wrap your App.tsx with this provider. When `enabled` is false (the
48
+ * default, intended for production), it renders children verbatim and
49
+ * introduces zero overhead — no context, no bridge, no overlay.
50
+ *
51
+ * When enabled, it manages the studio state machine, opens a WebSocket
52
+ * connection to the CLI server, and renders the floating bubble,
53
+ * selection overlay, and inspector panel above your app.
54
+ */
55
+ function StudioProvider({ children, enabled = false, serverPort = 7878, bubblePosition = 'bottom-right', }) {
56
+ if (!enabled) {
57
+ return react_1.default.createElement(react_1.default.Fragment, null, children);
58
+ }
59
+ return (react_1.default.createElement(StudioProviderInner, { serverPort: serverPort, bubblePosition: bubblePosition }, children));
60
+ }
61
+ const StudioProviderInner = ({ serverPort, bubblePosition, children }) => {
62
+ const [state, setState] = (0, react_1.useState)('IDLE');
63
+ const [selectedComponent, setSelectedComponent] = (0, react_1.useState)(null);
64
+ const bridgeRef = (0, react_1.useRef)(null);
65
+ if (!bridgeRef.current) {
66
+ bridgeRef.current = new WebSocketBridge_1.WebSocketBridge(serverPort);
67
+ }
68
+ (0, react_1.useEffect)(() => {
69
+ const bridge = bridgeRef.current;
70
+ bridge.connect();
71
+ const off = bridge.on('ACK', () => {
72
+ // Success acks are surfaced to child editors via the per-row
73
+ // debounce timer; nothing else to do here for now.
74
+ });
75
+ return () => {
76
+ off();
77
+ bridge.disconnect();
78
+ };
79
+ }, []);
80
+ const toggleActive = () => {
81
+ setState((s) => {
82
+ if (s === 'IDLE')
83
+ return 'ACTIVE';
84
+ setSelectedComponent(null);
85
+ return 'IDLE';
86
+ });
87
+ };
88
+ const selectComponent = (node) => {
89
+ setSelectedComponent(node);
90
+ setState('SELECTED');
91
+ };
92
+ const clearSelection = () => {
93
+ setSelectedComponent(null);
94
+ setState('ACTIVE');
95
+ };
96
+ const updateStyle = (key, value) => {
97
+ if (!selectedComponent)
98
+ return;
99
+ bridgeRef.current?.send({
100
+ type: 'STYLE_CHANGE',
101
+ payload: {
102
+ source: selectedComponent.source,
103
+ key,
104
+ value,
105
+ },
106
+ });
107
+ };
108
+ const ctx = {
109
+ isActive: state !== 'IDLE',
110
+ isSelecting: state === 'SELECTING' || state === 'ACTIVE',
111
+ selectedComponent,
112
+ toggleActive,
113
+ selectComponent,
114
+ clearSelection,
115
+ updateStyle,
116
+ };
117
+ return (react_1.default.createElement(StudioContext_1.StudioContext.Provider, { value: ctx },
118
+ children,
119
+ react_1.default.createElement(SelectionOverlay_1.SelectionOverlay, null),
120
+ react_1.default.createElement(InspectorPanel_1.InspectorPanel, null),
121
+ react_1.default.createElement(FloatingBubble_1.FloatingBubble, { position: bubblePosition })));
122
+ };
123
+ /** Hook for any descendant of `<StudioProvider>` to read studio state. */
124
+ function useStudio() {
125
+ const ctx = (0, react_1.useContext)(StudioContext_1.StudioContext);
126
+ if (!ctx) {
127
+ throw new Error('useStudio must be used inside <StudioProvider>');
128
+ }
129
+ return ctx;
130
+ }
131
+ //# sourceMappingURL=StudioProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StudioProvider.js","sourceRoot":"","sources":["../src/StudioProvider.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ export interface RewriteOptions {
2
+ file: string;
3
+ line: number;
4
+ column: number;
5
+ key: string;
6
+ value: string | number;
7
+ }
8
+ /**
9
+ * Rewrites (or inserts) a single style key on the JSX element that
10
+ * begins at the supplied (line, column). Supports:
11
+ *
12
+ * - Inline object literal: style={{ backgroundColor: 'red' }}
13
+ * - Array with inline obj: style={[base, { backgroundColor: 'red' }]}
14
+ */
15
+ export declare function rewriteStyle(opts: RewriteOptions): Promise<void>;
16
+ //# sourceMappingURL=AstEngine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AstEngine.d.ts","sourceRoot":"","sources":["../../src/ast/AstEngine.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;CACxB;AASD;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CA+FtE"}
@@ -0,0 +1,153 @@
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.rewriteStyle = rewriteStyle;
37
+ /**
38
+ * AstEngine
39
+ *
40
+ * Server-side (Node.js) module that rewrites a JSX element's `style` prop
41
+ * in-place on disk, preserving formatting, comments, and surrounding code
42
+ * thanks to recast.
43
+ *
44
+ * Location resolution is based on the (line, column) coordinates of the
45
+ * JSXOpeningElement — the same coordinates the babel plugin embeds into
46
+ * `__rnStudioSource` at compile time.
47
+ */
48
+ const parser = __importStar(require("@babel/parser"));
49
+ const recast = __importStar(require("recast"));
50
+ const fs = __importStar(require("fs"));
51
+ const b = recast.types.builders;
52
+ function buildLiteral(value) {
53
+ if (typeof value === 'number')
54
+ return b.numericLiteral(value);
55
+ return b.stringLiteral(String(value));
56
+ }
57
+ /**
58
+ * Rewrites (or inserts) a single style key on the JSX element that
59
+ * begins at the supplied (line, column). Supports:
60
+ *
61
+ * - Inline object literal: style={{ backgroundColor: 'red' }}
62
+ * - Array with inline obj: style={[base, { backgroundColor: 'red' }]}
63
+ */
64
+ async function rewriteStyle(opts) {
65
+ const { file, line, column, key, value } = opts;
66
+ if (!fs.existsSync(file)) {
67
+ throw new Error(`Source file not found: ${file}`);
68
+ }
69
+ const source = fs.readFileSync(file, 'utf-8');
70
+ const ast = recast.parse(source, {
71
+ parser: {
72
+ parse: (src) => parser.parse(src, {
73
+ sourceType: 'module',
74
+ plugins: ['typescript', 'jsx'],
75
+ tokens: true,
76
+ }),
77
+ },
78
+ });
79
+ let matched = false;
80
+ recast.visit(ast, {
81
+ visitJSXOpeningElement(path) {
82
+ const loc = path.node.loc && path.node.loc.start;
83
+ if (!loc || loc.line !== line || loc.column !== column) {
84
+ return this.traverse(path);
85
+ }
86
+ matched = true;
87
+ // Locate the `style` prop.
88
+ const attributes = (path.node.attributes || []);
89
+ let styleAttr = attributes.find((a) => a.type === 'JSXAttribute' && a.name && a.name.name === 'style');
90
+ // If no style prop exists, create one: style={{ [key]: value }}
91
+ if (!styleAttr) {
92
+ const obj = b.objectExpression([
93
+ b.property('init', b.identifier(key), buildLiteral(value)),
94
+ ]);
95
+ styleAttr = b.jsxAttribute(b.jsxIdentifier('style'), b.jsxExpressionContainer(obj));
96
+ path.node.attributes.push(styleAttr);
97
+ return false;
98
+ }
99
+ const expr = styleAttr.value &&
100
+ styleAttr.value.type === 'JSXExpressionContainer' &&
101
+ styleAttr.value.expression;
102
+ if (!expr)
103
+ return false;
104
+ // Case 1: inline object.
105
+ if (expr.type === 'ObjectExpression') {
106
+ upsertObjectProperty(expr, key, value);
107
+ return false;
108
+ }
109
+ // Case 2: array — look for the first ObjectExpression and upsert.
110
+ if (expr.type === 'ArrayExpression') {
111
+ const firstObj = (expr.elements || []).find((el) => el && el.type === 'ObjectExpression');
112
+ if (firstObj) {
113
+ upsertObjectProperty(firstObj, key, value);
114
+ }
115
+ else {
116
+ expr.elements.push(b.objectExpression([
117
+ b.property('init', b.identifier(key), buildLiteral(value)),
118
+ ]));
119
+ }
120
+ return false;
121
+ }
122
+ // Case 3: identifier (e.g. style={styles.foo}) — unsupported for now.
123
+ // A future version could resolve StyleSheet.create definitions.
124
+ return false;
125
+ },
126
+ });
127
+ if (!matched) {
128
+ throw new Error(`Could not locate JSX element at ${file}:${line}:${column}`);
129
+ }
130
+ const output = recast.print(ast).code;
131
+ fs.writeFileSync(file, output, 'utf-8');
132
+ }
133
+ function upsertObjectProperty(objExpr, key, value) {
134
+ const props = objExpr.properties;
135
+ const existing = props.find((p) => {
136
+ if (!p || (p.type !== 'ObjectProperty' && p.type !== 'Property'))
137
+ return false;
138
+ const k = p.key;
139
+ if (!k)
140
+ return false;
141
+ return ((k.type === 'Identifier' && k.name === key) ||
142
+ (k.type === 'StringLiteral' && k.value === key) ||
143
+ (k.type === 'Literal' && k.value === key));
144
+ });
145
+ const literal = buildLiteral(value);
146
+ if (existing) {
147
+ existing.value = literal;
148
+ }
149
+ else {
150
+ props.push(b.property('init', b.identifier(key), literal));
151
+ }
152
+ }
153
+ //# sourceMappingURL=AstEngine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AstEngine.js","sourceRoot":"","sources":["../../src/ast/AstEngine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { StudioMessage } from '../types';
2
+ /**
3
+ * WebSocketBridge
4
+ *
5
+ * Runs inside the React Native app (Metro bundle). Maintains a
6
+ * persistent connection to the rn-studio CLI server (default ws://localhost:7878).
7
+ * Automatically reconnects with exponential backoff if the server is
8
+ * not yet running — typical during a fresh `npm run studio`.
9
+ */
10
+ export declare class WebSocketBridge {
11
+ private ws;
12
+ private port;
13
+ private reconnectAttempts;
14
+ private maxAttempts;
15
+ private listeners;
16
+ private manuallyClosed;
17
+ constructor(port?: number);
18
+ connect(): void;
19
+ private scheduleReconnect;
20
+ send(msg: StudioMessage): void;
21
+ on(type: StudioMessage['type'], cb: (msg: StudioMessage) => void): () => void;
22
+ disconnect(): void;
23
+ }
24
+ //# sourceMappingURL=WebSocketBridge.d.ts.map