@rayburst/cli 0.1.0 → 0.1.8
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 +236 -157
- package/bin/rayburst.js +156 -1
- package/index.html +3 -11
- package/package.json +8 -26
- package/scripts/analyze-project.js +475 -0
- package/server.js +78 -1
- package/src/file-watcher.js +174 -0
- package/src/git-utils.js +105 -0
- package/src/incremental-analyzer.js +295 -0
- package/src/main.tsx +138 -0
- package/src/registry.js +262 -0
- package/vite-plugin-api.js +123 -0
- package/vite.config.ts +72 -0
- package/dist/assets/_commonjsHelpers-B85MJLTf.js +0 -5
- package/dist/assets/hostInit-CYZeRSfr.js +0 -9
- package/dist/assets/index-9R1akZrm.js +0 -578
- package/dist/assets/index-BW-RulSg.js +0 -258
- package/dist/assets/index-Cap7gsMp.js +0 -16597
- package/dist/assets/preload-helper-Dea3Szod.js +0 -54
- package/dist/assets/rayburstCli__loadRemote__rayburstApp_mf_1_App__loadRemote__-CHUYMhiU.js +0 -35
- package/dist/assets/rayburstCli__loadShare__react__loadShare__-CE7VtFm0.js +0 -19
- package/dist/assets/rayburstCli__mf_v__runtimeInit__mf_v__-C_SVfzik.js +0 -4173
- package/dist/assets/remoteEntry-BkHjZ4dx.js +0 -122
- package/dist/assets/virtualExposes-DwA08f_D.js +0 -5
- package/dist/index.html +0 -64
package/index.html
CHANGED
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html lang="en"
|
|
2
|
+
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<link rel="icon" href="http://localhost:3000/rayburst-letter.png" />
|
|
7
6
|
<title>Rayburst</title>
|
|
8
7
|
<style>
|
|
9
|
-
/* Ensure full height for HTML and body */
|
|
10
|
-
html, body, #app {
|
|
11
|
-
height: 100%;
|
|
12
|
-
margin: 0;
|
|
13
|
-
padding: 0;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
8
|
/* Scoped styles for loading screen only - do not interfere with remote app styles */
|
|
17
9
|
.loading-container {
|
|
18
10
|
display: flex;
|
|
@@ -50,8 +42,8 @@
|
|
|
50
42
|
}
|
|
51
43
|
</style>
|
|
52
44
|
</head>
|
|
53
|
-
<body
|
|
54
|
-
<div id="app"
|
|
45
|
+
<body>
|
|
46
|
+
<div id="app">
|
|
55
47
|
<div class="loading-container">
|
|
56
48
|
<div class="spinner"></div>
|
|
57
49
|
<div class="loading-text">Loading Rayburst...</div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rayburst/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Rayburst CLI - A module federation host for Rayburst app",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,40 +10,22 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"dev": "vite",
|
|
12
12
|
"build": "vite build",
|
|
13
|
-
"start": "node server.js"
|
|
14
|
-
"prepublishOnly": "npm run build"
|
|
13
|
+
"start": "node server.js"
|
|
15
14
|
},
|
|
16
15
|
"keywords": [
|
|
17
16
|
"rayburst",
|
|
18
17
|
"cli",
|
|
19
|
-
"module-federation"
|
|
20
|
-
"vite",
|
|
21
|
-
"react"
|
|
18
|
+
"module-federation"
|
|
22
19
|
],
|
|
23
|
-
"author": "
|
|
20
|
+
"author": "",
|
|
24
21
|
"license": "MIT",
|
|
25
|
-
"repository": {
|
|
26
|
-
"type": "git",
|
|
27
|
-
"url": "https://github.com/rayburst/rayburst-cli.git"
|
|
28
|
-
},
|
|
29
|
-
"homepage": "https://rayburst.app",
|
|
30
|
-
"bugs": {
|
|
31
|
-
"url": "https://github.com/rayburst/rayburst-cli/issues"
|
|
32
|
-
},
|
|
33
|
-
"publishConfig": {
|
|
34
|
-
"access": "public"
|
|
35
|
-
},
|
|
36
|
-
"files": [
|
|
37
|
-
"dist",
|
|
38
|
-
"bin",
|
|
39
|
-
"server.js",
|
|
40
|
-
"index.html",
|
|
41
|
-
"README.md"
|
|
42
|
-
],
|
|
43
22
|
"dependencies": {
|
|
44
23
|
"chalk": "^5.3.0",
|
|
24
|
+
"chokidar": "^4.0.3",
|
|
45
25
|
"commander": "^11.1.0",
|
|
46
|
-
"express": "^4.18.2"
|
|
26
|
+
"express": "^4.18.2",
|
|
27
|
+
"ts-morph": "^21.0.1",
|
|
28
|
+
"ws": "^8.18.3"
|
|
47
29
|
},
|
|
48
30
|
"devDependencies": {
|
|
49
31
|
"@module-federation/vite": "^1.9.0",
|
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Project, SyntaxKind, Node } from 'ts-morph';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import crypto from 'crypto';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Analyze a TypeScript/React project and generate nodes/edges data
|
|
11
|
+
* @param {string} projectPath - Absolute path to project root
|
|
12
|
+
* @returns {Object} Analysis data with nodes and edges
|
|
13
|
+
*/
|
|
14
|
+
export function analyzeProject(projectPath) {
|
|
15
|
+
console.log(`Analyzing project at: ${projectPath}`);
|
|
16
|
+
|
|
17
|
+
const gitHash = getGitHash(projectPath);
|
|
18
|
+
const gitInfo = getGitInfo(projectPath);
|
|
19
|
+
|
|
20
|
+
console.log(` Git hash: ${gitHash}`);
|
|
21
|
+
console.log(` Current branch: ${gitInfo.branch}`);
|
|
22
|
+
|
|
23
|
+
// Find tsconfig.json
|
|
24
|
+
const tsconfigPath = path.join(projectPath, 'tsconfig.json');
|
|
25
|
+
if (!fs.existsSync(tsconfigPath)) {
|
|
26
|
+
console.warn(` Warning: No tsconfig.json found, skipping TypeScript analysis`);
|
|
27
|
+
return createEmptyAnalysis(projectPath, gitInfo);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
// Initialize ts-morph project
|
|
32
|
+
const project = new Project({
|
|
33
|
+
tsConfigFilePath: tsconfigPath,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const nodes = [];
|
|
37
|
+
const edges = [];
|
|
38
|
+
const nodeMap = new Map();
|
|
39
|
+
const usedIds = new Set();
|
|
40
|
+
const idCounters = new Map();
|
|
41
|
+
|
|
42
|
+
// Track file positions for layout
|
|
43
|
+
let fileIndex = 0;
|
|
44
|
+
const filePositions = new Map();
|
|
45
|
+
|
|
46
|
+
// Get all source files (excluding node_modules, test files)
|
|
47
|
+
const sourceFiles = project.getSourceFiles()
|
|
48
|
+
.filter(sf => {
|
|
49
|
+
const filePath = sf.getFilePath();
|
|
50
|
+
return !filePath.includes('node_modules') &&
|
|
51
|
+
!filePath.includes('.test.') &&
|
|
52
|
+
!filePath.includes('.spec.') &&
|
|
53
|
+
(filePath.endsWith('.ts') || filePath.endsWith('.tsx'));
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
console.log(` Analyzing ${sourceFiles.length} source files...`);
|
|
57
|
+
|
|
58
|
+
// Analyze each source file
|
|
59
|
+
for (const sourceFile of sourceFiles) {
|
|
60
|
+
const filePath = sourceFile.getFilePath();
|
|
61
|
+
const relativePath = filePath.replace(projectPath + '/', '');
|
|
62
|
+
|
|
63
|
+
if (!filePositions.has(relativePath)) {
|
|
64
|
+
filePositions.set(relativePath, fileIndex++);
|
|
65
|
+
}
|
|
66
|
+
const baseX = filePositions.get(relativePath) * 400;
|
|
67
|
+
let nodeY = 100;
|
|
68
|
+
|
|
69
|
+
// Extract React components
|
|
70
|
+
const components = extractComponents(sourceFile, relativePath, gitHash, baseX, nodeY, nodes, nodeMap, usedIds, idCounters);
|
|
71
|
+
nodeY += components * 150;
|
|
72
|
+
|
|
73
|
+
// Extract functions
|
|
74
|
+
const functions = extractFunctions(sourceFile, relativePath, gitHash, baseX, nodeY, nodes, nodeMap, usedIds, idCounters);
|
|
75
|
+
nodeY += functions * 150;
|
|
76
|
+
|
|
77
|
+
// Extract state declarations
|
|
78
|
+
const states = extractState(sourceFile, relativePath, gitHash, baseX, nodeY, nodes, nodeMap, usedIds, idCounters);
|
|
79
|
+
nodeY += states * 150;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
console.log(` Extracted ${nodes.length} nodes`);
|
|
83
|
+
|
|
84
|
+
// Generate edges by analyzing relationships
|
|
85
|
+
for (const sourceFile of sourceFiles) {
|
|
86
|
+
generateEdges(sourceFile, nodeMap, edges);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
console.log(` Generated ${edges.length} edges`);
|
|
90
|
+
|
|
91
|
+
// Read package.json for project metadata
|
|
92
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(projectPath, 'package.json'), 'utf-8'));
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
project: {
|
|
96
|
+
id: crypto.createHash('md5').update(projectPath).digest('hex').substring(0, 12),
|
|
97
|
+
title: packageJson.name || path.basename(projectPath),
|
|
98
|
+
subtitle: new Date().toLocaleDateString(),
|
|
99
|
+
value: `${nodes.length} nodes`,
|
|
100
|
+
trend: 0,
|
|
101
|
+
chartData: [30, 40, 35, 50, 45, 60, 55, 70],
|
|
102
|
+
chartColor: '#22c55e',
|
|
103
|
+
},
|
|
104
|
+
branches: [
|
|
105
|
+
{
|
|
106
|
+
id: gitInfo.branch.replace(/[^a-zA-Z0-9]/g, '-'),
|
|
107
|
+
name: gitInfo.branch,
|
|
108
|
+
lastCommit: gitInfo.lastCommit,
|
|
109
|
+
lastCommitDate: 'just now',
|
|
110
|
+
author: gitInfo.author,
|
|
111
|
+
status: 'active',
|
|
112
|
+
commitCount: gitInfo.commitCount,
|
|
113
|
+
pullRequests: 0,
|
|
114
|
+
}
|
|
115
|
+
],
|
|
116
|
+
planData: {
|
|
117
|
+
[gitInfo.branch.replace(/[^a-zA-Z0-9]/g, '-')]: {
|
|
118
|
+
nodes,
|
|
119
|
+
edges,
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
analyzedAt: new Date().toISOString(),
|
|
123
|
+
};
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error(` Error analyzing project:`, error.message);
|
|
126
|
+
return createEmptyAnalysis(projectPath, gitInfo);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function createEmptyAnalysis(projectPath, gitInfo) {
|
|
131
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(projectPath, 'package.json'), 'utf-8'));
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
project: {
|
|
135
|
+
id: crypto.createHash('md5').update(projectPath).digest('hex').substring(0, 12),
|
|
136
|
+
title: packageJson.name || path.basename(projectPath),
|
|
137
|
+
subtitle: new Date().toLocaleDateString(),
|
|
138
|
+
value: '0 nodes',
|
|
139
|
+
trend: 0,
|
|
140
|
+
chartData: [],
|
|
141
|
+
chartColor: '#6b7280',
|
|
142
|
+
},
|
|
143
|
+
branches: [{
|
|
144
|
+
id: gitInfo.branch.replace(/[^a-zA-Z0-9]/g, '-'),
|
|
145
|
+
name: gitInfo.branch,
|
|
146
|
+
lastCommit: gitInfo.lastCommit,
|
|
147
|
+
lastCommitDate: 'just now',
|
|
148
|
+
author: gitInfo.author,
|
|
149
|
+
status: 'active',
|
|
150
|
+
commitCount: gitInfo.commitCount,
|
|
151
|
+
pullRequests: 0,
|
|
152
|
+
}],
|
|
153
|
+
planData: {},
|
|
154
|
+
analyzedAt: new Date().toISOString(),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function getGitHash(projectPath) {
|
|
159
|
+
try {
|
|
160
|
+
return execSync('git rev-parse --short HEAD', { cwd: projectPath, encoding: 'utf-8' }).trim();
|
|
161
|
+
} catch {
|
|
162
|
+
return 'nogit';
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function getGitInfo(projectPath) {
|
|
167
|
+
try {
|
|
168
|
+
const branch = execSync('git branch --show-current', { cwd: projectPath, encoding: 'utf-8' }).trim() || 'main';
|
|
169
|
+
const lastCommit = execSync('git log -1 --pretty=%s', { cwd: projectPath, encoding: 'utf-8' }).trim() || 'No commits';
|
|
170
|
+
const author = execSync('git log -1 --pretty=%an', { cwd: projectPath, encoding: 'utf-8' }).trim() || 'Unknown';
|
|
171
|
+
const commitCount = parseInt(execSync('git rev-list --count HEAD', { cwd: projectPath, encoding: 'utf-8' }).trim() || '0');
|
|
172
|
+
return { branch, lastCommit, author, commitCount };
|
|
173
|
+
} catch {
|
|
174
|
+
return {
|
|
175
|
+
branch: 'main',
|
|
176
|
+
lastCommit: 'No commits',
|
|
177
|
+
author: 'Unknown',
|
|
178
|
+
commitCount: 0
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function createNodeId(filePath, nodeName, gitHash, counter = 0) {
|
|
184
|
+
const suffix = counter > 0 ? `-${counter}` : '';
|
|
185
|
+
return `${filePath}::${nodeName}${suffix}::${gitHash}`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function getUniqueNodeId(filePath, nodeName, gitHash, usedIds, idCounters) {
|
|
189
|
+
const baseKey = `${filePath}::${nodeName}`;
|
|
190
|
+
const counter = idCounters.get(baseKey) || 0;
|
|
191
|
+
const id = createNodeId(filePath, nodeName, gitHash, counter);
|
|
192
|
+
|
|
193
|
+
if (usedIds.has(id)) {
|
|
194
|
+
idCounters.set(baseKey, counter + 1);
|
|
195
|
+
return getUniqueNodeId(filePath, nodeName, gitHash, usedIds, idCounters);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
usedIds.add(id);
|
|
199
|
+
idCounters.set(baseKey, counter + 1);
|
|
200
|
+
return id;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function isReactComponent(func) {
|
|
204
|
+
const body = func.getBody();
|
|
205
|
+
if (!body) return false;
|
|
206
|
+
|
|
207
|
+
const text = body.getText();
|
|
208
|
+
return text.includes('return') && (text.includes('</') || text.includes('<>') || text.includes('jsx'));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function extractComponents(sourceFile, relativePath, gitHash, baseX, startY, nodes, nodeMap, usedIds, idCounters) {
|
|
212
|
+
let count = 0;
|
|
213
|
+
let y = startY;
|
|
214
|
+
|
|
215
|
+
// Find function components
|
|
216
|
+
const functions = sourceFile.getFunctions();
|
|
217
|
+
for (const func of functions) {
|
|
218
|
+
if (isReactComponent(func)) {
|
|
219
|
+
const name = func.getName() || 'AnonymousComponent';
|
|
220
|
+
const filePath = sourceFile.getFilePath();
|
|
221
|
+
const id = getUniqueNodeId(filePath, name, gitHash, usedIds, idCounters);
|
|
222
|
+
|
|
223
|
+
const params = func.getParameters();
|
|
224
|
+
const propsParam = params[0];
|
|
225
|
+
const propsType = propsParam ? propsParam.getType().getText() : 'any';
|
|
226
|
+
|
|
227
|
+
const node = {
|
|
228
|
+
id,
|
|
229
|
+
type: 'component',
|
|
230
|
+
position: { x: baseX, y: y },
|
|
231
|
+
data: {
|
|
232
|
+
componentName: name,
|
|
233
|
+
props: propsType,
|
|
234
|
+
label: name,
|
|
235
|
+
description: `Component in ${relativePath}`,
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
nodes.push(node);
|
|
240
|
+
nodeMap.set(id, node);
|
|
241
|
+
nodeMap.set(name, node);
|
|
242
|
+
count++;
|
|
243
|
+
y += 150;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Find arrow function components
|
|
248
|
+
const variables = sourceFile.getVariableDeclarations();
|
|
249
|
+
for (const variable of variables) {
|
|
250
|
+
const init = variable.getInitializer();
|
|
251
|
+
if (init && (Node.isArrowFunction(init) || Node.isFunctionExpression(init))) {
|
|
252
|
+
if (isReactComponent(init)) {
|
|
253
|
+
const name = variable.getName();
|
|
254
|
+
const filePath = sourceFile.getFilePath();
|
|
255
|
+
const id = getUniqueNodeId(filePath, name, gitHash, usedIds, idCounters);
|
|
256
|
+
|
|
257
|
+
const params = init.getParameters();
|
|
258
|
+
const propsParam = params[0];
|
|
259
|
+
const propsType = propsParam ? propsParam.getType().getText() : 'any';
|
|
260
|
+
|
|
261
|
+
const node = {
|
|
262
|
+
id,
|
|
263
|
+
type: 'component',
|
|
264
|
+
position: { x: baseX, y: y },
|
|
265
|
+
data: {
|
|
266
|
+
componentName: name,
|
|
267
|
+
props: propsType,
|
|
268
|
+
label: name,
|
|
269
|
+
description: `Component in ${relativePath}`,
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
nodes.push(node);
|
|
274
|
+
nodeMap.set(id, node);
|
|
275
|
+
nodeMap.set(name, node);
|
|
276
|
+
count++;
|
|
277
|
+
y += 150;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return count;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function extractFunctions(sourceFile, relativePath, gitHash, baseX, startY, nodes, nodeMap, usedIds, idCounters) {
|
|
286
|
+
let count = 0;
|
|
287
|
+
let y = startY;
|
|
288
|
+
|
|
289
|
+
const functions = sourceFile.getFunctions();
|
|
290
|
+
for (const func of functions) {
|
|
291
|
+
if (!isReactComponent(func)) {
|
|
292
|
+
const name = func.getName() || 'anonymous';
|
|
293
|
+
if (name === 'anonymous') continue;
|
|
294
|
+
|
|
295
|
+
const filePath = sourceFile.getFilePath();
|
|
296
|
+
const id = getUniqueNodeId(filePath, name, gitHash, usedIds, idCounters);
|
|
297
|
+
|
|
298
|
+
const params = func.getParameters().map((p) => {
|
|
299
|
+
const paramName = p.getName();
|
|
300
|
+
const paramType = p.getType().getText();
|
|
301
|
+
return `${paramName}: ${paramType}`;
|
|
302
|
+
}).join(', ');
|
|
303
|
+
|
|
304
|
+
const returnType = func.getReturnType().getText();
|
|
305
|
+
|
|
306
|
+
const node = {
|
|
307
|
+
id,
|
|
308
|
+
type: 'function',
|
|
309
|
+
position: { x: baseX, y: y },
|
|
310
|
+
data: {
|
|
311
|
+
functionName: name,
|
|
312
|
+
parameters: params,
|
|
313
|
+
returnType: returnType,
|
|
314
|
+
label: name,
|
|
315
|
+
description: `Function in ${relativePath}`,
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
nodes.push(node);
|
|
320
|
+
nodeMap.set(id, node);
|
|
321
|
+
nodeMap.set(name, node);
|
|
322
|
+
count++;
|
|
323
|
+
y += 150;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return count;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function extractState(sourceFile, relativePath, gitHash, baseX, startY, nodes, nodeMap, usedIds, idCounters) {
|
|
331
|
+
let count = 0;
|
|
332
|
+
let y = startY;
|
|
333
|
+
|
|
334
|
+
sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((callExpr) => {
|
|
335
|
+
const expr = callExpr.getExpression();
|
|
336
|
+
if (expr.getText() === 'useState') {
|
|
337
|
+
const parent = callExpr.getParent();
|
|
338
|
+
if (Node.isVariableDeclaration(parent)) {
|
|
339
|
+
const nameNode = parent.getNameNode();
|
|
340
|
+
if (Node.isArrayBindingPattern(nameNode)) {
|
|
341
|
+
const elements = nameNode.getElements();
|
|
342
|
+
if (elements.length > 0) {
|
|
343
|
+
const stateName = elements[0].getText();
|
|
344
|
+
const filePath = sourceFile.getFilePath();
|
|
345
|
+
const id = getUniqueNodeId(filePath, stateName, gitHash, usedIds, idCounters);
|
|
346
|
+
|
|
347
|
+
const args = callExpr.getArguments();
|
|
348
|
+
const initialValue = args.length > 0 ? args[0].getText() : 'undefined';
|
|
349
|
+
const stateType = parent.getType().getText();
|
|
350
|
+
|
|
351
|
+
const node = {
|
|
352
|
+
id,
|
|
353
|
+
type: 'state',
|
|
354
|
+
position: { x: baseX, y: y },
|
|
355
|
+
data: {
|
|
356
|
+
stateName: stateName,
|
|
357
|
+
stateType: stateType,
|
|
358
|
+
initialValue: initialValue,
|
|
359
|
+
label: stateName,
|
|
360
|
+
description: `State in ${relativePath}`,
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
nodes.push(node);
|
|
365
|
+
nodeMap.set(id, node);
|
|
366
|
+
nodeMap.set(stateName, node);
|
|
367
|
+
count++;
|
|
368
|
+
y += 150;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
return count;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function generateEdges(sourceFile, nodeMap, edges) {
|
|
379
|
+
// Find JSX elements (component usage)
|
|
380
|
+
sourceFile.getDescendantsOfKind(SyntaxKind.JsxElement).forEach((jsxElement) => {
|
|
381
|
+
const openingElement = jsxElement.getOpeningElement();
|
|
382
|
+
const tagName = openingElement.getTagNameNode().getText();
|
|
383
|
+
|
|
384
|
+
const containingFunc = jsxElement.getFirstAncestorByKind(SyntaxKind.FunctionDeclaration) ||
|
|
385
|
+
jsxElement.getFirstAncestorByKind(SyntaxKind.ArrowFunction);
|
|
386
|
+
|
|
387
|
+
if (containingFunc) {
|
|
388
|
+
const parentName = containingFunc.getName?.() ||
|
|
389
|
+
containingFunc.getParent()?.getName?.() ||
|
|
390
|
+
null;
|
|
391
|
+
|
|
392
|
+
if (parentName && nodeMap.has(parentName) && nodeMap.has(tagName)) {
|
|
393
|
+
const sourceNode = nodeMap.get(parentName);
|
|
394
|
+
const targetNode = nodeMap.get(tagName);
|
|
395
|
+
|
|
396
|
+
if (sourceNode && targetNode) {
|
|
397
|
+
const edgeId = `e-${sourceNode.id}-${targetNode.id}`;
|
|
398
|
+
if (!edges.find(e => e.id === edgeId)) {
|
|
399
|
+
edges.push({
|
|
400
|
+
id: edgeId,
|
|
401
|
+
source: sourceNode.id,
|
|
402
|
+
target: targetNode.id,
|
|
403
|
+
type: 'floating',
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// Find self-closing JSX elements
|
|
412
|
+
sourceFile.getDescendantsOfKind(SyntaxKind.JsxSelfClosingElement).forEach((jsxElement) => {
|
|
413
|
+
const tagName = jsxElement.getTagNameNode().getText();
|
|
414
|
+
|
|
415
|
+
const containingFunc = jsxElement.getFirstAncestorByKind(SyntaxKind.FunctionDeclaration) ||
|
|
416
|
+
jsxElement.getFirstAncestorByKind(SyntaxKind.ArrowFunction);
|
|
417
|
+
|
|
418
|
+
if (containingFunc) {
|
|
419
|
+
const parentName = containingFunc.getName?.() ||
|
|
420
|
+
containingFunc.getParent()?.getName?.() ||
|
|
421
|
+
null;
|
|
422
|
+
|
|
423
|
+
if (parentName && nodeMap.has(parentName) && nodeMap.has(tagName)) {
|
|
424
|
+
const sourceNode = nodeMap.get(parentName);
|
|
425
|
+
const targetNode = nodeMap.get(tagName);
|
|
426
|
+
|
|
427
|
+
if (sourceNode && targetNode) {
|
|
428
|
+
const edgeId = `e-${sourceNode.id}-${targetNode.id}`;
|
|
429
|
+
if (!edges.find(e => e.id === edgeId)) {
|
|
430
|
+
edges.push({
|
|
431
|
+
id: edgeId,
|
|
432
|
+
source: sourceNode.id,
|
|
433
|
+
target: targetNode.id,
|
|
434
|
+
type: 'floating',
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// Find function calls
|
|
443
|
+
sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((callExpr) => {
|
|
444
|
+
const expr = callExpr.getExpression();
|
|
445
|
+
const calledName = expr.getText().split('.').pop();
|
|
446
|
+
|
|
447
|
+
if (calledName && nodeMap.has(calledName)) {
|
|
448
|
+
const containingFunc = callExpr.getFirstAncestorByKind(SyntaxKind.FunctionDeclaration) ||
|
|
449
|
+
callExpr.getFirstAncestorByKind(SyntaxKind.ArrowFunction);
|
|
450
|
+
|
|
451
|
+
if (containingFunc) {
|
|
452
|
+
const parentName = containingFunc.getName?.() ||
|
|
453
|
+
containingFunc.getParent()?.getName?.() ||
|
|
454
|
+
null;
|
|
455
|
+
|
|
456
|
+
if (parentName && nodeMap.has(parentName)) {
|
|
457
|
+
const sourceNode = nodeMap.get(parentName);
|
|
458
|
+
const targetNode = nodeMap.get(calledName);
|
|
459
|
+
|
|
460
|
+
if (sourceNode && targetNode && sourceNode.id !== targetNode.id) {
|
|
461
|
+
const edgeId = `e-${sourceNode.id}-${targetNode.id}`;
|
|
462
|
+
if (!edges.find(e => e.id === edgeId)) {
|
|
463
|
+
edges.push({
|
|
464
|
+
id: edgeId,
|
|
465
|
+
source: sourceNode.id,
|
|
466
|
+
target: targetNode.id,
|
|
467
|
+
type: 'floating',
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
}
|
package/server.js
CHANGED
|
@@ -2,6 +2,14 @@ import express from 'express';
|
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
3
|
import { dirname, join } from 'path';
|
|
4
4
|
import { existsSync } from 'fs';
|
|
5
|
+
import {
|
|
6
|
+
listProjects,
|
|
7
|
+
getProject,
|
|
8
|
+
readAnalysisData,
|
|
9
|
+
registerProject,
|
|
10
|
+
unregisterProject,
|
|
11
|
+
} from './src/registry.js';
|
|
12
|
+
import { analyzeProject } from './scripts/analyze-project.js';
|
|
5
13
|
|
|
6
14
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
15
|
const __dirname = dirname(__filename);
|
|
@@ -40,6 +48,70 @@ console.log(` Built: ${hasBuilt ? 'Yes' : 'No (using Vite dev)'}`);
|
|
|
40
48
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
41
49
|
console.log('');
|
|
42
50
|
|
|
51
|
+
// Enable JSON body parsing for API routes
|
|
52
|
+
app.use(express.json());
|
|
53
|
+
|
|
54
|
+
// API Routes - These run regardless of build status
|
|
55
|
+
// GET /api/projects - List all registered projects
|
|
56
|
+
app.get('/api/projects', (req, res) => {
|
|
57
|
+
try {
|
|
58
|
+
const projects = listProjects();
|
|
59
|
+
res.json({ projects });
|
|
60
|
+
} catch (error) {
|
|
61
|
+
res.status(500).json({ error: error.message });
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// GET /api/projects/:id - Get specific project
|
|
66
|
+
app.get('/api/projects/:id', (req, res) => {
|
|
67
|
+
try {
|
|
68
|
+
const project = getProject(req.params.id);
|
|
69
|
+
if (!project) {
|
|
70
|
+
return res.status(404).json({ error: 'Project not found' });
|
|
71
|
+
}
|
|
72
|
+
res.json({ project });
|
|
73
|
+
} catch (error) {
|
|
74
|
+
res.status(500).json({ error: error.message });
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// GET /api/projects/:id/analysis - Get project analysis data
|
|
79
|
+
app.get('/api/projects/:id/analysis', (req, res) => {
|
|
80
|
+
try {
|
|
81
|
+
const analysisData = readAnalysisData(req.params.id);
|
|
82
|
+
if (!analysisData) {
|
|
83
|
+
return res.status(404).json({ error: 'Analysis data not found' });
|
|
84
|
+
}
|
|
85
|
+
res.json(analysisData);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
res.status(500).json({ error: error.message });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// POST /api/projects/register - Register a new project
|
|
92
|
+
app.post('/api/projects/register', (req, res) => {
|
|
93
|
+
try {
|
|
94
|
+
const { path } = req.body;
|
|
95
|
+
if (!path) {
|
|
96
|
+
return res.status(400).json({ error: 'Path is required' });
|
|
97
|
+
}
|
|
98
|
+
const project = registerProject(path);
|
|
99
|
+
res.json({ project });
|
|
100
|
+
} catch (error) {
|
|
101
|
+
res.status(400).json({ error: error.message });
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// DELETE /api/projects/:id - Unregister a project
|
|
106
|
+
app.delete('/api/projects/:id', (req, res) => {
|
|
107
|
+
try {
|
|
108
|
+
const project = unregisterProject(req.params.id);
|
|
109
|
+
res.json({ project });
|
|
110
|
+
} catch (error) {
|
|
111
|
+
res.status(404).json({ error: error.message });
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
43
115
|
if (!hasBuilt) {
|
|
44
116
|
// Development mode: Use Vite dev server
|
|
45
117
|
console.log('⚠️ No built files found. Please run:');
|
|
@@ -53,8 +125,12 @@ if (!hasBuilt) {
|
|
|
53
125
|
// Production mode: Serve built files
|
|
54
126
|
app.use(express.static(distPath));
|
|
55
127
|
|
|
56
|
-
// SPA fallback - serve index.html for all routes
|
|
128
|
+
// SPA fallback - serve index.html for all routes (but not API routes)
|
|
57
129
|
app.get('*', (req, res) => {
|
|
130
|
+
// Skip API routes
|
|
131
|
+
if (req.path.startsWith('/api/')) {
|
|
132
|
+
return res.status(404).json({ error: 'API endpoint not found' });
|
|
133
|
+
}
|
|
58
134
|
res.sendFile(join(distPath, 'index.html'));
|
|
59
135
|
});
|
|
60
136
|
|
|
@@ -63,6 +139,7 @@ if (!hasBuilt) {
|
|
|
63
139
|
console.log(` http://localhost:${PORT}`);
|
|
64
140
|
console.log('');
|
|
65
141
|
console.log(`📡 Loading Rayburst app from: ${remoteUrl}`);
|
|
142
|
+
console.log(`📊 API available at: http://localhost:${PORT}/api/projects`);
|
|
66
143
|
console.log('');
|
|
67
144
|
console.log('Press Ctrl+C to stop');
|
|
68
145
|
console.log('');
|