projectify-cli 1.0.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.
- package/README.md +94 -0
- package/dist/ai/index.js +100 -0
- package/dist/ai/providers/base.js +2 -0
- package/dist/ai/providers/gemini.js +56 -0
- package/dist/ai/providers/ollama.js +50 -0
- package/dist/ai/providers/openai.js +72 -0
- package/dist/analyzer/index.js +34 -0
- package/dist/analyzer/parsers/javascript.js +137 -0
- package/dist/analyzer/parsers/python.js +44 -0
- package/dist/graph/index.js +126 -0
- package/dist/index.js +136 -0
- package/dist/report/htmlGenerator.js +582 -0
- package/dist/scanner/index.js +29 -0
- package/dist/utils/fileUtils.js +24 -0
- package/dist/utils/gitUtils.js +47 -0
- package/functions.png +0 -0
- package/graph.png +0 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Projectify
|
|
2
|
+
|
|
3
|
+
> **Autonomous Code Analysis & Visualization Engine**
|
|
4
|
+
|
|
5
|
+
Projectify is a powerful, standalone Node.js tool designed to analyze codebase architectures, calculate dependency impacts ("Blast Radius"), and generate stunning, interactive visualizations. Built with TypeScript and powered by AI.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **AI-Powered Insights**: Uses OpenAI (LangChain) to summarize architecture and explain high-risk components.
|
|
13
|
+
- **Blast Radius Calculation**: Instantly identify the most critical files in your project based on dependency impact.
|
|
14
|
+
- **Interactive Visualization**: Generates a premium, Shadcn-style HTML report with physics-based graphs and search.
|
|
15
|
+
- **Multi-Language Support**:
|
|
16
|
+
- JavaScript / TypeScript (AST-based)
|
|
17
|
+
- Python (Regex/Heuristic-based)
|
|
18
|
+
- **Fast & Standalone**: optimized for speed, zero external Python dependencies.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Clone the repository
|
|
24
|
+
git clone https://github.com/your-repo/projectify.git
|
|
25
|
+
|
|
26
|
+
# Install dependencies
|
|
27
|
+
cd projectify-cli
|
|
28
|
+
npm install
|
|
29
|
+
|
|
30
|
+
# Build the project
|
|
31
|
+
npm run build
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
### Using npx (Recommended)
|
|
37
|
+
|
|
38
|
+
Run directly without installing:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx projectify-cli [path] [options]
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Global Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm install -g projectify-cli
|
|
48
|
+
|
|
49
|
+
# Run anywhere
|
|
50
|
+
projectify [path] [options]
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Local Development
|
|
54
|
+
|
|
55
|
+
Run the analyzer on any project directory:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Basic Analysis
|
|
59
|
+
npm start -- /path/to/target/project
|
|
60
|
+
|
|
61
|
+
# Analysis with AI Summary (Requires API Key)
|
|
62
|
+
export OPENAI_API_KEY=sk-your-key-here
|
|
63
|
+
export GEMINI_API_KEY=sk-your-key-here
|
|
64
|
+
export OLLAMA_API_KEY=sk-your-key-here
|
|
65
|
+
npm start -- /path/to/target/project --summary
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### CLI Options
|
|
69
|
+
- `path`: (Optional) Path to the project to analyze. Defaults to current directory.
|
|
70
|
+
- `--summary`: Enable AI-powered project summarization.
|
|
71
|
+
- `--no-ai`: Disable individual file AI analysis.
|
|
72
|
+
|
|
73
|
+
## Output
|
|
74
|
+
|
|
75
|
+
All reports are generated in the `projectify-cli` directory:
|
|
76
|
+
|
|
77
|
+
| File | Description |
|
|
78
|
+
|------|-------------|
|
|
79
|
+
| `analysis-report.html` | **Interactive Dashboard**. View dependency graphs, search nodes, and see detailed stats. |
|
|
80
|
+
| `analysis-report.json` | **Raw Data**. Complete dependency graph and metrics in JSON format. |
|
|
81
|
+
| `project-summary.md` | **AI Report**. High-level architecture overview and risk assessment. |
|
|
82
|
+
|
|
83
|
+
## Architecture
|
|
84
|
+
|
|
85
|
+
- **Scanner**: Fast-glob based file traversal.
|
|
86
|
+
- **Analyzer**: Babel-based AST parsing for JS/TS; Custom regex parser for Python.
|
|
87
|
+
- **Graph Engine**: Directed graph construction with cycle detection and impact scoring.
|
|
88
|
+
- **UI**: Customized `vis-network` with Tailwind CSS (Shadcn/Zinc theme).
|
|
89
|
+
|
|
90
|
+
## Developer
|
|
91
|
+
|
|
92
|
+
Developed by **GreenHacker**.
|
|
93
|
+
|
|
94
|
+
|
package/dist/ai/index.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CodeIntelligence = void 0;
|
|
4
|
+
const openai_1 = require("@langchain/openai");
|
|
5
|
+
const google_genai_1 = require("@langchain/google-genai");
|
|
6
|
+
const ollama_1 = require("@langchain/ollama");
|
|
7
|
+
const prebuilt_1 = require("@langchain/langgraph/prebuilt");
|
|
8
|
+
const langgraph_1 = require("@langchain/langgraph");
|
|
9
|
+
const messages_1 = require("@langchain/core/messages");
|
|
10
|
+
class CodeIntelligence {
|
|
11
|
+
constructor(provider, apiKey, modelName) {
|
|
12
|
+
let model;
|
|
13
|
+
// Initialize Model based on Provider
|
|
14
|
+
if (provider === 'openai') {
|
|
15
|
+
if (!apiKey)
|
|
16
|
+
throw new Error("OpenAI API Key required");
|
|
17
|
+
model = new openai_1.ChatOpenAI({
|
|
18
|
+
apiKey,
|
|
19
|
+
modelName: modelName || 'gpt-4o',
|
|
20
|
+
temperature: 0
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
else if (provider === 'gemini') {
|
|
24
|
+
if (!apiKey)
|
|
25
|
+
throw new Error("Gemini API Key required");
|
|
26
|
+
model = new google_genai_1.ChatGoogleGenerativeAI({
|
|
27
|
+
apiKey,
|
|
28
|
+
model: modelName || 'gemini-1.5-flash',
|
|
29
|
+
temperature: 0
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
else if (provider === 'ollama') {
|
|
33
|
+
model = new ollama_1.ChatOllama({
|
|
34
|
+
baseUrl: 'http://localhost:11434',
|
|
35
|
+
model: modelName || 'llama3',
|
|
36
|
+
temperature: 0
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
throw new Error(`Unsupported provider: ${provider}`);
|
|
41
|
+
}
|
|
42
|
+
// Initialize Memory
|
|
43
|
+
const checkpointer = new langgraph_1.MemorySaver();
|
|
44
|
+
// Create Agent
|
|
45
|
+
this.agent = (0, prebuilt_1.createReactAgent)({
|
|
46
|
+
llm: model,
|
|
47
|
+
tools: [],
|
|
48
|
+
checkpointSaver: checkpointer
|
|
49
|
+
});
|
|
50
|
+
// Config for thread persistence
|
|
51
|
+
this.config = { configurable: { thread_id: "project-analysis-session" } };
|
|
52
|
+
}
|
|
53
|
+
async askAgent(systemPrompt, userContent) {
|
|
54
|
+
const result = await this.agent.invoke({
|
|
55
|
+
messages: [
|
|
56
|
+
new messages_1.SystemMessage(systemPrompt),
|
|
57
|
+
new messages_1.HumanMessage(userContent)
|
|
58
|
+
]
|
|
59
|
+
}, this.config);
|
|
60
|
+
const lastMessage = result.messages[result.messages.length - 1];
|
|
61
|
+
return lastMessage.content;
|
|
62
|
+
}
|
|
63
|
+
async summarizeFile(fileName, content) {
|
|
64
|
+
return this.askAgent('You are an expert code analyst. Summarize the purpose of this file in 1-2 sentences.', `File: ${fileName}\n\nCode:\n${content.substring(0, 5000)}`);
|
|
65
|
+
}
|
|
66
|
+
async analyzeBlastRadius(node) {
|
|
67
|
+
return this.askAgent('You are an expert software architect. Explain why this file has a high blast radius and the risks involved.', `File: ${node.id} is imported by ${node.affectedFiles} other files (${node.blastRadius.toFixed(2)}% of the codebase).`);
|
|
68
|
+
}
|
|
69
|
+
async generateProjectSummary(fileCount, topRisks, files) {
|
|
70
|
+
return this.askAgent('You are a technical lead. Provide a high-level summary of this project based on the file statistics and high-risk components.', `
|
|
71
|
+
Project Statistics:
|
|
72
|
+
- Total Files: ${fileCount}
|
|
73
|
+
|
|
74
|
+
Key Components (High Dependency/Risk):
|
|
75
|
+
${topRisks.map(n => `- ${n.id} (Impacts ${n.affectedFiles} files)`).join('\n')}
|
|
76
|
+
|
|
77
|
+
File List Sample:
|
|
78
|
+
${files.slice(0, 50).join('\n')}
|
|
79
|
+
|
|
80
|
+
Please provide:
|
|
81
|
+
1. An estimated architecture type.
|
|
82
|
+
2. Key risks.
|
|
83
|
+
3. Recommendations.
|
|
84
|
+
`);
|
|
85
|
+
}
|
|
86
|
+
async analyzeGitHistory(gitStats) {
|
|
87
|
+
return this.askAgent('You are a technical lead analyzing project evolution. Provide insights based on commit history.', `
|
|
88
|
+
Git History Statistics:
|
|
89
|
+
- Total Commits: ${gitStats.totalCommits}
|
|
90
|
+
- Active Authors: ${gitStats.authroStats.length}
|
|
91
|
+
|
|
92
|
+
Top Authors:
|
|
93
|
+
${gitStats.authroStats.slice(0, 5).map((a) => `- ${a.name}: ${a.commits} commits`).join('\n')}
|
|
94
|
+
|
|
95
|
+
Recent Activity:
|
|
96
|
+
${gitStats.recentActivity.join('\n')}
|
|
97
|
+
`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
exports.CodeIntelligence = CodeIntelligence;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GeminiProvider = void 0;
|
|
4
|
+
const generative_ai_1 = require("@google/generative-ai");
|
|
5
|
+
class GeminiProvider {
|
|
6
|
+
constructor(apiKey, modelName = 'gemini-1.5-flash') {
|
|
7
|
+
this.genAI = new generative_ai_1.GoogleGenerativeAI(apiKey);
|
|
8
|
+
this.model = this.genAI.getGenerativeModel({ model: modelName });
|
|
9
|
+
}
|
|
10
|
+
async generateText(prompt) {
|
|
11
|
+
const result = await this.model.generateContent(prompt);
|
|
12
|
+
const response = await result.response;
|
|
13
|
+
return response.text();
|
|
14
|
+
}
|
|
15
|
+
async summarizeFile(fileName, content) {
|
|
16
|
+
return this.generateText(`You are an expert code analyst. Summarize the purpose of this file in 1-2 sentencs.\n\nFile: ${fileName}\n\nCode:\n${content.substring(0, 10000)}`);
|
|
17
|
+
}
|
|
18
|
+
async analyzeBlastRadius(riskNode) {
|
|
19
|
+
return this.generateText(`You are an expert software architect. Explain why this file has a high blast radius and the risks involved.\n\nFile: ${riskNode.id} is imported by ${riskNode.affectedFiles} other files (${riskNode.blastRadius.toFixed(2)}% of the codebase).`);
|
|
20
|
+
}
|
|
21
|
+
async generateProjectSummary(fileCount, topRisks, files) {
|
|
22
|
+
return this.generateText(`You are a technical lead. Provide a high-level summary of this project based on the file statistics and high-risk components.\n\n
|
|
23
|
+
Project Statistics:
|
|
24
|
+
- Total Files: ${fileCount}
|
|
25
|
+
|
|
26
|
+
Key Components (High Dependency/Risk):
|
|
27
|
+
${topRisks.map(n => `- ${n.id} (Impacts ${n.affectedFiles} files)`).join('\n')}
|
|
28
|
+
|
|
29
|
+
File List Sample:
|
|
30
|
+
${files.slice(0, 50).join('\n')}
|
|
31
|
+
|
|
32
|
+
Please provide:
|
|
33
|
+
1. An estimated architecture type (CLI, Web App, Library, etc.).
|
|
34
|
+
2. Key risks based on the high-dependency components.
|
|
35
|
+
3. Recommendations for improvement.`);
|
|
36
|
+
}
|
|
37
|
+
async analyzeGitHistory(gitStats) {
|
|
38
|
+
return this.generateText(`You are a technical lead analyzing project evolution. Provide insights based on commit history.
|
|
39
|
+
|
|
40
|
+
Git History Statistics:
|
|
41
|
+
- Total Commits: ${gitStats.totalCommits}
|
|
42
|
+
- Active Authors: ${gitStats.authroStats.length}
|
|
43
|
+
|
|
44
|
+
Top Authors:
|
|
45
|
+
${gitStats.authroStats.slice(0, 5).map((a) => `- ${a.name}: ${a.commits} commits`).join('\n')}
|
|
46
|
+
|
|
47
|
+
Recent Activity:
|
|
48
|
+
${gitStats.recentActivity.join('\n')}
|
|
49
|
+
|
|
50
|
+
Please provide:
|
|
51
|
+
1. Assessment of project activity (Active/Stale).
|
|
52
|
+
2. Key contributors analysis.
|
|
53
|
+
3. Recent development focus based on commit messages.`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.GeminiProvider = GeminiProvider;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.OllamaProvider = void 0;
|
|
7
|
+
const node_fetch_1 = __importDefault(require("node-fetch")); // Ensure node-fetch is available or use global fetch if on Node 18+
|
|
8
|
+
class OllamaProvider {
|
|
9
|
+
constructor(modelName = 'llama3', baseUrl = 'http://localhost:11434') {
|
|
10
|
+
this.modelName = modelName;
|
|
11
|
+
this.baseUrl = baseUrl;
|
|
12
|
+
}
|
|
13
|
+
async generate(prompt) {
|
|
14
|
+
try {
|
|
15
|
+
const response = await (0, node_fetch_1.default)(`${this.baseUrl}/api/generate`, {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: { 'Content-Type': 'application/json' },
|
|
18
|
+
body: JSON.stringify({
|
|
19
|
+
model: this.modelName,
|
|
20
|
+
prompt: prompt,
|
|
21
|
+
stream: false
|
|
22
|
+
})
|
|
23
|
+
});
|
|
24
|
+
const data = await response.json();
|
|
25
|
+
return data.response;
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
console.error('Ollama generation failed:', error);
|
|
29
|
+
return 'Ollama analysis failed. Ensure Ollama is running.';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async summarizeFile(fileName, content) {
|
|
33
|
+
return this.generate(`Summarize this file in 1-2 sentences:\nFile: ${fileName}\nCode:\n${content.substring(0, 2000)}`);
|
|
34
|
+
}
|
|
35
|
+
async analyzeBlastRadius(riskNode) {
|
|
36
|
+
return this.generate(`Explain high blast radius risks for: ${riskNode.id} (Imported by ${riskNode.affectedFiles} files).`);
|
|
37
|
+
}
|
|
38
|
+
async generateProjectSummary(fileCount, topRisks, files) {
|
|
39
|
+
return this.generate(`Summarize project architecture and risks based on:
|
|
40
|
+
${fileCount} files.
|
|
41
|
+
Top Risks: ${topRisks.map(n => n.id).join(', ')}.`);
|
|
42
|
+
}
|
|
43
|
+
async analyzeGitHistory(gitStats) {
|
|
44
|
+
return this.generate(`Analyze project evolution based on git history:
|
|
45
|
+
Total Commits: ${gitStats.totalCommits}.
|
|
46
|
+
Recent Activity: ${gitStats.recentActivity.join('\n')}.
|
|
47
|
+
Provide insights on activity and focus.`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
exports.OllamaProvider = OllamaProvider;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OpenAIProvider = void 0;
|
|
4
|
+
const openai_1 = require("@langchain/openai");
|
|
5
|
+
const messages_1 = require("@langchain/core/messages");
|
|
6
|
+
class OpenAIProvider {
|
|
7
|
+
constructor(apiKey, modelName = 'gpt-4o') {
|
|
8
|
+
this.model = new openai_1.ChatOpenAI({
|
|
9
|
+
openAIApiKey: apiKey,
|
|
10
|
+
temperature: 0,
|
|
11
|
+
modelName
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
async summarizeFile(fileName, content) {
|
|
15
|
+
const response = await this.model.invoke([
|
|
16
|
+
new messages_1.SystemMessage('You are an expert code analyst. Summarize the purpose of this file in 1-2 sentencs.'),
|
|
17
|
+
new messages_1.HumanMessage(`File: ${fileName}\n\nCode:\n${content.substring(0, 4000)}`)
|
|
18
|
+
]);
|
|
19
|
+
return response.content;
|
|
20
|
+
}
|
|
21
|
+
async analyzeBlastRadius(riskNode) {
|
|
22
|
+
const response = await this.model.invoke([
|
|
23
|
+
new messages_1.SystemMessage('You are an expert software architect. Explain why this file has a high blast radius and the risks involved.'),
|
|
24
|
+
new messages_1.HumanMessage(`File: ${riskNode.id} is imported by ${riskNode.affectedFiles} other files (${riskNode.blastRadius.toFixed(2)}% of the codebase).`)
|
|
25
|
+
]);
|
|
26
|
+
return response.content;
|
|
27
|
+
}
|
|
28
|
+
async generateProjectSummary(fileCount, topRisks, files) {
|
|
29
|
+
const response = await this.model.invoke([
|
|
30
|
+
new messages_1.SystemMessage('You are a technical lead. Provide a high-level summary of this project based on the file statistics and high-risk components.'),
|
|
31
|
+
new messages_1.HumanMessage(`
|
|
32
|
+
Project Statistics:
|
|
33
|
+
- Total Files: ${fileCount}
|
|
34
|
+
|
|
35
|
+
Key Components (High Dependency/Risk):
|
|
36
|
+
${topRisks.map(n => `- ${n.id} (Impacts ${n.affectedFiles} files)`).join('\n')}
|
|
37
|
+
|
|
38
|
+
File List Sample:
|
|
39
|
+
${files.slice(0, 50).join('\n')}
|
|
40
|
+
|
|
41
|
+
Please provide:
|
|
42
|
+
1. An estimated architecture type (CLI, Web App, Library, etc.).
|
|
43
|
+
2. Key risks based on the high-dependency components.
|
|
44
|
+
3. Recommendations for improvement.
|
|
45
|
+
`)
|
|
46
|
+
]);
|
|
47
|
+
return response.content;
|
|
48
|
+
}
|
|
49
|
+
async analyzeGitHistory(gitStats) {
|
|
50
|
+
const response = await this.model.invoke([
|
|
51
|
+
new messages_1.SystemMessage('You are a technical lead analyzing project evolution. Provide insights based on commit history.'),
|
|
52
|
+
new messages_1.HumanMessage(`
|
|
53
|
+
Git History Statistics:
|
|
54
|
+
- Total Commits: ${gitStats.totalCommits}
|
|
55
|
+
- Active Authors: ${gitStats.authroStats.length}
|
|
56
|
+
|
|
57
|
+
Top Authors:
|
|
58
|
+
${gitStats.authroStats.slice(0, 5).map((a) => `- ${a.name}: ${a.commits} commits`).join('\n')}
|
|
59
|
+
|
|
60
|
+
Recent Activity:
|
|
61
|
+
${gitStats.recentActivity.join('\n')}
|
|
62
|
+
|
|
63
|
+
Please provide:
|
|
64
|
+
1. Assessment of project activity (Active/Stale).
|
|
65
|
+
2. Key contributors analysis.
|
|
66
|
+
3. Recent development focus based on commit messages.
|
|
67
|
+
`)
|
|
68
|
+
]);
|
|
69
|
+
return response.content;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
exports.OpenAIProvider = OpenAIProvider;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.analyzeFiles = analyzeFiles;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const fileUtils_1 = require("../utils/fileUtils");
|
|
9
|
+
const javascript_1 = require("./parsers/javascript");
|
|
10
|
+
const python_1 = require("./parsers/python");
|
|
11
|
+
async function analyzeFiles(filePaths) {
|
|
12
|
+
const analysis = {
|
|
13
|
+
fileCount: filePaths.length,
|
|
14
|
+
files: {}
|
|
15
|
+
};
|
|
16
|
+
for (const filePath of filePaths) {
|
|
17
|
+
const content = await (0, fileUtils_1.readFileSafe)(filePath);
|
|
18
|
+
if (!content)
|
|
19
|
+
continue;
|
|
20
|
+
const ext = path_1.default.extname(filePath);
|
|
21
|
+
let fileResult = { imports: [], exports: [], functions: [], classes: [] };
|
|
22
|
+
if (['.js', '.jsx', '.ts', '.tsx'].includes(ext)) {
|
|
23
|
+
fileResult = (0, javascript_1.parseJS)(content, filePath);
|
|
24
|
+
}
|
|
25
|
+
else if (['.py'].includes(ext)) {
|
|
26
|
+
fileResult = (0, python_1.parsePython)(content, filePath);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
// TODO: Add generic regex parser
|
|
30
|
+
}
|
|
31
|
+
analysis.files[filePath] = fileResult;
|
|
32
|
+
}
|
|
33
|
+
return analysis;
|
|
34
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.parseJS = parseJS;
|
|
40
|
+
const parser_1 = require("@babel/parser");
|
|
41
|
+
const traverse_1 = __importDefault(require("@babel/traverse"));
|
|
42
|
+
const t = __importStar(require("@babel/types"));
|
|
43
|
+
const path_1 = __importDefault(require("path"));
|
|
44
|
+
function parseJS(content, filePath) {
|
|
45
|
+
const analysis = {
|
|
46
|
+
imports: [],
|
|
47
|
+
exports: [],
|
|
48
|
+
functions: [],
|
|
49
|
+
classes: []
|
|
50
|
+
};
|
|
51
|
+
try {
|
|
52
|
+
const isTs = filePath.endsWith('.ts') || filePath.endsWith('.tsx');
|
|
53
|
+
const ast = (0, parser_1.parse)(content, {
|
|
54
|
+
sourceType: 'module',
|
|
55
|
+
errorRecovery: true,
|
|
56
|
+
plugins: [
|
|
57
|
+
'jsx',
|
|
58
|
+
'typescript',
|
|
59
|
+
'asyncGenerators',
|
|
60
|
+
'bigInt',
|
|
61
|
+
'classProperties',
|
|
62
|
+
'classPrivateProperties',
|
|
63
|
+
'classPrivateMethods',
|
|
64
|
+
'decorators-legacy',
|
|
65
|
+
'doExpressions',
|
|
66
|
+
'dynamicImport',
|
|
67
|
+
'exportDefaultFrom',
|
|
68
|
+
'exportNamespaceFrom',
|
|
69
|
+
'functionBind',
|
|
70
|
+
'functionSent',
|
|
71
|
+
'importMeta',
|
|
72
|
+
'logicalAssignment',
|
|
73
|
+
'nullishCoalescingOperator',
|
|
74
|
+
'numericSeparator',
|
|
75
|
+
'objectRestSpread',
|
|
76
|
+
'optionalCatchBinding',
|
|
77
|
+
'optionalChaining',
|
|
78
|
+
'partialApplication'
|
|
79
|
+
]
|
|
80
|
+
});
|
|
81
|
+
(0, traverse_1.default)(ast, {
|
|
82
|
+
ImportDeclaration(path) {
|
|
83
|
+
analysis.imports.push(path.node.source.value);
|
|
84
|
+
},
|
|
85
|
+
ExportNamedDeclaration(path) {
|
|
86
|
+
if (path.node.declaration) {
|
|
87
|
+
if (t.isFunctionDeclaration(path.node.declaration) && path.node.declaration.id) {
|
|
88
|
+
analysis.exports.push(path.node.declaration.id.name);
|
|
89
|
+
}
|
|
90
|
+
else if (t.isVariableDeclaration(path.node.declaration)) {
|
|
91
|
+
path.node.declaration.declarations.forEach(d => {
|
|
92
|
+
if (t.isIdentifier(d.id)) {
|
|
93
|
+
analysis.exports.push(d.id.name);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
else if (t.isClassDeclaration(path.node.declaration) && path.node.declaration.id) {
|
|
98
|
+
analysis.exports.push(path.node.declaration.id.name);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
FunctionDeclaration(path) {
|
|
103
|
+
if (path.node.id && path.node.loc) {
|
|
104
|
+
const start = path.node.loc.start.line - 1;
|
|
105
|
+
const end = path.node.loc.end.line;
|
|
106
|
+
const code = content.split('\n').slice(start, end).join('\n');
|
|
107
|
+
const params = path.node.params.map(p => {
|
|
108
|
+
if (t.isIdentifier(p))
|
|
109
|
+
return p.name;
|
|
110
|
+
if (t.isAssignmentPattern(p) && t.isIdentifier(p.left))
|
|
111
|
+
return p.left.name;
|
|
112
|
+
return 'arg';
|
|
113
|
+
});
|
|
114
|
+
const doc = path.node.leadingComments
|
|
115
|
+
? path.node.leadingComments.map(c => c.value.trim()).join('\n')
|
|
116
|
+
: undefined;
|
|
117
|
+
analysis.functions.push({
|
|
118
|
+
name: path.node.id.name,
|
|
119
|
+
line: path.node.loc.start.line,
|
|
120
|
+
params: params,
|
|
121
|
+
doc: doc,
|
|
122
|
+
code: code
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
ClassDeclaration(path) {
|
|
127
|
+
if (path.node.id) {
|
|
128
|
+
analysis.classes.push(path.node.id.name);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
console.warn(`⚠️ Parser warning in ${path_1.default.basename(filePath)}: ${error.message?.split('\n')[0]}`);
|
|
135
|
+
}
|
|
136
|
+
return analysis;
|
|
137
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parsePython = parsePython;
|
|
4
|
+
function parsePython(content, filePath) {
|
|
5
|
+
const analysis = {
|
|
6
|
+
imports: [],
|
|
7
|
+
exports: [],
|
|
8
|
+
functions: [],
|
|
9
|
+
classes: []
|
|
10
|
+
};
|
|
11
|
+
const lines = content.split('\n');
|
|
12
|
+
// Regex patterns
|
|
13
|
+
const importRegex = /^(?:from|import)\s+([\w\.]+)/;
|
|
14
|
+
const defRegex = /^\s*def\s+([a-zA-Z_]\w*)/;
|
|
15
|
+
const classRegex = /^\s*class\s+([a-zA-Z_]\w*)/;
|
|
16
|
+
const assignRegex = /^([a-zA-Z_]\w*)\s*=/; // Global assignments
|
|
17
|
+
lines.forEach((line, index) => {
|
|
18
|
+
// Imports
|
|
19
|
+
const importMatch = line.match(importRegex);
|
|
20
|
+
if (importMatch) {
|
|
21
|
+
analysis.imports.push(importMatch[1]);
|
|
22
|
+
}
|
|
23
|
+
// Functions
|
|
24
|
+
const defMatch = line.match(defRegex);
|
|
25
|
+
if (defMatch) {
|
|
26
|
+
analysis.functions.push({
|
|
27
|
+
name: defMatch[1],
|
|
28
|
+
line: index + 1,
|
|
29
|
+
params: [], // Regex parser doesn't extract params yet
|
|
30
|
+
doc: undefined,
|
|
31
|
+
code: undefined
|
|
32
|
+
});
|
|
33
|
+
// Python functions at module level are exports
|
|
34
|
+
analysis.exports.push(defMatch[1]);
|
|
35
|
+
}
|
|
36
|
+
// Classes
|
|
37
|
+
const classMatch = line.match(classRegex);
|
|
38
|
+
if (classMatch) {
|
|
39
|
+
analysis.classes.push(classMatch[1]);
|
|
40
|
+
analysis.exports.push(classMatch[1]);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
return analysis;
|
|
44
|
+
}
|