react-state-flow 0.0.3 → 0.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.
@@ -0,0 +1,4 @@
1
+ import type { GraphData } from './types.js';
2
+ export type { GraphData, GraphNode, GraphEdge } from './types.js';
3
+ export declare function parseProject(projectRoot: string): GraphData;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/parser/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAwB,MAAM,YAAY,CAAA;AAEjE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAqBjE,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAgD3D"}
@@ -0,0 +1,65 @@
1
+ import { readFileSync, readdirSync, statSync } from 'fs';
2
+ import { join, relative } from 'path';
3
+ import { parseFile } from './parse-file.js';
4
+ const EXTENSIONS = ['.tsx', '.jsx', '.ts', '.js'];
5
+ function collectFiles(dir) {
6
+ const results = [];
7
+ const IGNORE = ['node_modules', '.git', 'dist', 'build', '.next'];
8
+ for (const entry of readdirSync(dir)) {
9
+ if (IGNORE.includes(entry))
10
+ continue;
11
+ const full = join(dir, entry);
12
+ const stat = statSync(full);
13
+ if (stat.isDirectory()) {
14
+ results.push(...collectFiles(full));
15
+ }
16
+ else if (EXTENSIONS.some((ext) => full.endsWith(ext))) {
17
+ results.push(full);
18
+ }
19
+ }
20
+ return results;
21
+ }
22
+ export function parseProject(projectRoot) {
23
+ const files = collectFiles(projectRoot);
24
+ // Pass 1: collect all component node ids for cross-file edge resolution
25
+ const globalComponentSet = new Set();
26
+ for (const file of files) {
27
+ const code = readFileSync(file, 'utf-8');
28
+ const relPath = relative(projectRoot, file);
29
+ const { nodes } = parseFile(code, relPath);
30
+ for (const n of nodes) {
31
+ if (n.type === 'component')
32
+ globalComponentSet.add(n.id);
33
+ }
34
+ }
35
+ // Pass 2: full parse with global component set for cross-file edges
36
+ const allNodes = [];
37
+ const allEdges = [];
38
+ for (const file of files) {
39
+ const code = readFileSync(file, 'utf-8');
40
+ const relPath = relative(projectRoot, file);
41
+ const { nodes, edges } = parseFile(code, relPath, globalComponentSet);
42
+ allNodes.push(...nodes);
43
+ allEdges.push(...edges);
44
+ }
45
+ // Deduplicate nodes by id (keep first occurrence)
46
+ const seen = new Set();
47
+ const uniqueNodes = allNodes.filter((n) => {
48
+ if (seen.has(n.id))
49
+ return false;
50
+ seen.add(n.id);
51
+ return true;
52
+ });
53
+ // Remove edges referencing unknown nodes
54
+ const nodeIds = new Set(uniqueNodes.map((n) => n.id));
55
+ const validEdges = allEdges.filter((e) => nodeIds.has(e.source) && nodeIds.has(e.target));
56
+ // Deduplicate edges
57
+ const edgeSeen = new Set();
58
+ const uniqueEdges = validEdges.filter((e) => {
59
+ if (edgeSeen.has(e.id))
60
+ return false;
61
+ edgeSeen.add(e.id);
62
+ return true;
63
+ });
64
+ return { nodes: uniqueNodes, edges: uniqueEdges };
65
+ }
@@ -0,0 +1,8 @@
1
+ import type { GraphNode, GraphEdge } from './types.js';
2
+ interface FileResult {
3
+ nodes: GraphNode[];
4
+ edges: GraphEdge[];
5
+ }
6
+ export declare function parseFile(code: string, filePath: string, externalComponents?: Set<string>): FileResult;
7
+ export {};
8
+ //# sourceMappingURL=parse-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-file.d.ts","sourceRoot":"","sources":["../../src/parser/parse-file.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAKtD,UAAU,UAAU;IAClB,KAAK,EAAE,SAAS,EAAE,CAAA;IAClB,KAAK,EAAE,SAAS,EAAE,CAAA;CACnB;AA8CD,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,kBAAkB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAC/B,UAAU,CA8LZ"}
@@ -0,0 +1,251 @@
1
+ import { parse } from '@babel/parser';
2
+ import _traverse from '@babel/traverse';
3
+ import * as t from '@babel/types';
4
+ // @babel/traverse ESM interop
5
+ const traverse = _traverse.default ?? _traverse;
6
+ function isComponentName(name) {
7
+ return /^[A-Z]/.test(name);
8
+ }
9
+ function getStateSlots(path) {
10
+ const slots = [];
11
+ path.traverse({
12
+ CallExpression(innerPath) {
13
+ const callee = innerPath.node.callee;
14
+ const isHook = (t.isIdentifier(callee, { name: 'useState' }) ||
15
+ t.isIdentifier(callee, { name: 'useReducer' })) &&
16
+ t.isIdentifier(callee);
17
+ if (!isHook)
18
+ return;
19
+ // useState([initialValue]) → destructure [state, setState]
20
+ const parent = innerPath.parentPath;
21
+ if (parent?.isVariableDeclarator() &&
22
+ t.isArrayPattern(parent.node.id)) {
23
+ const first = parent.node.id.elements[0];
24
+ if (t.isIdentifier(first))
25
+ slots.push(first.name);
26
+ }
27
+ },
28
+ });
29
+ return slots;
30
+ }
31
+ function getContextUsages(path) {
32
+ const names = [];
33
+ path.traverse({
34
+ CallExpression(innerPath) {
35
+ const callee = innerPath.node.callee;
36
+ if (!t.isIdentifier(callee, { name: 'useContext' }))
37
+ return;
38
+ const arg = innerPath.node.arguments[0];
39
+ if (t.isIdentifier(arg))
40
+ names.push(arg.name);
41
+ },
42
+ });
43
+ return names;
44
+ }
45
+ // A1: Accept optional externalComponents for cross-file edge resolution
46
+ export function parseFile(code, filePath, externalComponents) {
47
+ const nodes = [];
48
+ const edges = [];
49
+ let ast;
50
+ try {
51
+ ast = parse(code, {
52
+ sourceType: 'module',
53
+ plugins: ['jsx', 'typescript', 'decorators-legacy'],
54
+ });
55
+ }
56
+ catch (err) {
57
+ const msg = err instanceof Error ? err.message : String(err);
58
+ console.warn(`[RSF] Failed to parse ${filePath}: ${msg}`);
59
+ return { nodes, edges };
60
+ }
61
+ // Map: context variable name → node id
62
+ const contextMap = new Map();
63
+ // Track component names found in this file for JSX child resolution
64
+ const componentSet = new Set();
65
+ // A5: Use Set for O(1) edge deduplication
66
+ const edgeIdSet = new Set();
67
+ function registerComponent(name, path, line) {
68
+ const stateSlots = getStateSlots(path);
69
+ const contextUsages = getContextUsages(path);
70
+ // Detect if this component renders a Context.Provider
71
+ let isContextProvider = false;
72
+ let contextName;
73
+ path.traverse({
74
+ JSXOpeningElement(innerPath) {
75
+ const el = innerPath.node.name;
76
+ // <XxxContext.Provider>
77
+ if (t.isJSXMemberExpression(el) &&
78
+ t.isJSXIdentifier(el.property, { name: 'Provider' }) &&
79
+ t.isJSXIdentifier(el.object)) {
80
+ isContextProvider = true;
81
+ contextName = el.object.name;
82
+ }
83
+ },
84
+ });
85
+ const node = {
86
+ id: name,
87
+ type: 'component',
88
+ label: name,
89
+ file: filePath,
90
+ line,
91
+ stateSlots,
92
+ isContextProvider,
93
+ contextName,
94
+ };
95
+ nodes.push(node);
96
+ componentSet.add(name);
97
+ // Add context-subscription edges
98
+ for (const ctxName of contextUsages) {
99
+ const ctxId = contextMap.get(ctxName) ?? ctxName;
100
+ const edgeId = `${ctxId}->${name}`;
101
+ if (!edgeIdSet.has(edgeId)) {
102
+ edgeIdSet.add(edgeId);
103
+ edges.push({
104
+ id: edgeId,
105
+ source: ctxId,
106
+ target: name,
107
+ type: 'context-subscription',
108
+ });
109
+ }
110
+ }
111
+ // A4: Create Provider → Context edge
112
+ if (isContextProvider && contextName) {
113
+ const ctxId = contextMap.get(contextName) ?? contextName;
114
+ const edgeId = `${name}->${ctxId}:provides`;
115
+ if (!edgeIdSet.has(edgeId)) {
116
+ edgeIdSet.add(edgeId);
117
+ edges.push({
118
+ id: edgeId,
119
+ source: name,
120
+ target: ctxId,
121
+ type: 'context-provision',
122
+ });
123
+ }
124
+ }
125
+ return node;
126
+ }
127
+ // First pass: find createContext calls to build contextMap
128
+ traverse(ast, {
129
+ VariableDeclarator(path) {
130
+ if (t.isCallExpression(path.node.init) &&
131
+ t.isIdentifier(path.node.init.callee, {
132
+ name: 'createContext',
133
+ }) &&
134
+ t.isIdentifier(path.node.id)) {
135
+ const ctxVarName = path.node.id.name;
136
+ const nodeId = ctxVarName;
137
+ contextMap.set(ctxVarName, nodeId);
138
+ nodes.push({
139
+ id: nodeId,
140
+ type: 'context',
141
+ label: ctxVarName,
142
+ file: filePath,
143
+ line: path.node.loc?.start.line ?? 0,
144
+ stateSlots: [],
145
+ isContextProvider: false,
146
+ });
147
+ }
148
+ },
149
+ });
150
+ // Second pass: find component declarations
151
+ traverse(ast, {
152
+ // function MyComponent() { ... }
153
+ FunctionDeclaration(path) {
154
+ const name = path.node.id?.name;
155
+ if (name && isComponentName(name)) {
156
+ registerComponent(name, path, path.node.loc?.start.line ?? 0);
157
+ }
158
+ },
159
+ // const MyComponent = () => { ... } or function() { ... } or memo(...) etc.
160
+ VariableDeclarator(path) {
161
+ if (!t.isIdentifier(path.node.id))
162
+ return;
163
+ const name = path.node.id.name;
164
+ if (!isComponentName(name))
165
+ return;
166
+ const init = path.node.init;
167
+ if (t.isArrowFunctionExpression(init) || t.isFunctionExpression(init)) {
168
+ registerComponent(name, path, path.node.loc?.start.line ?? 0);
169
+ return;
170
+ }
171
+ // A2: HOC / memo / forwardRef wrapping a function
172
+ if (t.isCallExpression(init)) {
173
+ const firstArg = init.arguments[0];
174
+ if (firstArg &&
175
+ (t.isArrowFunctionExpression(firstArg) ||
176
+ t.isFunctionExpression(firstArg) ||
177
+ t.isIdentifier(firstArg))) {
178
+ registerComponent(name, path, path.node.loc?.start.line ?? 0);
179
+ }
180
+ }
181
+ },
182
+ // A3: export default function() { ... } — derive name from filename
183
+ ExportDefaultDeclaration(path) {
184
+ const decl = path.node.declaration;
185
+ const isAnonFn = (t.isFunctionDeclaration(decl) || t.isArrowFunctionExpression(decl)) &&
186
+ !decl.id;
187
+ if (!isAnonFn)
188
+ return;
189
+ const base = filePath.split('/').pop() ?? filePath;
190
+ const name = base.replace(/\.[^.]+$/, '');
191
+ if (!isComponentName(name))
192
+ return;
193
+ registerComponent(name, path, path.node.loc?.start.line ?? 0);
194
+ },
195
+ });
196
+ // Third pass: find JSX parent→child relationships
197
+ traverse(ast, {
198
+ FunctionDeclaration(path) {
199
+ extractJSXChildren(path, edges, edgeIdSet, componentSet, externalComponents);
200
+ },
201
+ VariableDeclarator(path) {
202
+ if (!t.isIdentifier(path.node.id))
203
+ return;
204
+ const name = path.node.id.name;
205
+ if (!isComponentName(name))
206
+ return;
207
+ const init = path.node.init;
208
+ const isFn = t.isArrowFunctionExpression(init) || t.isFunctionExpression(init);
209
+ const isWrapped = t.isCallExpression(init);
210
+ if (isFn || isWrapped) {
211
+ extractJSXChildren(path, edges, edgeIdSet, componentSet, externalComponents, name);
212
+ }
213
+ },
214
+ });
215
+ return { nodes, edges };
216
+ }
217
+ function extractJSXChildren(path, edges, edgeIdSet, componentSet, externalComponents, parentNameOverride) {
218
+ const parentName = parentNameOverride ??
219
+ path.node.id?.name;
220
+ if (!parentName || !isComponentName(parentName))
221
+ return;
222
+ // A1: Use externalComponents (global set) when available for cross-file resolution
223
+ const allKnown = externalComponents ?? componentSet;
224
+ path.traverse({
225
+ JSXOpeningElement(innerPath) {
226
+ const el = innerPath.node.name;
227
+ let childName;
228
+ if (t.isJSXIdentifier(el) && isComponentName(el.name)) {
229
+ childName = el.name;
230
+ }
231
+ // A6: Handle <Namespace.Component /> — use namespace as the component reference
232
+ if (!childName && t.isJSXMemberExpression(el)) {
233
+ const ns = t.isJSXIdentifier(el.object) ? el.object.name : undefined;
234
+ if (ns && allKnown.has(ns))
235
+ childName = ns;
236
+ }
237
+ if (!childName || !allKnown.has(childName))
238
+ return;
239
+ const edgeId = `${parentName}->${childName}`;
240
+ if (edgeIdSet.has(edgeId))
241
+ return; // A5: O(1) check
242
+ edgeIdSet.add(edgeId);
243
+ edges.push({
244
+ id: edgeId,
245
+ source: parentName,
246
+ target: childName,
247
+ type: 'parent-child',
248
+ });
249
+ },
250
+ });
251
+ }
@@ -0,0 +1,23 @@
1
+ export type NodeType = 'component' | 'context';
2
+ export interface GraphNode {
3
+ id: string;
4
+ type: NodeType;
5
+ label: string;
6
+ file: string;
7
+ line: number;
8
+ stateSlots: string[];
9
+ isContextProvider: boolean;
10
+ contextName?: string;
11
+ }
12
+ export type EdgeType = 'parent-child' | 'context-subscription' | 'context-provision';
13
+ export interface GraphEdge {
14
+ id: string;
15
+ source: string;
16
+ target: string;
17
+ type: EdgeType;
18
+ }
19
+ export interface GraphData {
20
+ nodes: GraphNode[];
21
+ edges: GraphEdge[];
22
+ }
23
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/parser/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,WAAW,GAAG,SAAS,CAAA;AAE9C,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,QAAQ,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,iBAAiB,EAAE,OAAO,CAAA;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,MAAM,QAAQ,GAAG,cAAc,GAAG,sBAAsB,GAAG,mBAAmB,CAAA;AAEpF,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,QAAQ,CAAA;CACf;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,SAAS,EAAE,CAAA;IAClB,KAAK,EAAE,SAAS,EAAE,CAAA;CACnB"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,22 @@
1
+ /**
2
+ * react-state-flow/runtime
3
+ *
4
+ * Import này TRƯỚC khi React mount để hook vào React DevTools global hook.
5
+ * Sends render events via WebSocket to the RSF CLI server.
6
+ *
7
+ * Usage:
8
+ * import 'react-state-flow/runtime' // top of main.tsx / index.tsx
9
+ */
10
+ export interface RenderEvent {
11
+ type: 'render';
12
+ componentName: string;
13
+ renderCount: number;
14
+ timestamp: number;
15
+ }
16
+ declare global {
17
+ interface Window {
18
+ __REACT_DEVTOOLS_GLOBAL_HOOK__: any;
19
+ __RSF_WS__: WebSocket | null;
20
+ }
21
+ }
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,QAAQ,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,8BAA8B,EAAE,GAAG,CAAA;QACnC,UAAU,EAAE,SAAS,GAAG,IAAI,CAAA;KAC7B;CACF"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * react-state-flow/runtime
3
+ *
4
+ * Import này TRƯỚC khi React mount để hook vào React DevTools global hook.
5
+ * Sends render events via WebSocket to the RSF CLI server.
6
+ *
7
+ * Usage:
8
+ * import 'react-state-flow/runtime' // top of main.tsx / index.tsx
9
+ */
10
+ const RSF_PORT = window.__RSF_PORT__ ?? 7272;
11
+ const WS_URL = `ws://localhost:${RSF_PORT}/runtime`;
12
+ // B2: Render counter per component with max-size cap to prevent memory leak
13
+ const renderCounts = new Map();
14
+ const MAX_RENDER_COUNT_ENTRIES = 500;
15
+ function incrementRenderCount(name) {
16
+ if (!renderCounts.has(name) && renderCounts.size >= MAX_RENDER_COUNT_ENTRIES) {
17
+ // Evict oldest entry (insertion order)
18
+ const firstKey = renderCounts.keys().next().value;
19
+ if (firstKey !== undefined)
20
+ renderCounts.delete(firstKey);
21
+ }
22
+ const count = (renderCounts.get(name) ?? 0) + 1;
23
+ renderCounts.set(name, count);
24
+ return count;
25
+ }
26
+ function send(event) {
27
+ if (!window.__RSF_WS__ || window.__RSF_WS__.readyState !== WebSocket.OPEN)
28
+ return;
29
+ window.__RSF_WS__.send(JSON.stringify(event));
30
+ }
31
+ // B3: Exponential backoff reconnect
32
+ let reconnectDelay = 2000;
33
+ function connect() {
34
+ const ws = new WebSocket(WS_URL);
35
+ window.__RSF_WS__ = ws;
36
+ ws.addEventListener('open', () => {
37
+ reconnectDelay = 2000; // reset on successful connection
38
+ renderCounts.clear(); // B2: sync counts with server on reconnect
39
+ console.debug('[RSF] Runtime connected');
40
+ });
41
+ ws.addEventListener('close', () => {
42
+ console.debug(`[RSF] Runtime disconnected, retrying in ${reconnectDelay / 1000}s...`);
43
+ setTimeout(connect, reconnectDelay);
44
+ reconnectDelay = Math.min(reconnectDelay * 2, 30000);
45
+ });
46
+ ws.addEventListener('error', () => {
47
+ // suppress — will retry on close
48
+ });
49
+ }
50
+ function hookIntoReact() {
51
+ // React checks for this global hook on load and calls it during reconciliation
52
+ const existing = window.__REACT_DEVTOOLS_GLOBAL_HOOK__ ?? {};
53
+ const originalOnCommitFiberRoot = existing.onCommitFiberRoot?.bind(existing) ?? (() => { });
54
+ window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
55
+ ...existing,
56
+ isDisabled: false,
57
+ supportsFiber: true,
58
+ onCommitFiberRoot(rendererID, root, priorityLevel) {
59
+ // Call through so React DevTools still works
60
+ originalOnCommitFiberRoot(rendererID, root, priorityLevel);
61
+ // B1: Pass per-commit Set to avoid counting sibling instances multiple times
62
+ try {
63
+ walkFiber(root.current, new Set());
64
+ }
65
+ catch {
66
+ // Never break the app
67
+ }
68
+ },
69
+ };
70
+ }
71
+ function getFiberName(fiber) {
72
+ const type = fiber?.type;
73
+ if (!type)
74
+ return null;
75
+ if (typeof type === 'string')
76
+ return null; // DOM element
77
+ if (typeof type === 'function')
78
+ return type.displayName ?? type.name ?? null;
79
+ if (typeof type === 'object' && type !== null) {
80
+ // forwardRef, memo, etc.
81
+ return (type.displayName ??
82
+ type.render?.displayName ??
83
+ type.render?.name ??
84
+ null);
85
+ }
86
+ return null;
87
+ }
88
+ // B1: seenInCommit prevents counting sibling instances multiple times per commit
89
+ function walkFiber(fiber, seenInCommit) {
90
+ if (!fiber)
91
+ return;
92
+ const name = getFiberName(fiber);
93
+ if (name && /^[A-Z]/.test(name) && !seenInCommit.has(name)) {
94
+ seenInCommit.add(name);
95
+ const count = incrementRenderCount(name);
96
+ send({ type: 'render', componentName: name, renderCount: count, timestamp: Date.now() });
97
+ }
98
+ walkFiber(fiber.child, seenInCommit);
99
+ walkFiber(fiber.sibling, seenInCommit);
100
+ }
101
+ // Bootstrap — only in development
102
+ const isDev = typeof process !== 'undefined'
103
+ ? process.env.NODE_ENV !== 'production'
104
+ : import.meta.env?.MODE !== 'production';
105
+ if (typeof window !== 'undefined' && isDev) {
106
+ hookIntoReact();
107
+ connect();
108
+ }
109
+ export {};
package/package.json CHANGED
@@ -1,34 +1,60 @@
1
1
  {
2
2
  "name": "react-state-flow",
3
- "version": "0.0.3",
3
+ "version": "0.1.1",
4
4
  "description": "Visualize React component hierarchy, state flow, and real-time render events",
5
5
  "type": "module",
6
+ "license": "MIT",
6
7
  "bin": {
7
8
  "react-state-flow": "./dist/index.js"
8
9
  },
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/index.js",
13
+ "types": "./dist/index.d.ts"
14
+ },
15
+ "./runtime": {
16
+ "import": "./dist/runtime/index.js",
17
+ "types": "./dist/runtime/index.d.ts"
18
+ }
19
+ },
9
20
  "files": [
10
- "dist"
21
+ "dist",
22
+ "ui/dist"
11
23
  ],
12
24
  "engines": {
13
25
  "node": ">=18"
14
26
  },
15
27
  "scripts": {
16
- "start": "tsx src/index.ts",
17
- "build": "pnpm --filter @rsf/ui build && node build.mjs"
28
+ "build": "npm run build:ts && npm run build:ui",
29
+ "build:ts": "tsc -p tsconfig.json",
30
+ "build:ui": "vite build --config ui/vite.config.ts",
31
+ "dev": "tsx src/index.ts",
32
+ "ui:dev": "vite --config ui/vite.config.ts",
33
+ "prepublishOnly": "npm run build"
18
34
  },
19
35
  "dependencies": {
36
+ "@babel/parser": "^7.24.0",
37
+ "@babel/traverse": "^7.24.0",
38
+ "@babel/types": "^7.24.0",
20
39
  "chokidar": "^3.6.0",
21
40
  "express": "^4.19.0",
22
- "ws": "^8.17.0",
23
41
  "open": "^10.1.0",
24
- "picocolors": "^1.0.1"
42
+ "picocolors": "^1.0.1",
43
+ "ws": "^8.17.0"
25
44
  },
26
45
  "devDependencies": {
27
- "@rsf/parser": "workspace:*",
46
+ "@dagrejs/dagre": "^1.0.0",
47
+ "@types/babel__traverse": "^7.20.6",
28
48
  "@types/express": "^4.17.21",
49
+ "@types/react": "^18.3.0",
50
+ "@types/react-dom": "^18.3.0",
29
51
  "@types/ws": "^8.5.10",
30
- "esbuild": "^0.21.0",
52
+ "@vitejs/plugin-react": "^4.3.0",
53
+ "@xyflow/react": "^12.0.0",
54
+ "react": "^18.3.0",
55
+ "react-dom": "^18.3.0",
31
56
  "tsx": "^4.15.0",
32
- "typescript": "^5.4.0"
57
+ "typescript": "^5.4.0",
58
+ "vite": "^5.3.0"
33
59
  }
34
60
  }