@rayburst/cli 0.2.2 → 0.2.4
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.
|
@@ -8,6 +8,25 @@ import crypto from "crypto";
|
|
|
8
8
|
// src/git-utils.ts
|
|
9
9
|
import { execSync } from "child_process";
|
|
10
10
|
import path from "path";
|
|
11
|
+
function getUncommittedChanges(projectPath) {
|
|
12
|
+
try {
|
|
13
|
+
if (!isGitRepository(projectPath)) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
const command = "git diff --name-only HEAD && git diff --name-only --cached";
|
|
17
|
+
const output = execSync(command, {
|
|
18
|
+
cwd: projectPath,
|
|
19
|
+
encoding: "utf8",
|
|
20
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
21
|
+
// Ignore stderr
|
|
22
|
+
});
|
|
23
|
+
const files = output.split("\n").filter(Boolean).map((file) => path.resolve(projectPath, file.trim()));
|
|
24
|
+
return [...new Set(files)];
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error("Failed to get git diff:", error.message);
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
11
30
|
function isGitRepository(projectPath) {
|
|
12
31
|
try {
|
|
13
32
|
execSync("git rev-parse --is-inside-work-tree", {
|
|
@@ -20,6 +39,27 @@ function isGitRepository(projectPath) {
|
|
|
20
39
|
return false;
|
|
21
40
|
}
|
|
22
41
|
}
|
|
42
|
+
function getUntrackedFiles(projectPath) {
|
|
43
|
+
try {
|
|
44
|
+
if (!isGitRepository(projectPath)) {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
const output = execSync("git ls-files --others --exclude-standard", {
|
|
48
|
+
cwd: projectPath,
|
|
49
|
+
encoding: "utf8",
|
|
50
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
51
|
+
});
|
|
52
|
+
return output.split("\n").filter(Boolean).map((file) => path.resolve(projectPath, file.trim()));
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error("Failed to get untracked files:", error.message);
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function getAllChangedFiles(projectPath) {
|
|
59
|
+
const uncommitted = getUncommittedChanges(projectPath);
|
|
60
|
+
const untracked = getUntrackedFiles(projectPath);
|
|
61
|
+
return [.../* @__PURE__ */ new Set([...uncommitted, ...untracked])];
|
|
62
|
+
}
|
|
23
63
|
function getChangedFilesVsRemote(projectPath, remoteBranch = void 0) {
|
|
24
64
|
try {
|
|
25
65
|
if (!isGitRepository(projectPath)) {
|
|
@@ -74,7 +114,9 @@ function getChangedFilesVsRemote(projectPath, remoteBranch = void 0) {
|
|
|
74
114
|
encoding: "utf8",
|
|
75
115
|
stdio: ["pipe", "pipe", "ignore"]
|
|
76
116
|
});
|
|
77
|
-
|
|
117
|
+
const committedChanges = output.split("\n").filter(Boolean).map((file) => path.resolve(projectPath, file.trim()));
|
|
118
|
+
const uncommittedChanges = getAllChangedFiles(projectPath);
|
|
119
|
+
return [.../* @__PURE__ */ new Set([...committedChanges, ...uncommittedChanges])];
|
|
78
120
|
} catch (error) {
|
|
79
121
|
console.warn("Failed to get changed files vs remote:", error.message);
|
|
80
122
|
return [];
|
|
@@ -267,10 +309,119 @@ function getUniqueNodeId(filePath, nodeName, gitHash, usedIds, idCounters) {
|
|
|
267
309
|
return id;
|
|
268
310
|
}
|
|
269
311
|
function isReactComponent(func) {
|
|
312
|
+
const name = func.getName() || func.getParent()?.asKind?.(SyntaxKind.VariableDeclaration)?.getName?.();
|
|
313
|
+
const hasComponentName = name && /^[A-Z]/.test(name);
|
|
314
|
+
const returnType = func.getReturnType().getText();
|
|
315
|
+
const hasJsxReturnType = returnType.includes("JSX.Element") || returnType.includes("React.ReactElement") || returnType.includes("ReactElement") || returnType.includes("React.ReactNode") || returnType.includes("ReactNode");
|
|
270
316
|
const body = func.getBody();
|
|
271
317
|
if (!body) return false;
|
|
272
|
-
const
|
|
273
|
-
|
|
318
|
+
const hasJsxElements = body.getDescendantsOfKind(SyntaxKind.JsxElement).length > 0 || body.getDescendantsOfKind(SyntaxKind.JsxSelfClosingElement).length > 0 || body.getDescendantsOfKind(SyntaxKind.JsxFragment).length > 0;
|
|
319
|
+
const hasHooks = body.getDescendantsOfKind(SyntaxKind.CallExpression).some((call) => {
|
|
320
|
+
const expr = call.getExpression().getText();
|
|
321
|
+
return expr.startsWith("use") && /^use[A-Z]/.test(expr);
|
|
322
|
+
});
|
|
323
|
+
if (hasJsxElements) return true;
|
|
324
|
+
if (hasJsxReturnType) return true;
|
|
325
|
+
if (hasComponentName && hasHooks) return true;
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
function analyzeReturnStatements(func) {
|
|
329
|
+
const body = func.getBody();
|
|
330
|
+
if (!body) {
|
|
331
|
+
return {
|
|
332
|
+
hasJsxReturn: false,
|
|
333
|
+
hasNullReturn: false,
|
|
334
|
+
hasUndefinedReturn: false,
|
|
335
|
+
returnValueSummary: "void",
|
|
336
|
+
primaryReturnType: "undefined"
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
const returnStatements = body.getDescendantsOfKind(SyntaxKind.ReturnStatement);
|
|
340
|
+
let hasJsxReturn = false;
|
|
341
|
+
let hasNullReturn = false;
|
|
342
|
+
let hasUndefinedReturn = false;
|
|
343
|
+
const returnTypes = [];
|
|
344
|
+
for (const returnStmt of returnStatements) {
|
|
345
|
+
const expr = returnStmt.getExpression();
|
|
346
|
+
if (!expr) {
|
|
347
|
+
hasUndefinedReturn = true;
|
|
348
|
+
returnTypes.push("undefined");
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
if (Node.isJsxElement(expr) || Node.isJsxSelfClosingElement(expr) || Node.isJsxFragment(expr)) {
|
|
352
|
+
hasJsxReturn = true;
|
|
353
|
+
let jsxName = "JSX";
|
|
354
|
+
if (Node.isJsxElement(expr)) {
|
|
355
|
+
jsxName = expr.getOpeningElement().getTagNameNode().getText();
|
|
356
|
+
} else if (Node.isJsxSelfClosingElement(expr)) {
|
|
357
|
+
jsxName = expr.getTagNameNode().getText();
|
|
358
|
+
} else {
|
|
359
|
+
jsxName = "<>";
|
|
360
|
+
}
|
|
361
|
+
returnTypes.push(`<${jsxName}>`);
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
if (expr.getKind() === SyntaxKind.NullKeyword) {
|
|
365
|
+
hasNullReturn = true;
|
|
366
|
+
returnTypes.push("null");
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
if (expr.getKind() === SyntaxKind.Identifier && expr.getText() === "undefined") {
|
|
370
|
+
hasUndefinedReturn = true;
|
|
371
|
+
returnTypes.push("undefined");
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
if (Node.isConditionalExpression(expr)) {
|
|
375
|
+
const whenTrue = expr.getWhenTrue();
|
|
376
|
+
const whenFalse = expr.getWhenFalse();
|
|
377
|
+
const trueIsJsx = Node.isJsxElement(whenTrue) || Node.isJsxSelfClosingElement(whenTrue);
|
|
378
|
+
const falseIsJsx = Node.isJsxElement(whenFalse) || Node.isJsxSelfClosingElement(whenFalse);
|
|
379
|
+
if (trueIsJsx || falseIsJsx) hasJsxReturn = true;
|
|
380
|
+
if (whenTrue.getText() === "null") hasNullReturn = true;
|
|
381
|
+
if (whenFalse.getText() === "null") hasNullReturn = true;
|
|
382
|
+
const trueText = whenTrue.getText().length > 20 ? whenTrue.getText().substring(0, 17) + "..." : whenTrue.getText();
|
|
383
|
+
const falseText = whenFalse.getText().length > 20 ? whenFalse.getText().substring(0, 17) + "..." : whenFalse.getText();
|
|
384
|
+
returnTypes.push(`${trueText} : ${falseText}`);
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
const exprText = expr.getText();
|
|
388
|
+
const truncated = exprText.length > 50 ? exprText.substring(0, 47) + "..." : exprText;
|
|
389
|
+
returnTypes.push(truncated);
|
|
390
|
+
}
|
|
391
|
+
let primaryReturnType = "mixed";
|
|
392
|
+
if (hasJsxReturn && !hasNullReturn && !hasUndefinedReturn) {
|
|
393
|
+
primaryReturnType = "jsx";
|
|
394
|
+
} else if (hasNullReturn && !hasJsxReturn && !hasUndefinedReturn) {
|
|
395
|
+
primaryReturnType = "null";
|
|
396
|
+
} else if (hasUndefinedReturn && !hasJsxReturn && !hasNullReturn) {
|
|
397
|
+
primaryReturnType = "undefined";
|
|
398
|
+
} else if (returnTypes.length === 1) {
|
|
399
|
+
primaryReturnType = "primitive";
|
|
400
|
+
}
|
|
401
|
+
const uniqueReturns = Array.from(new Set(returnTypes));
|
|
402
|
+
const returnValueSummary = uniqueReturns.join(" | ") || "void";
|
|
403
|
+
return {
|
|
404
|
+
hasJsxReturn,
|
|
405
|
+
hasNullReturn,
|
|
406
|
+
hasUndefinedReturn,
|
|
407
|
+
returnValueSummary,
|
|
408
|
+
primaryReturnType
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
function getReturnDescription(returnAnalysis) {
|
|
412
|
+
if (returnAnalysis.hasJsxReturn && returnAnalysis.hasNullReturn) {
|
|
413
|
+
return "Returns JSX or null";
|
|
414
|
+
}
|
|
415
|
+
if (returnAnalysis.hasJsxReturn) {
|
|
416
|
+
return "Returns JSX";
|
|
417
|
+
}
|
|
418
|
+
if (returnAnalysis.hasNullReturn) {
|
|
419
|
+
return "Returns null";
|
|
420
|
+
}
|
|
421
|
+
if (returnAnalysis.hasUndefinedReturn) {
|
|
422
|
+
return "Returns void";
|
|
423
|
+
}
|
|
424
|
+
return "Returns value";
|
|
274
425
|
}
|
|
275
426
|
function extractComponents(sourceFile, relativePath, gitHash, baseX, startY, nodes, nodeMap, usedIds, idCounters) {
|
|
276
427
|
let count = 0;
|
|
@@ -283,6 +434,7 @@ function extractComponents(sourceFile, relativePath, gitHash, baseX, startY, nod
|
|
|
283
434
|
const params = func.getParameters();
|
|
284
435
|
const propsParam = params[0];
|
|
285
436
|
const propsType = propsParam ? propsParam.getType().getText() : "any";
|
|
437
|
+
const returnAnalysis = analyzeReturnStatements(func);
|
|
286
438
|
const node = {
|
|
287
439
|
id,
|
|
288
440
|
type: "component",
|
|
@@ -291,7 +443,12 @@ function extractComponents(sourceFile, relativePath, gitHash, baseX, startY, nod
|
|
|
291
443
|
componentName: name,
|
|
292
444
|
props: propsType,
|
|
293
445
|
label: name,
|
|
294
|
-
description: `Component in ${relativePath}
|
|
446
|
+
description: `Component in ${relativePath}`,
|
|
447
|
+
returnValueSummary: returnAnalysis.returnValueSummary,
|
|
448
|
+
returnDescription: getReturnDescription(returnAnalysis),
|
|
449
|
+
hasJsxReturn: returnAnalysis.hasJsxReturn,
|
|
450
|
+
hasNullReturn: returnAnalysis.hasNullReturn,
|
|
451
|
+
primaryReturnType: returnAnalysis.primaryReturnType
|
|
295
452
|
}
|
|
296
453
|
};
|
|
297
454
|
nodes.push(node);
|
|
@@ -311,6 +468,7 @@ function extractComponents(sourceFile, relativePath, gitHash, baseX, startY, nod
|
|
|
311
468
|
const params = init.getParameters();
|
|
312
469
|
const propsParam = params[0];
|
|
313
470
|
const propsType = propsParam ? propsParam.getType().getText() : "any";
|
|
471
|
+
const returnAnalysis = analyzeReturnStatements(init);
|
|
314
472
|
const node = {
|
|
315
473
|
id,
|
|
316
474
|
type: "component",
|
|
@@ -319,7 +477,12 @@ function extractComponents(sourceFile, relativePath, gitHash, baseX, startY, nod
|
|
|
319
477
|
componentName: name,
|
|
320
478
|
props: propsType,
|
|
321
479
|
label: name,
|
|
322
|
-
description: `Component in ${relativePath}
|
|
480
|
+
description: `Component in ${relativePath}`,
|
|
481
|
+
returnValueSummary: returnAnalysis.returnValueSummary,
|
|
482
|
+
returnDescription: getReturnDescription(returnAnalysis),
|
|
483
|
+
hasJsxReturn: returnAnalysis.hasJsxReturn,
|
|
484
|
+
hasNullReturn: returnAnalysis.hasNullReturn,
|
|
485
|
+
primaryReturnType: returnAnalysis.primaryReturnType
|
|
323
486
|
}
|
|
324
487
|
};
|
|
325
488
|
nodes.push(node);
|
|
@@ -347,6 +510,7 @@ function extractFunctions(sourceFile, relativePath, gitHash, baseX, startY, node
|
|
|
347
510
|
return `${paramName}: ${paramType}`;
|
|
348
511
|
}).join(", ");
|
|
349
512
|
const returnType = func.getReturnType().getText();
|
|
513
|
+
const returnAnalysis = analyzeReturnStatements(func);
|
|
350
514
|
const node = {
|
|
351
515
|
id,
|
|
352
516
|
type: "function",
|
|
@@ -356,7 +520,12 @@ function extractFunctions(sourceFile, relativePath, gitHash, baseX, startY, node
|
|
|
356
520
|
parameters: params,
|
|
357
521
|
returnType,
|
|
358
522
|
label: name,
|
|
359
|
-
description: `Function in ${relativePath}
|
|
523
|
+
description: `Function in ${relativePath}`,
|
|
524
|
+
returnValueSummary: returnAnalysis.returnValueSummary,
|
|
525
|
+
returnDescription: getReturnDescription(returnAnalysis),
|
|
526
|
+
hasJsxReturn: returnAnalysis.hasJsxReturn,
|
|
527
|
+
hasNullReturn: returnAnalysis.hasNullReturn,
|
|
528
|
+
primaryReturnType: returnAnalysis.primaryReturnType
|
|
360
529
|
}
|
|
361
530
|
};
|
|
362
531
|
nodes.push(node);
|
package/dist/index.js
CHANGED
package/dist/vite-plugin.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rayburst/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Rayburst - Automatic code analysis for TypeScript/JavaScript projects via Vite plugin",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -42,12 +42,12 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@rayburst/types": "^0.1.3",
|
|
46
45
|
"chalk": "^5.3.0",
|
|
47
46
|
"ts-morph": "^21.0.1",
|
|
48
47
|
"zod": "^4.2.0"
|
|
49
48
|
},
|
|
50
49
|
"devDependencies": {
|
|
50
|
+
"@rayburst/types": "^0.1.4",
|
|
51
51
|
"@types/node": "^25.0.2",
|
|
52
52
|
"tsup": "^8.5.1",
|
|
53
53
|
"typescript": "^5.9.3",
|