@rayburst/cli 0.1.18 → 0.2.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.
@@ -1,475 +0,0 @@
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 DELETED
@@ -1,147 +0,0 @@
1
- import express from 'express';
2
- import { fileURLToPath } from 'url';
3
- import { dirname, join } from 'path';
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';
13
-
14
- const __filename = fileURLToPath(import.meta.url);
15
- const __dirname = dirname(__filename);
16
-
17
- const app = express();
18
- const PORT = process.env.PORT || 3105;
19
- const NODE_ENV = process.env.NODE_ENV || 'development';
20
-
21
- // Determine which directory to serve
22
- const distPath = join(__dirname, 'dist');
23
- const hasBuilt = existsSync(distPath);
24
-
25
- // Environment-specific remote URLs
26
- function getRemoteUrl(env) {
27
- switch (env) {
28
- case 'production':
29
- return 'https://www.rayburst.app';
30
- case 'staging':
31
- return 'https://dev.rayburst.app';
32
- case 'development':
33
- default:
34
- return 'http://localhost:3000';
35
- }
36
- }
37
-
38
- const remoteUrl = getRemoteUrl(NODE_ENV);
39
-
40
- // Log startup information
41
- console.log('');
42
- console.log('🚀 Rayburst CLI Server');
43
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
44
- console.log(` Environment: ${NODE_ENV}`);
45
- console.log(` Port: ${PORT}`);
46
- console.log(` Remote URL: ${remoteUrl}`);
47
- console.log(` Built: ${hasBuilt ? 'Yes' : 'No (using Vite dev)'}`);
48
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
49
- console.log('');
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
-
115
- if (!hasBuilt) {
116
- // Development mode: Use Vite dev server
117
- console.log('⚠️ No built files found. Please run:');
118
- console.log(' cd cli && npm run build');
119
- console.log('');
120
- console.log(' Or use Vite dev server:');
121
- console.log(' cd cli && npx vite --port 3105');
122
- console.log('');
123
- process.exit(1);
124
- } else {
125
- // Production mode: Serve built files
126
- app.use(express.static(distPath));
127
-
128
- // SPA fallback - serve index.html for all routes (but not API routes)
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
- }
134
- res.sendFile(join(distPath, 'index.html'));
135
- });
136
-
137
- app.listen(PORT, '0.0.0.0', () => {
138
- console.log(`✅ Server running at:`);
139
- console.log(` http://localhost:${PORT}`);
140
- console.log('');
141
- console.log(`📡 Loading Rayburst app from: ${remoteUrl}`);
142
- console.log(`📊 API available at: http://localhost:${PORT}/api/projects`);
143
- console.log('');
144
- console.log('Press Ctrl+C to stop');
145
- console.log('');
146
- });
147
- }