fileflows 1.0.2 → 1.0.5
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 +19 -11
- package/cli.mjs +7 -0
- package/config/jest-require-polyfill.cjs +20 -0
- package/config/jest-setup.mjs +28 -0
- package/config/jest.config.mjs +72 -0
- package/config/localVars.js +2 -86
- package/dist/cli.js +76 -0
- package/dist/config/localVars.js +76 -0
- package/dist/index.js +7 -0
- package/dist/lib/dataFlowGrouper.js +137 -0
- package/dist/lib/dependencyExtractor.js +46 -0
- package/dist/lib/fileClassifier.js +78 -0
- package/dist/lib/fileFlowsGenerator.js +301 -0
- package/dist/lib/fileIO.js +35 -0
- package/dist/lib/graphUtils.js +89 -0
- package/dist/lib/index.js +50 -0
- package/dist/lib/jsParser.js +131 -0
- package/dist/lib/otherFileParser.js +131 -0
- package/index.mjs +2 -0
- package/package.json +32 -16
- package/scripts/broadcast.sh +26 -0
- package/scripts/clean-bun-cache.mjs +14 -0
- package/scripts/clean-dist.mjs +7 -0
- package/scripts/ensure-runner.mjs +9 -0
- package/scripts/kill-agent.sh +24 -0
- package/scripts/kill-all-agents.sh +31 -0
- package/scripts/list-agents.sh +16 -0
- package/scripts/send-to-agent.sh +28 -0
- package/scripts/spawn-agent.sh +62 -0
- package/cli.js +0 -81
- package/config/localVars.test.js +0 -37
- package/index.js +0 -13
- package/lib/SUMMARY.md +0 -53
- package/lib/dataFlowGrouper.js +0 -150
- package/lib/dataFlowGrouper.test.js +0 -17
- package/lib/dependencyExtractor.js +0 -70
- package/lib/dependencyExtractor.test.js +0 -9
- package/lib/fileClassifier.js +0 -38
- package/lib/fileClassifier.test.js +0 -9
- package/lib/fileFlowsGenerator.js +0 -156
- package/lib/fileFlowsGenerator.test.js +0 -17
- package/lib/fileIO.js +0 -60
- package/lib/fileIO.test.js +0 -13
- package/lib/graphUtils.js +0 -139
- package/lib/graphUtils.test.js +0 -25
- package/lib/index.js +0 -29
- package/lib/index.test.js +0 -53
- package/lib/jsParser.js +0 -132
- package/lib/jsParser.test.js +0 -13
- package/lib/otherFileParser.js +0 -103
- package/lib/otherFileParser.test.js +0 -9
|
@@ -0,0 +1,78 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.classifyFile = classifyFile;
|
|
37
|
+
const localVars = __importStar(require("../config/localVars"));
|
|
38
|
+
function classifyFile(filePath, ext) {
|
|
39
|
+
if (localVars.CODE_EXTENSIONS.includes(ext)) {
|
|
40
|
+
if (ext === 'tsx')
|
|
41
|
+
return 'UI Component';
|
|
42
|
+
if (filePath.includes('test') || filePath.includes('spec'))
|
|
43
|
+
return 'Test File';
|
|
44
|
+
if (filePath.includes('component') || filePath.includes('Component'))
|
|
45
|
+
return 'UI Component';
|
|
46
|
+
if (filePath.includes('api') || filePath.includes('route'))
|
|
47
|
+
return 'API/Route';
|
|
48
|
+
if (filePath.includes('util') || filePath.includes('helper'))
|
|
49
|
+
return 'Utility';
|
|
50
|
+
if (filePath.includes('config') || filePath.includes('setting'))
|
|
51
|
+
return 'Configuration';
|
|
52
|
+
if (filePath.includes('model') || filePath.includes('schema'))
|
|
53
|
+
return 'Data Model';
|
|
54
|
+
return 'Code File';
|
|
55
|
+
}
|
|
56
|
+
switch (ext) {
|
|
57
|
+
case 'json':
|
|
58
|
+
return 'Configuration/Data';
|
|
59
|
+
case 'md':
|
|
60
|
+
return 'Documentation';
|
|
61
|
+
case 'yml':
|
|
62
|
+
case 'yaml':
|
|
63
|
+
return 'Configuration';
|
|
64
|
+
case 'env':
|
|
65
|
+
return 'Environment';
|
|
66
|
+
case 'html':
|
|
67
|
+
return 'Template/View';
|
|
68
|
+
case 'css':
|
|
69
|
+
case 'scss':
|
|
70
|
+
return 'Stylesheet';
|
|
71
|
+
case 'sh':
|
|
72
|
+
return 'Script';
|
|
73
|
+
case 'graphql':
|
|
74
|
+
return 'Schema';
|
|
75
|
+
default:
|
|
76
|
+
return 'Other';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,301 @@
|
|
|
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.generateFileFlows = generateFileFlows;
|
|
40
|
+
const fs_1 = require("fs");
|
|
41
|
+
const path_1 = __importDefault(require("path"));
|
|
42
|
+
const qerrors_1 = __importDefault(require("qerrors"));
|
|
43
|
+
const localVars = __importStar(require("../config/localVars"));
|
|
44
|
+
const dataFlowGrouper_1 = require("./dataFlowGrouper");
|
|
45
|
+
const fileClassifier_1 = require("./fileClassifier");
|
|
46
|
+
const jsParser_1 = require("./jsParser");
|
|
47
|
+
const otherFileParser_1 = require("./otherFileParser");
|
|
48
|
+
const { logError, logWarn, logInfo } = qerrors_1.default;
|
|
49
|
+
async function generateFileFlows(rootDir = '.', outputFile = null) {
|
|
50
|
+
const ROOT = rootDir;
|
|
51
|
+
const FILE_OUTPUT = outputFile || localVars.DEFAULT_OUTPUT_FILE;
|
|
52
|
+
const shouldIgnore = (relativePath) => {
|
|
53
|
+
return localVars.IGNORE_PATTERNS.some(pattern => {
|
|
54
|
+
const cleanPattern = pattern.replace(/\*\*/g, '').replace(/\*/g, '').replace(/\//g, '');
|
|
55
|
+
return relativePath.includes(cleanPattern);
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
const discoverFiles = () => {
|
|
59
|
+
const traverse = (dir, depth = 0) => {
|
|
60
|
+
if (depth > 10)
|
|
61
|
+
return [];
|
|
62
|
+
const fullDir = path_1.default.join(ROOT, dir);
|
|
63
|
+
if (!pathIsDirectory(fullDir))
|
|
64
|
+
return [];
|
|
65
|
+
const items = (0, fs_1.readdirSync)(fullDir);
|
|
66
|
+
const found = [];
|
|
67
|
+
for (const item of items) {
|
|
68
|
+
const relativePath = dir === '.' ? item : path_1.default.join(dir, item).replace(/\\/g, '/');
|
|
69
|
+
if (shouldIgnore(relativePath))
|
|
70
|
+
continue;
|
|
71
|
+
try {
|
|
72
|
+
const fullPath = path_1.default.join(fullDir, item);
|
|
73
|
+
const stats = (0, fs_1.statSync)(fullPath);
|
|
74
|
+
if (stats.isDirectory()) {
|
|
75
|
+
found.push(...traverse(relativePath, depth + 1));
|
|
76
|
+
}
|
|
77
|
+
else if (stats.isFile()) {
|
|
78
|
+
const ext = path_1.default.extname(item).slice(1);
|
|
79
|
+
if (localVars.ALL_EXTENSIONS.includes(ext)) {
|
|
80
|
+
found.push(relativePath);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return found;
|
|
89
|
+
};
|
|
90
|
+
return traverse('.');
|
|
91
|
+
};
|
|
92
|
+
const files = discoverFiles();
|
|
93
|
+
if (files.length === 0) {
|
|
94
|
+
logWarn(`No files found in directory: ${ROOT}`, 'generateFileFlows', {
|
|
95
|
+
context: 'NO_FILES_FOUND',
|
|
96
|
+
rootDir: ROOT,
|
|
97
|
+
searchExtensions: localVars.ALL_EXTENSIONS
|
|
98
|
+
});
|
|
99
|
+
return { filesAnalyzed: 0, flowGroups: 0, outputFile: null, jsonOutputFile: null };
|
|
100
|
+
}
|
|
101
|
+
const grouped = await (0, dataFlowGrouper_1.groupByDataFlow)(files, ROOT);
|
|
102
|
+
const fileMetadata = grouped.fileMetadata || new Map();
|
|
103
|
+
const structuredOutput = buildStructuredFlowData(grouped, fileMetadata, files, fileClassifier_1.classifyFile);
|
|
104
|
+
const jsonOutputPath = getStructuredJsonPath(FILE_OUTPUT);
|
|
105
|
+
(0, fs_1.writeFileSync)(jsonOutputPath, JSON.stringify(structuredOutput, null, 2));
|
|
106
|
+
const outputLines = [];
|
|
107
|
+
outputLines.push('# FILE_FLOWS');
|
|
108
|
+
outputLines.push('> Auto-generated. Do not edit directly.');
|
|
109
|
+
outputLines.push('> Files grouped by PRIMARY: actual data flow relationships, SECONDARY: filename similarity.\n');
|
|
110
|
+
let flowIndex = 1;
|
|
111
|
+
let fileIndex = 1;
|
|
112
|
+
for (const group of grouped) {
|
|
113
|
+
const groupName = group.name || 'Unknown-Group';
|
|
114
|
+
const fileGroup = group.files || [];
|
|
115
|
+
outputLines.push(`### 🧩 Flow Group: [${flowIndex++}] \`${groupName}\`\n`);
|
|
116
|
+
for (const relPath of fileGroup) {
|
|
117
|
+
const absPath = path_1.default.resolve(ROOT, relPath);
|
|
118
|
+
const ext = path_1.default.extname(relPath).slice(1);
|
|
119
|
+
let content = '';
|
|
120
|
+
try {
|
|
121
|
+
content = (0, fs_1.readFileSync)(absPath, 'utf8');
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
if (error.code === 'ENOENT') {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
const metadata = localVars.CODE_EXTENSIONS.includes(ext)
|
|
130
|
+
? parseJSMetadata(content, relPath)
|
|
131
|
+
: parseOtherMetadata(content, relPath, ext);
|
|
132
|
+
const section = [`## [${fileIndex++}] \`${relPath}\``];
|
|
133
|
+
section.push(`**Type:** ${(0, fileClassifier_1.classifyFile)(relPath, ext)}`);
|
|
134
|
+
const values = extractMetadataEntries(metadata);
|
|
135
|
+
values.forEach(entry => section.push(entry));
|
|
136
|
+
section.push('\n---\n');
|
|
137
|
+
outputLines.push(section.join('\n'));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
(0, fs_1.writeFileSync)(FILE_OUTPUT, outputLines.join('\n'));
|
|
141
|
+
const result = {
|
|
142
|
+
filesAnalyzed: fileIndex - 1,
|
|
143
|
+
flowGroups: grouped.length,
|
|
144
|
+
outputFile: FILE_OUTPUT,
|
|
145
|
+
jsonOutputFile: jsonOutputPath
|
|
146
|
+
};
|
|
147
|
+
logInfo('FILE_FLOWS.md generation completed', 'generateFileFlows', {
|
|
148
|
+
context: 'GENERATION_SUCCESS',
|
|
149
|
+
filesAnalyzed: result.filesAnalyzed,
|
|
150
|
+
flowGroups: result.flowGroups,
|
|
151
|
+
outputFile: result.outputFile
|
|
152
|
+
});
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
function parseJSMetadata(content, relPath) {
|
|
156
|
+
return (0, jsParser_1.parseJSFile)(content, relPath);
|
|
157
|
+
}
|
|
158
|
+
function parseOtherMetadata(content, relPath, ext) {
|
|
159
|
+
return (0, otherFileParser_1.parseOtherFile)(content, relPath, ext);
|
|
160
|
+
}
|
|
161
|
+
function extractMetadataEntries(metadata) {
|
|
162
|
+
const entries = [];
|
|
163
|
+
for (const [label, value] of Object.entries(metadata)) {
|
|
164
|
+
const arrayValue = Array.isArray(value) ? value : [value];
|
|
165
|
+
if (arrayValue.length > 0) {
|
|
166
|
+
entries.push(`**${label}:** ${arrayValue.join(', ')}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return entries;
|
|
170
|
+
}
|
|
171
|
+
function pathIsDirectory(fullPath) {
|
|
172
|
+
try {
|
|
173
|
+
return (0, fs_1.statSync)(fullPath).isDirectory();
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function buildStructuredFlowData(grouped, fileMetadata, allFiles, classifyFileFn) {
|
|
180
|
+
const fileToGroupMap = new Map();
|
|
181
|
+
grouped.forEach((group, idx) => {
|
|
182
|
+
for (const file of group.files || []) {
|
|
183
|
+
fileToGroupMap.set(file, idx);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
const flowRelations = grouped.map(() => ({
|
|
187
|
+
importsFrom: new Set(),
|
|
188
|
+
importedBy: new Set()
|
|
189
|
+
}));
|
|
190
|
+
const createFallbackMetadata = (file) => ({
|
|
191
|
+
Imports: [],
|
|
192
|
+
Exports: [],
|
|
193
|
+
Functions: [],
|
|
194
|
+
Components: [],
|
|
195
|
+
ApiCalls: [],
|
|
196
|
+
ReduxActions: [],
|
|
197
|
+
Routes: [],
|
|
198
|
+
Keys: [],
|
|
199
|
+
Summary: [],
|
|
200
|
+
Headings: [],
|
|
201
|
+
Vars: [],
|
|
202
|
+
Types: [],
|
|
203
|
+
Commands: [],
|
|
204
|
+
Tags: [],
|
|
205
|
+
type: classifyFileFn(file, path_1.default.extname(file).slice(1)),
|
|
206
|
+
dependencies: [],
|
|
207
|
+
roleScore: 0
|
|
208
|
+
});
|
|
209
|
+
const flows = grouped.map((group, idx) => {
|
|
210
|
+
const dirCounts = {};
|
|
211
|
+
const internalDeps = new Set();
|
|
212
|
+
const externalDeps = new Set();
|
|
213
|
+
let totalRoleScore = 0;
|
|
214
|
+
const roleEntries = [];
|
|
215
|
+
const files = (group.files || []).map((file) => {
|
|
216
|
+
const meta = fileMetadata.get(file) ?? createFallbackMetadata(file);
|
|
217
|
+
const dependencies = meta.dependencies;
|
|
218
|
+
for (const dependency of dependencies) {
|
|
219
|
+
const targetGroup = fileToGroupMap.get(dependency);
|
|
220
|
+
if (targetGroup === idx) {
|
|
221
|
+
internalDeps.add(dependency);
|
|
222
|
+
}
|
|
223
|
+
else if (typeof targetGroup === 'number') {
|
|
224
|
+
flowRelations[idx].importsFrom.add(targetGroup);
|
|
225
|
+
flowRelations[targetGroup].importedBy.add(idx);
|
|
226
|
+
}
|
|
227
|
+
else if (dependency) {
|
|
228
|
+
externalDeps.add(dependency);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const roleScore = meta.roleScore;
|
|
232
|
+
totalRoleScore += roleScore;
|
|
233
|
+
roleEntries.push({ file, score: roleScore });
|
|
234
|
+
const dirName = path_1.default.dirname(file) || '.';
|
|
235
|
+
dirCounts[dirName] = (dirCounts[dirName] || 0) + 1;
|
|
236
|
+
return {
|
|
237
|
+
path: file,
|
|
238
|
+
type: meta.type,
|
|
239
|
+
roleScore,
|
|
240
|
+
imports: meta.Imports ?? [],
|
|
241
|
+
exports: meta.Exports ?? []
|
|
242
|
+
};
|
|
243
|
+
});
|
|
244
|
+
const averageScore = files.length ? totalRoleScore / files.length : 0;
|
|
245
|
+
const primaryEntry = roleEntries.reduce((best, entry) => {
|
|
246
|
+
if (!best || entry.score > best.score)
|
|
247
|
+
return entry;
|
|
248
|
+
return best;
|
|
249
|
+
}, null);
|
|
250
|
+
const sortedDirs = Object.entries(dirCounts).sort((a, b) => b[1] - a[1]);
|
|
251
|
+
const dominantDirectories = sortedDirs.slice(0, 3).map(([dir, count]) => ({
|
|
252
|
+
directory: dir,
|
|
253
|
+
fileCount: count
|
|
254
|
+
}));
|
|
255
|
+
return {
|
|
256
|
+
flowNumber: idx + 1,
|
|
257
|
+
name: group.name || `flow-${idx + 1}`,
|
|
258
|
+
fileCount: files.length,
|
|
259
|
+
metadata: group.metadata || [],
|
|
260
|
+
files,
|
|
261
|
+
hints: {
|
|
262
|
+
dominantDirectories,
|
|
263
|
+
internalDependencyCount: internalDeps.size,
|
|
264
|
+
externalDependencies: [...externalDeps].sort(),
|
|
265
|
+
roleScoreSummary: {
|
|
266
|
+
average: Number(averageScore.toFixed(2)),
|
|
267
|
+
highest: primaryEntry ? { path: primaryEntry.file, score: primaryEntry.score } : null
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
crossFlowDependencies: {
|
|
271
|
+
importsFrom: [],
|
|
272
|
+
importedBy: []
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
});
|
|
276
|
+
flows.forEach((flow, idx) => {
|
|
277
|
+
flow.crossFlowDependencies.importsFrom = Array.from(flowRelations[idx].importsFrom)
|
|
278
|
+
.map(groupIndex => flows[groupIndex]?.name)
|
|
279
|
+
.filter(Boolean);
|
|
280
|
+
flow.crossFlowDependencies.importedBy = Array.from(flowRelations[idx].importedBy)
|
|
281
|
+
.map(groupIndex => flows[groupIndex]?.name)
|
|
282
|
+
.filter(Boolean);
|
|
283
|
+
});
|
|
284
|
+
return {
|
|
285
|
+
generatedAt: new Date().toISOString(),
|
|
286
|
+
stats: {
|
|
287
|
+
flowCount: grouped.length,
|
|
288
|
+
fileCount: allFiles.length
|
|
289
|
+
},
|
|
290
|
+
flows
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
function getStructuredJsonPath(outputPath) {
|
|
294
|
+
if (!outputPath) {
|
|
295
|
+
return 'FILE_FLOWS.json';
|
|
296
|
+
}
|
|
297
|
+
const dir = path_1.default.dirname(outputPath);
|
|
298
|
+
const ext = path_1.default.extname(outputPath);
|
|
299
|
+
const baseName = ext ? path_1.default.basename(outputPath, ext) : path_1.default.basename(outputPath);
|
|
300
|
+
return path_1.default.join(dir, `${baseName}.json`);
|
|
301
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
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.safeReadFileSync = safeReadFileSync;
|
|
7
|
+
exports.safeResolvePath = safeResolvePath;
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const qerrors_1 = __importDefault(require("qerrors"));
|
|
11
|
+
const { logError, createTypedError, ErrorTypes } = qerrors_1.default;
|
|
12
|
+
function safeReadFileSync(rootDir, filePath) {
|
|
13
|
+
try {
|
|
14
|
+
const fullPath = path_1.default.join(rootDir, filePath);
|
|
15
|
+
return { success: true, content: (0, fs_1.readFileSync)(fullPath, 'utf8') };
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
logError(error, `Failed to read file: ${filePath}`, { context: 'FILE_READ_ERROR' });
|
|
19
|
+
return { success: false, error: error };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function safeResolvePath(rootDir, filePath) {
|
|
23
|
+
try {
|
|
24
|
+
const resolved = path_1.default.resolve(rootDir, filePath);
|
|
25
|
+
const relative = path_1.default.relative(rootDir, resolved);
|
|
26
|
+
if (relative.startsWith('..')) {
|
|
27
|
+
throw createTypedError(`Path traversal attempt detected: ${filePath}`, ErrorTypes.VALIDATION, 'PATH_TRAVERSAL_DETECTED');
|
|
28
|
+
}
|
|
29
|
+
return { success: true, resolvedPath: resolved };
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
logError(error, `Failed to resolve path: ${filePath}`, { context: 'PATH_RESOLVE_ERROR' });
|
|
33
|
+
return { success: false, error: error };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
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.buildDependencyGraph = buildDependencyGraph;
|
|
7
|
+
exports.findConnectedComponents = findConnectedComponents;
|
|
8
|
+
exports.depthFirstSearch = depthFirstSearch;
|
|
9
|
+
exports.groupBySimilarity = groupBySimilarity;
|
|
10
|
+
exports.calculateRoleScore = calculateRoleScore;
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
function buildDependencyGraph(fileList, fileMetadata) {
|
|
13
|
+
const graph = new Map();
|
|
14
|
+
for (const filePath of fileList) {
|
|
15
|
+
const metadata = fileMetadata.get(filePath);
|
|
16
|
+
const dependencies = metadata?.dependencies || [];
|
|
17
|
+
graph.set(filePath, dependencies);
|
|
18
|
+
}
|
|
19
|
+
return graph;
|
|
20
|
+
}
|
|
21
|
+
function findConnectedComponents(graph) {
|
|
22
|
+
const visited = new Set();
|
|
23
|
+
const components = [];
|
|
24
|
+
for (const node of graph.keys()) {
|
|
25
|
+
if (!visited.has(node)) {
|
|
26
|
+
const component = [];
|
|
27
|
+
depthFirstSearch(node, graph, visited, component);
|
|
28
|
+
if (component.length > 0) {
|
|
29
|
+
components.push(component);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return components;
|
|
34
|
+
}
|
|
35
|
+
function depthFirstSearch(node, graph, visited, component) {
|
|
36
|
+
if (visited.has(node))
|
|
37
|
+
return;
|
|
38
|
+
visited.add(node);
|
|
39
|
+
component.push(node);
|
|
40
|
+
const dependencies = graph.get(node) || [];
|
|
41
|
+
for (const dependency of dependencies) {
|
|
42
|
+
if (graph.has(dependency)) {
|
|
43
|
+
depthFirstSearch(dependency, graph, visited, component);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function groupBySimilarity(files) {
|
|
48
|
+
const groups = new Map();
|
|
49
|
+
for (const file of files) {
|
|
50
|
+
const basename = path_1.default.basename(file, path_1.default.extname(file));
|
|
51
|
+
const parts = basename.split(/[-_.]/).filter(part => part.length > 2);
|
|
52
|
+
let bestGroupKey = null;
|
|
53
|
+
let maxSimilarity = 0;
|
|
54
|
+
for (const [groupKey, groupFiles] of groups) {
|
|
55
|
+
const groupBasename = path_1.default.basename(groupFiles[0], path_1.default.extname(groupFiles[0]));
|
|
56
|
+
const groupParts = groupBasename.split(/[-_.]/).filter(part => part.length > 2);
|
|
57
|
+
const commonParts = parts.filter(part => groupParts.includes(part));
|
|
58
|
+
const similarity = commonParts.length / Math.max(parts.length || 1, groupParts.length || 1);
|
|
59
|
+
if (similarity > maxSimilarity && similarity > 0.3) {
|
|
60
|
+
maxSimilarity = similarity;
|
|
61
|
+
bestGroupKey = groupKey;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (bestGroupKey) {
|
|
65
|
+
groups.get(bestGroupKey)?.push(file);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
const groupKey = basename.replace(/[-_.]/g, '-');
|
|
69
|
+
groups.set(groupKey, [file]);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return groups;
|
|
73
|
+
}
|
|
74
|
+
function calculateRoleScore(filePath) {
|
|
75
|
+
const basename = path_1.default.basename(filePath, path_1.default.extname(filePath)).toLowerCase();
|
|
76
|
+
const fullPath = filePath.toLowerCase();
|
|
77
|
+
let score = 0;
|
|
78
|
+
if (basename.includes('index') || basename.includes('main'))
|
|
79
|
+
score += 10;
|
|
80
|
+
if (basename.includes('generator') || basename.includes('engine'))
|
|
81
|
+
score += 8;
|
|
82
|
+
if (basename.includes('parser') || basename.includes('analyzer'))
|
|
83
|
+
score += 6;
|
|
84
|
+
if (basename.includes('util') || basename.includes('helper'))
|
|
85
|
+
score += 4;
|
|
86
|
+
if (basename.includes('config') || basename.includes('constant') || fullPath.includes('config/'))
|
|
87
|
+
score += 7;
|
|
88
|
+
return score;
|
|
89
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.graphUtils = exports.fileIO = exports.extractDependencies = exports.parseOtherFile = exports.parseJSFile = exports.classifyFile = exports.groupByDataFlow = exports.generateFileFlows = void 0;
|
|
37
|
+
var fileFlowsGenerator_1 = require("./fileFlowsGenerator");
|
|
38
|
+
Object.defineProperty(exports, "generateFileFlows", { enumerable: true, get: function () { return fileFlowsGenerator_1.generateFileFlows; } });
|
|
39
|
+
var dataFlowGrouper_1 = require("./dataFlowGrouper");
|
|
40
|
+
Object.defineProperty(exports, "groupByDataFlow", { enumerable: true, get: function () { return dataFlowGrouper_1.groupByDataFlow; } });
|
|
41
|
+
var fileClassifier_1 = require("./fileClassifier");
|
|
42
|
+
Object.defineProperty(exports, "classifyFile", { enumerable: true, get: function () { return fileClassifier_1.classifyFile; } });
|
|
43
|
+
var jsParser_1 = require("./jsParser");
|
|
44
|
+
Object.defineProperty(exports, "parseJSFile", { enumerable: true, get: function () { return jsParser_1.parseJSFile; } });
|
|
45
|
+
var otherFileParser_1 = require("./otherFileParser");
|
|
46
|
+
Object.defineProperty(exports, "parseOtherFile", { enumerable: true, get: function () { return otherFileParser_1.parseOtherFile; } });
|
|
47
|
+
var dependencyExtractor_1 = require("./dependencyExtractor");
|
|
48
|
+
Object.defineProperty(exports, "extractDependencies", { enumerable: true, get: function () { return dependencyExtractor_1.extractDependencies; } });
|
|
49
|
+
exports.fileIO = __importStar(require("./fileIO"));
|
|
50
|
+
exports.graphUtils = __importStar(require("./graphUtils"));
|
|
@@ -0,0 +1,131 @@
|
|
|
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.parseJSFile = parseJSFile;
|
|
7
|
+
const parser_1 = require("@babel/parser");
|
|
8
|
+
const qerrors_1 = __importDefault(require("qerrors"));
|
|
9
|
+
const { logWarn } = qerrors_1.default;
|
|
10
|
+
function parseJSFile(content, filePath) {
|
|
11
|
+
const imports = new Set();
|
|
12
|
+
const exports = new Set();
|
|
13
|
+
const functions = new Set();
|
|
14
|
+
const components = new Set();
|
|
15
|
+
const apiCalls = new Set();
|
|
16
|
+
const reduxActions = new Set();
|
|
17
|
+
const routes = new Set();
|
|
18
|
+
try {
|
|
19
|
+
const ast = (0, parser_1.parse)(content, {
|
|
20
|
+
sourceType: 'module',
|
|
21
|
+
allowImportExportEverywhere: true,
|
|
22
|
+
plugins: ['jsx', 'typescript', 'decorators-legacy'],
|
|
23
|
+
errorRecovery: true
|
|
24
|
+
});
|
|
25
|
+
const visitNode = (node) => {
|
|
26
|
+
if (!node || typeof node !== 'object')
|
|
27
|
+
return;
|
|
28
|
+
switch (node.type) {
|
|
29
|
+
case 'ImportDeclaration': {
|
|
30
|
+
const source = node.source?.value;
|
|
31
|
+
if (source)
|
|
32
|
+
imports.add(source);
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
case 'CallExpression': {
|
|
36
|
+
if (node.callee?.name === 'require' && node.arguments?.[0]?.value) {
|
|
37
|
+
const reqSource = node.arguments[0].value;
|
|
38
|
+
const builtInModules = ['fs', 'path', 'http', 'https', 'os', 'util', 'crypto', 'events', 'stream'];
|
|
39
|
+
if (!reqSource.startsWith('/') && !builtInModules.includes(reqSource)) {
|
|
40
|
+
imports.add(reqSource);
|
|
41
|
+
}
|
|
42
|
+
else if (reqSource.startsWith('.')) {
|
|
43
|
+
imports.add(reqSource);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const callee = node.callee;
|
|
47
|
+
if (callee?.type === 'MemberExpression') {
|
|
48
|
+
const obj = callee.object?.name;
|
|
49
|
+
const prop = callee.property?.name;
|
|
50
|
+
if (['fetch', 'axios', 'http', 'https', 'api'].includes(obj) || ['get', 'post', 'put', 'delete', 'call'].includes(prop)) {
|
|
51
|
+
apiCalls.add(`${obj}.${prop}`);
|
|
52
|
+
}
|
|
53
|
+
if (obj === 'dispatch' && prop) {
|
|
54
|
+
reduxActions.add(prop);
|
|
55
|
+
}
|
|
56
|
+
if (['app', 'router', 'express'].includes(obj) && ['get', 'post', 'put', 'delete', 'patch'].includes(prop)) {
|
|
57
|
+
const routeArg = node.arguments?.[0];
|
|
58
|
+
if (routeArg?.value) {
|
|
59
|
+
routes.add(`${prop.toUpperCase()} ${routeArg.value}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (callee?.name === 'fetch' || callee?.name === 'axios') {
|
|
64
|
+
apiCalls.add(`${callee.name}.call`);
|
|
65
|
+
}
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
case 'ExportNamedDeclaration': {
|
|
69
|
+
node.specifiers?.forEach((spec) => {
|
|
70
|
+
if (spec.exported?.name)
|
|
71
|
+
exports.add(spec.exported.name);
|
|
72
|
+
});
|
|
73
|
+
if (node.declaration?.declarations) {
|
|
74
|
+
node.declaration.declarations.forEach((decl) => {
|
|
75
|
+
if (decl.id?.name)
|
|
76
|
+
exports.add(decl.id.name);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (node.declaration?.id?.name) {
|
|
80
|
+
exports.add(node.declaration.id.name);
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
case 'ExportDefaultDeclaration': {
|
|
85
|
+
exports.add('default');
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
case 'FunctionDeclaration': {
|
|
89
|
+
if (node.id?.name) {
|
|
90
|
+
functions.add(node.id.name);
|
|
91
|
+
if (node.id.name[0] === node.id.name[0].toUpperCase()) {
|
|
92
|
+
components.add(node.id.name);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
case 'VariableDeclarator': {
|
|
98
|
+
if ((node.init?.type === 'ArrowFunctionExpression' || node.init?.type === 'FunctionExpression') && node.id?.name) {
|
|
99
|
+
functions.add(node.id.name);
|
|
100
|
+
if (node.id.name[0] === node.id.name[0].toUpperCase()) {
|
|
101
|
+
components.add(node.id.name);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
for (const key of Object.keys(node)) {
|
|
108
|
+
const value = node[key];
|
|
109
|
+
if (Array.isArray(value)) {
|
|
110
|
+
value.forEach(visitNode);
|
|
111
|
+
}
|
|
112
|
+
else if (value && typeof value === 'object') {
|
|
113
|
+
visitNode(value);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
visitNode(ast);
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
logWarn(`JavaScript parsing failed for ${filePath}`, 'parseJSFile', { context: 'JS_PARSE_ERROR', filePath, errorMessage: err.message });
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
Imports: [...imports].sort(),
|
|
124
|
+
Exports: [...exports].sort(),
|
|
125
|
+
Functions: [...functions].sort(),
|
|
126
|
+
Components: [...components].sort(),
|
|
127
|
+
ApiCalls: [...apiCalls].sort(),
|
|
128
|
+
ReduxActions: [...reduxActions].sort(),
|
|
129
|
+
Routes: [...routes].sort()
|
|
130
|
+
};
|
|
131
|
+
}
|