depgraph-core 1.0.2 → 1.0.3
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 +181 -105
- package/depgraph-output.json +1273 -233
- package/depgraph.js +349 -66
- package/docs/README.md +56 -0
- package/docs/architecture.md +89 -0
- package/docs/data-types.md +218 -0
- package/docs/language-registry.md +205 -0
- package/docs/stage-collector.md +109 -0
- package/docs/stage-graph.md +157 -0
- package/docs/stage-impact.md +154 -0
- package/docs/stage-metrics.md +113 -0
- package/docs/stage-output.md +144 -0
- package/docs/stage-parser.md +144 -0
- package/package.json +1 -1
package/depgraph.js
CHANGED
|
@@ -83,6 +83,7 @@ var require_javascript = __commonJS({
|
|
|
83
83
|
"dist/languages/javascript.js"(exports2) {
|
|
84
84
|
"use strict";
|
|
85
85
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
86
|
+
exports2.jsEntityPatterns = void 0;
|
|
86
87
|
var registry_1 = require_registry();
|
|
87
88
|
var constants_1 = require_constants();
|
|
88
89
|
function estimateComplexity(code, name) {
|
|
@@ -98,52 +99,51 @@ var require_javascript = __commonJS({
|
|
|
98
99
|
return "medium";
|
|
99
100
|
return "high";
|
|
100
101
|
}
|
|
102
|
+
exports2.jsEntityPatterns = [
|
|
103
|
+
// React components (PascalCase arrow functions)
|
|
104
|
+
{
|
|
105
|
+
regex: /^(?:export\s+)?const\s+([A-Z]\w+)\s*=\s*(?:\([^)]*\)|[^=])\s*=>/gm,
|
|
106
|
+
type: "component"
|
|
107
|
+
},
|
|
108
|
+
// React hooks (camelCase starting with "use")
|
|
109
|
+
{
|
|
110
|
+
regex: /^(?:export\s+)?(?:const\s+)?(use[A-Z]\w+)\s*=/gm,
|
|
111
|
+
type: "hook"
|
|
112
|
+
},
|
|
113
|
+
// regular functions
|
|
114
|
+
{
|
|
115
|
+
regex: /^(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s+(\w+)\s*\(/gm,
|
|
116
|
+
type: "function"
|
|
117
|
+
},
|
|
118
|
+
// arrow functions assigned to const
|
|
119
|
+
{
|
|
120
|
+
regex: /^(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s*)?\([^)]*\)\s*=>/gm,
|
|
121
|
+
type: "function"
|
|
122
|
+
},
|
|
123
|
+
// classes
|
|
124
|
+
{
|
|
125
|
+
regex: /^(?:export\s+)?(?:default\s+)?class\s+(\w+)/gm,
|
|
126
|
+
type: "class"
|
|
127
|
+
},
|
|
128
|
+
// TypeScript interfaces
|
|
129
|
+
{
|
|
130
|
+
regex: /^(?:export\s+)?interface\s+(\w+)/gm,
|
|
131
|
+
type: "interface"
|
|
132
|
+
},
|
|
133
|
+
// TypeScript types
|
|
134
|
+
{
|
|
135
|
+
regex: /^(?:export\s+)?type\s+(\w+)\s*=/gm,
|
|
136
|
+
type: "type"
|
|
137
|
+
},
|
|
138
|
+
// Express routes (capture group 1 = method, group 2 = path — skipped in gitdiff context matching)
|
|
139
|
+
{
|
|
140
|
+
regex: /(?:app|router)\.(get|post|put|delete|patch)\s*\(\s*['"]([^'"]+)['"]/gm,
|
|
141
|
+
type: "api"
|
|
142
|
+
}
|
|
143
|
+
];
|
|
101
144
|
function extractEntities(code, filePath) {
|
|
102
145
|
const entities = [];
|
|
103
|
-
const
|
|
104
|
-
const patterns = [
|
|
105
|
-
// React components (PascalCase arrow functions)
|
|
106
|
-
{
|
|
107
|
-
regex: /^(?:export\s+)?const\s+([A-Z]\w+)\s*=\s*(?:\([^)]*\)|[^=])\s*=>/gm,
|
|
108
|
-
type: "component"
|
|
109
|
-
},
|
|
110
|
-
// React hooks (camelCase starting with "use")
|
|
111
|
-
{
|
|
112
|
-
regex: /^(?:export\s+)?(?:const\s+)?(use[A-Z]\w+)\s*=/gm,
|
|
113
|
-
type: "hook"
|
|
114
|
-
},
|
|
115
|
-
// regular functions
|
|
116
|
-
{
|
|
117
|
-
regex: /^(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s+(\w+)\s*\(/gm,
|
|
118
|
-
type: "function"
|
|
119
|
-
},
|
|
120
|
-
// arrow functions assigned to const
|
|
121
|
-
{
|
|
122
|
-
regex: /^(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s*)?\([^)]*\)\s*=>/gm,
|
|
123
|
-
type: "function"
|
|
124
|
-
},
|
|
125
|
-
// classes
|
|
126
|
-
{
|
|
127
|
-
regex: /^(?:export\s+)?(?:default\s+)?class\s+(\w+)/gm,
|
|
128
|
-
type: "class"
|
|
129
|
-
},
|
|
130
|
-
// TypeScript interfaces
|
|
131
|
-
{
|
|
132
|
-
regex: /^(?:export\s+)?interface\s+(\w+)/gm,
|
|
133
|
-
type: "interface"
|
|
134
|
-
},
|
|
135
|
-
// TypeScript types
|
|
136
|
-
{
|
|
137
|
-
regex: /^(?:export\s+)?type\s+(\w+)\s*=/gm,
|
|
138
|
-
type: "type"
|
|
139
|
-
},
|
|
140
|
-
// Express routes
|
|
141
|
-
{
|
|
142
|
-
regex: /(?:app|router)\.(get|post|put|delete|patch)\s*\(\s*['"]([^'"]+)['"]/gm,
|
|
143
|
-
type: "api"
|
|
144
|
-
}
|
|
145
|
-
];
|
|
146
|
-
for (const { regex, type } of patterns) {
|
|
146
|
+
for (const { regex, type } of exports2.jsEntityPatterns) {
|
|
147
147
|
let match;
|
|
148
148
|
regex.lastIndex = 0;
|
|
149
149
|
while ((match = regex.exec(code)) !== null) {
|
|
@@ -222,7 +222,8 @@ var require_javascript = __commonJS({
|
|
|
222
222
|
extensions: [".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"],
|
|
223
223
|
extractEntities,
|
|
224
224
|
extractImports,
|
|
225
|
-
extractExports
|
|
225
|
+
extractExports,
|
|
226
|
+
entityPatterns: exports2.jsEntityPatterns
|
|
226
227
|
};
|
|
227
228
|
(0, registry_1.registerParser)(JavaScriptParser);
|
|
228
229
|
}
|
|
@@ -233,6 +234,7 @@ var require_python = __commonJS({
|
|
|
233
234
|
"dist/languages/python.js"(exports2) {
|
|
234
235
|
"use strict";
|
|
235
236
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
237
|
+
exports2.pyEntityPatterns = void 0;
|
|
236
238
|
var registry_1 = require_registry();
|
|
237
239
|
var constants_1 = require_constants();
|
|
238
240
|
function estimateComplexity(code, name) {
|
|
@@ -257,21 +259,21 @@ var require_python = __commonJS({
|
|
|
257
259
|
return "medium";
|
|
258
260
|
return "high";
|
|
259
261
|
}
|
|
262
|
+
exports2.pyEntityPatterns = [
|
|
263
|
+
// regular functions
|
|
264
|
+
{
|
|
265
|
+
regex: /^(?:async\s+)?def\s+(\w+)\s*\(/gm,
|
|
266
|
+
type: "function"
|
|
267
|
+
},
|
|
268
|
+
// classes
|
|
269
|
+
{
|
|
270
|
+
regex: /^class\s+(\w+)(?:\s*\([^)]*\))?\s*:/gm,
|
|
271
|
+
type: "class"
|
|
272
|
+
}
|
|
273
|
+
];
|
|
260
274
|
function extractEntities(code, filePath) {
|
|
261
275
|
const entities = [];
|
|
262
|
-
const
|
|
263
|
-
// regular functions
|
|
264
|
-
{
|
|
265
|
-
regex: /^(?:async\s+)?def\s+(\w+)\s*\(/gm,
|
|
266
|
-
type: "function"
|
|
267
|
-
},
|
|
268
|
-
// classes
|
|
269
|
-
{
|
|
270
|
-
regex: /^class\s+(\w+)(?:\s*\([^)]*\))?\s*:/gm,
|
|
271
|
-
type: "class"
|
|
272
|
-
}
|
|
273
|
-
];
|
|
274
|
-
for (const { regex, type } of patterns) {
|
|
276
|
+
for (const { regex, type } of exports2.pyEntityPatterns) {
|
|
275
277
|
regex.lastIndex = 0;
|
|
276
278
|
let match;
|
|
277
279
|
while ((match = regex.exec(code)) !== null) {
|
|
@@ -325,12 +327,88 @@ var require_python = __commonJS({
|
|
|
325
327
|
extensions: [".py"],
|
|
326
328
|
extractEntities,
|
|
327
329
|
extractImports,
|
|
328
|
-
extractExports
|
|
330
|
+
extractExports,
|
|
331
|
+
entityPatterns: exports2.pyEntityPatterns
|
|
329
332
|
};
|
|
330
333
|
(0, registry_1.registerParser)(PythonParser);
|
|
331
334
|
}
|
|
332
335
|
});
|
|
333
336
|
|
|
337
|
+
// dist/languages/go.js
|
|
338
|
+
var require_go = __commonJS({
|
|
339
|
+
"dist/languages/go.js"(exports2) {
|
|
340
|
+
"use strict";
|
|
341
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
342
|
+
exports2.goEntityPatterns = void 0;
|
|
343
|
+
var registry_1 = require_registry();
|
|
344
|
+
exports2.goEntityPatterns = [
|
|
345
|
+
// functions (including methods: func (r *Receiver) Name(...))
|
|
346
|
+
{
|
|
347
|
+
regex: /^func\s+(?:\(\w+\s+\*?\w+\)\s+)?(\w+)\s*\(/gm,
|
|
348
|
+
type: "function"
|
|
349
|
+
},
|
|
350
|
+
// type declarations (structs, interfaces, type aliases)
|
|
351
|
+
{
|
|
352
|
+
regex: /^type\s+(\w+)\s+(?:struct|interface)/gm,
|
|
353
|
+
type: "class"
|
|
354
|
+
}
|
|
355
|
+
];
|
|
356
|
+
function extractEntities(code, filePath) {
|
|
357
|
+
const entities = [];
|
|
358
|
+
for (const { regex, type } of exports2.goEntityPatterns) {
|
|
359
|
+
regex.lastIndex = 0;
|
|
360
|
+
let match;
|
|
361
|
+
while ((match = regex.exec(code)) !== null) {
|
|
362
|
+
const name = match[1];
|
|
363
|
+
if (entities.some((e) => e.name === name))
|
|
364
|
+
continue;
|
|
365
|
+
const upToMatch = code.slice(0, match.index);
|
|
366
|
+
const line = upToMatch.split("\n").length;
|
|
367
|
+
entities.push({ name, type, line, complexity: "low" });
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return entities;
|
|
371
|
+
}
|
|
372
|
+
function extractImports(code) {
|
|
373
|
+
const imports = [];
|
|
374
|
+
const singlePattern = /^import\s+(?:\w+\s+)?["']([^"']+)["']/gm;
|
|
375
|
+
let match;
|
|
376
|
+
while ((match = singlePattern.exec(code)) !== null) {
|
|
377
|
+
imports.push({ source: match[1], names: [match[1]], isLocal: match[1].startsWith(".") });
|
|
378
|
+
}
|
|
379
|
+
const blockPattern = /import\s+\(([^)]+)\)/gs;
|
|
380
|
+
while ((match = blockPattern.exec(code)) !== null) {
|
|
381
|
+
const lines = match[1].split("\n");
|
|
382
|
+
for (const line of lines) {
|
|
383
|
+
const pkgMatch = line.match(/(?:\w+\s+)?["']([^"']+)["']/);
|
|
384
|
+
if (pkgMatch) {
|
|
385
|
+
imports.push({ source: pkgMatch[1], names: [pkgMatch[1]], isLocal: pkgMatch[1].startsWith(".") });
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return imports;
|
|
390
|
+
}
|
|
391
|
+
function extractExports(code) {
|
|
392
|
+
const exports3 = [];
|
|
393
|
+
const pattern = /^func\s+(?:\(\w+\s+\*?\w+\)\s+)?([A-Z]\w*)\s*\(/gm;
|
|
394
|
+
let match;
|
|
395
|
+
while ((match = pattern.exec(code)) !== null) {
|
|
396
|
+
exports3.push(match[1]);
|
|
397
|
+
}
|
|
398
|
+
return [...new Set(exports3)];
|
|
399
|
+
}
|
|
400
|
+
var GoParser = {
|
|
401
|
+
lang: "go",
|
|
402
|
+
extensions: [".go"],
|
|
403
|
+
extractEntities,
|
|
404
|
+
extractImports,
|
|
405
|
+
extractExports,
|
|
406
|
+
entityPatterns: exports2.goEntityPatterns
|
|
407
|
+
};
|
|
408
|
+
(0, registry_1.registerParser)(GoParser);
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
334
412
|
// dist/stages/collector.js
|
|
335
413
|
var require_collector = __commonJS({
|
|
336
414
|
"dist/stages/collector.js"(exports2) {
|
|
@@ -812,6 +890,157 @@ var require_output = __commonJS({
|
|
|
812
890
|
}
|
|
813
891
|
});
|
|
814
892
|
|
|
893
|
+
// dist/stages/gitdiff.js
|
|
894
|
+
var require_gitdiff = __commonJS({
|
|
895
|
+
"dist/stages/gitdiff.js"(exports2) {
|
|
896
|
+
"use strict";
|
|
897
|
+
var __importDefault2 = exports2 && exports2.__importDefault || function(mod) {
|
|
898
|
+
return mod && mod.__esModule ? mod : { "default": mod };
|
|
899
|
+
};
|
|
900
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
901
|
+
exports2.getChangedEntities = getChangedEntities;
|
|
902
|
+
var child_process_1 = require("child_process");
|
|
903
|
+
var path_1 = __importDefault2(require("path"));
|
|
904
|
+
var registry_1 = require_registry();
|
|
905
|
+
function getChangedEntities(options) {
|
|
906
|
+
const diff = runGitDiff(options);
|
|
907
|
+
if (!diff)
|
|
908
|
+
return [];
|
|
909
|
+
return parseDiff(diff, options.projectDir);
|
|
910
|
+
}
|
|
911
|
+
function runGitDiff(options) {
|
|
912
|
+
const { projectDir: projectDir2, mode, commit, from, to } = options;
|
|
913
|
+
let command;
|
|
914
|
+
if (mode === "uncommitted") {
|
|
915
|
+
command = "git diff HEAD";
|
|
916
|
+
} else if (mode === "last-commit") {
|
|
917
|
+
if (commit) {
|
|
918
|
+
command = `git diff ${commit}~1 ${commit}`;
|
|
919
|
+
} else {
|
|
920
|
+
command = "git diff HEAD~1 HEAD";
|
|
921
|
+
}
|
|
922
|
+
} else if (mode === "branches") {
|
|
923
|
+
if (!from || !to) {
|
|
924
|
+
console.warn("\u26A0 --from and --to are required for branch comparison");
|
|
925
|
+
return null;
|
|
926
|
+
}
|
|
927
|
+
command = `git diff ${from}...${to}`;
|
|
928
|
+
} else {
|
|
929
|
+
return null;
|
|
930
|
+
}
|
|
931
|
+
try {
|
|
932
|
+
const result = (0, child_process_1.execSync)(command, {
|
|
933
|
+
cwd: projectDir2,
|
|
934
|
+
encoding: "utf-8",
|
|
935
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
936
|
+
});
|
|
937
|
+
return result || null;
|
|
938
|
+
} catch (err) {
|
|
939
|
+
console.warn(`\u26A0 Git command failed: ${command}`);
|
|
940
|
+
console.warn(` Make sure ${projectDir2} is a git repository`);
|
|
941
|
+
return null;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
function parseDiff(diff, projectDir2) {
|
|
945
|
+
const entities = [];
|
|
946
|
+
const lines = diff.split("\n");
|
|
947
|
+
let currentFile = "";
|
|
948
|
+
let changeType = "modified";
|
|
949
|
+
for (let i = 0; i < lines.length; i++) {
|
|
950
|
+
const line = lines[i];
|
|
951
|
+
if (line.startsWith("diff --git")) {
|
|
952
|
+
const fileMatch = line.match(/b\/(.+)$/);
|
|
953
|
+
if (fileMatch) {
|
|
954
|
+
currentFile = fileMatch[1];
|
|
955
|
+
}
|
|
956
|
+
changeType = "modified";
|
|
957
|
+
continue;
|
|
958
|
+
}
|
|
959
|
+
if (line.startsWith("new file mode")) {
|
|
960
|
+
changeType = "added";
|
|
961
|
+
continue;
|
|
962
|
+
}
|
|
963
|
+
if (line.startsWith("deleted file mode")) {
|
|
964
|
+
changeType = "deleted";
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
967
|
+
if (line.startsWith("index ")) {
|
|
968
|
+
if (changeType === "modified")
|
|
969
|
+
changeType = "modified";
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
972
|
+
if (line.startsWith("@@")) {
|
|
973
|
+
const contextMatch = line.match(/@@[^@]*@@\s*(.+)$/);
|
|
974
|
+
if (contextMatch) {
|
|
975
|
+
const context = contextMatch[1].trim();
|
|
976
|
+
const entity = extractEntityFromContext(context, currentFile);
|
|
977
|
+
if (entity) {
|
|
978
|
+
const description = buildDescription(lines, i, entity.name);
|
|
979
|
+
const alreadyFound = entities.some((e) => e.name === entity.name && e.file === currentFile);
|
|
980
|
+
if (!alreadyFound) {
|
|
981
|
+
entities.push({
|
|
982
|
+
name: entity.name,
|
|
983
|
+
file: currentFile,
|
|
984
|
+
changeType,
|
|
985
|
+
description
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
return entities;
|
|
994
|
+
}
|
|
995
|
+
function extractEntityFromContext(context, file) {
|
|
996
|
+
const ext = path_1.default.extname(file).toLowerCase();
|
|
997
|
+
const parser = (0, registry_1.getLanguageParser)(ext);
|
|
998
|
+
if (parser?.entityPatterns) {
|
|
999
|
+
for (const { regex, type } of parser.entityPatterns) {
|
|
1000
|
+
if (type === "api")
|
|
1001
|
+
continue;
|
|
1002
|
+
const singleLineRegex = new RegExp(regex.source, regex.flags.replace("g", ""));
|
|
1003
|
+
const m = singleLineRegex.exec(context);
|
|
1004
|
+
if (m?.[1])
|
|
1005
|
+
return { name: m[1], type };
|
|
1006
|
+
}
|
|
1007
|
+
return null;
|
|
1008
|
+
}
|
|
1009
|
+
if ([".java", ".cs"].includes(ext)) {
|
|
1010
|
+
const m = context.match(/(?:public|private|protected|static|override|async|virtual)\s+\S+\s+(\w+)\s*\(/);
|
|
1011
|
+
if (m)
|
|
1012
|
+
return { name: m[1], type: "method" };
|
|
1013
|
+
}
|
|
1014
|
+
return null;
|
|
1015
|
+
}
|
|
1016
|
+
function buildDescription(lines, contextIdx, entityName) {
|
|
1017
|
+
const added = [];
|
|
1018
|
+
const removed = [];
|
|
1019
|
+
for (let i = contextIdx + 1; i < Math.min(contextIdx + 20, lines.length); i++) {
|
|
1020
|
+
const line = lines[i];
|
|
1021
|
+
if (line.startsWith("@@") || line.startsWith("diff"))
|
|
1022
|
+
break;
|
|
1023
|
+
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
1024
|
+
added.push(line.slice(1).trim());
|
|
1025
|
+
}
|
|
1026
|
+
if (line.startsWith("-") && !line.startsWith("---")) {
|
|
1027
|
+
removed.push(line.slice(1).trim());
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
if (added.length === 0 && removed.length > 0) {
|
|
1031
|
+
return `${entityName}: ${removed.length} line(s) removed`;
|
|
1032
|
+
}
|
|
1033
|
+
if (added.length > 0 && removed.length === 0) {
|
|
1034
|
+
return `${entityName}: ${added.length} line(s) added`;
|
|
1035
|
+
}
|
|
1036
|
+
if (added.length > 0 && removed.length > 0) {
|
|
1037
|
+
return `${entityName}: ${removed.length} line(s) changed to ${added.length} new line(s)`;
|
|
1038
|
+
}
|
|
1039
|
+
return `${entityName}: modified`;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
|
|
815
1044
|
// dist/main.js
|
|
816
1045
|
var __importDefault = exports && exports.__importDefault || function(mod) {
|
|
817
1046
|
return mod && mod.__esModule ? mod : { "default": mod };
|
|
@@ -819,6 +1048,7 @@ var __importDefault = exports && exports.__importDefault || function(mod) {
|
|
|
819
1048
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
820
1049
|
require_javascript();
|
|
821
1050
|
require_python();
|
|
1051
|
+
require_go();
|
|
822
1052
|
var fs_1 = __importDefault(require("fs"));
|
|
823
1053
|
var collector_1 = require_collector();
|
|
824
1054
|
var parser_1 = require_parser();
|
|
@@ -826,6 +1056,7 @@ var graph_1 = require_graph();
|
|
|
826
1056
|
var metrics_1 = require_metrics();
|
|
827
1057
|
var impact_1 = require_impact();
|
|
828
1058
|
var output_1 = require_output();
|
|
1059
|
+
var gitdiff_1 = require_gitdiff();
|
|
829
1060
|
var args = process.argv.slice(2);
|
|
830
1061
|
var noColor = args.includes("--no-color");
|
|
831
1062
|
var verbose = args.includes("--verbose");
|
|
@@ -846,11 +1077,11 @@ function getFlag(flag) {
|
|
|
846
1077
|
}
|
|
847
1078
|
function printHelp() {
|
|
848
1079
|
console.log(`
|
|
849
|
-
${bold("DepGraph
|
|
1080
|
+
${bold("DepGraph")} ${dim("v1.0.2")}
|
|
850
1081
|
${dim("Dependency mapping \xB7 Impact simulation \xB7 Developer intelligence")}
|
|
851
1082
|
|
|
852
1083
|
${bold("USAGE")}
|
|
853
|
-
|
|
1084
|
+
depgraph ${cyan("<projectDir>")} ${dim("[options]")}
|
|
854
1085
|
|
|
855
1086
|
${bold("OPTIONS")}
|
|
856
1087
|
${cyan("--output")} ${dim("<file>")} Output path ${dim("(default: ./depgraph-output.json)")}
|
|
@@ -859,24 +1090,43 @@ ${bold("OPTIONS")}
|
|
|
859
1090
|
${cyan("--no-color")} Disable colors ${dim("(for CI)")}
|
|
860
1091
|
${cyan("--help, -h")} Show this help message
|
|
861
1092
|
|
|
1093
|
+
${bold("GIT FLAGS")}
|
|
1094
|
+
${cyan("--git-impact")} Auto-detect changes from git diff
|
|
1095
|
+
${cyan("--commit")} ${dim("<sha>")} Analyze a specific commit
|
|
1096
|
+
${cyan("--from")} ${dim("<branch>")} Compare from this branch
|
|
1097
|
+
${cyan("--to")} ${dim("<branch>")} Compare to this branch
|
|
1098
|
+
|
|
862
1099
|
${bold("EXAMPLES")}
|
|
863
1100
|
${dim("# Map a project")}
|
|
864
|
-
|
|
1101
|
+
depgraph ./my-app
|
|
865
1102
|
|
|
866
1103
|
${dim("# Map with custom output")}
|
|
867
|
-
|
|
1104
|
+
depgraph ./my-app --output ./reports/graph.json
|
|
868
1105
|
|
|
869
1106
|
${dim("# Simulate a change")}
|
|
870
|
-
|
|
1107
|
+
depgraph ./my-app --impact "getUserById" "removing userId param"
|
|
871
1108
|
|
|
872
1109
|
${dim("# CI mode")}
|
|
873
|
-
|
|
1110
|
+
depgraph ./src --no-color --output ./ci/depgraph.json
|
|
1111
|
+
|
|
1112
|
+
${bold("GIT EXAMPLES")}
|
|
1113
|
+
${dim("# Analyze uncommitted changes")}
|
|
1114
|
+
depgraph ./src --git-impact
|
|
1115
|
+
|
|
1116
|
+
${dim("# Analyze last commit")}
|
|
1117
|
+
depgraph ./src --git-impact --commit HEAD
|
|
1118
|
+
|
|
1119
|
+
${dim("# Compare two branches")}
|
|
1120
|
+
depgraph ./src --git-impact --from main --to feature/my-branch
|
|
1121
|
+
|
|
1122
|
+
${dim("# Specific commit")}
|
|
1123
|
+
depgraph ./src --git-impact --commit abc1234
|
|
874
1124
|
`);
|
|
875
1125
|
}
|
|
876
1126
|
function printBanner() {
|
|
877
1127
|
console.log(`
|
|
878
1128
|
${bold("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501")}
|
|
879
|
-
${bold(" DepGraph
|
|
1129
|
+
${bold(" DepGraph")} ${dim("v1.0.0")}
|
|
880
1130
|
${bold("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501")}
|
|
881
1131
|
`);
|
|
882
1132
|
}
|
|
@@ -936,6 +1186,11 @@ var projectDir = args[0];
|
|
|
936
1186
|
var outputPath = getFlag("--output") ?? "./depgraph-output.json";
|
|
937
1187
|
var impactTarget = getFlag("--impact");
|
|
938
1188
|
var impactDesc = impactTarget ? args[args.indexOf("--impact") + 2] ?? "no description provided" : void 0;
|
|
1189
|
+
var gitImpact = args.includes("--git-impact");
|
|
1190
|
+
var gitCommit = getFlag("--commit");
|
|
1191
|
+
var gitFrom = getFlag("--from");
|
|
1192
|
+
var gitTo = getFlag("--to");
|
|
1193
|
+
var gitMode = gitFrom && gitTo ? "branches" : gitCommit ? "last-commit" : "uncommitted";
|
|
939
1194
|
printBanner();
|
|
940
1195
|
console.log(`${bold("\u{1F50D} Scanning")} ${cyan(projectDir)}
|
|
941
1196
|
`);
|
|
@@ -959,6 +1214,34 @@ try {
|
|
|
959
1214
|
if (impactTarget) {
|
|
960
1215
|
impact = (0, impact_1.simulateImpact)(metrics, impactTarget, impactDesc ?? "");
|
|
961
1216
|
printImpact(impact);
|
|
1217
|
+
} else if (gitImpact) {
|
|
1218
|
+
console.log(`
|
|
1219
|
+
${bold("\u{1F50D} Reading git diff...")}`);
|
|
1220
|
+
const changed = (0, gitdiff_1.getChangedEntities)({
|
|
1221
|
+
projectDir,
|
|
1222
|
+
mode: gitMode,
|
|
1223
|
+
commit: gitCommit,
|
|
1224
|
+
from: gitFrom,
|
|
1225
|
+
to: gitTo
|
|
1226
|
+
});
|
|
1227
|
+
if (changed.length === 0) {
|
|
1228
|
+
console.log(`
|
|
1229
|
+
${green("\u2713")} No changed entities found in diff`);
|
|
1230
|
+
} else {
|
|
1231
|
+
console.log(`
|
|
1232
|
+
${bold(`Found ${changed.length} changed entity(s):`)}`);
|
|
1233
|
+
for (const entity of changed) {
|
|
1234
|
+
console.log(` ${dim("\u2192")} ${entity.name} ${dim(`(${entity.file})`)}`);
|
|
1235
|
+
}
|
|
1236
|
+
console.log(`
|
|
1237
|
+
${bold("Running impact simulation...")}`);
|
|
1238
|
+
for (const entity of changed) {
|
|
1239
|
+
console.log(`
|
|
1240
|
+
${dim("\u2500".repeat(42))}`);
|
|
1241
|
+
const result = (0, impact_1.simulateImpact)(metrics, entity.name, entity.description);
|
|
1242
|
+
printImpact(result);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
962
1245
|
}
|
|
963
1246
|
(0, output_1.writeOutput)(metrics, parsed, outputPath, impact);
|
|
964
1247
|
} catch (err) {
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# DepGraph — Developer Documentation
|
|
2
|
+
|
|
3
|
+
Welcome to the internal documentation for **DepGraph Core**.
|
|
4
|
+
|
|
5
|
+
This folder explains how every moving part of the compiler works so you can contribute confidently, extend it with new languages, or debug an unexpected result.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Contents
|
|
10
|
+
|
|
11
|
+
| File | What it covers |
|
|
12
|
+
|---|---|
|
|
13
|
+
| [architecture.md](./architecture.md) | Big-picture overview — how the pipeline fits together |
|
|
14
|
+
| [stage-collector.md](./stage-collector.md) | Stage 1 — File collection (scanning the project directory) |
|
|
15
|
+
| [stage-parser.md](./stage-parser.md) | Stage 2 — Source file parsing (entities, imports, exports) |
|
|
16
|
+
| [stage-graph.md](./stage-graph.md) | Stage 3 — Dependency graph construction |
|
|
17
|
+
| [stage-metrics.md](./stage-metrics.md) | Stage 4 — Metrics computation (centrality, degrees) |
|
|
18
|
+
| [stage-impact.md](./stage-impact.md) | Stage 5 — Impact simulation (BFS + risk scoring) |
|
|
19
|
+
| [stage-output.md](./stage-output.md) | Stage 6 — JSON report generation |
|
|
20
|
+
| [language-registry.md](./language-registry.md) | The language plugin system — how to add a new language |
|
|
21
|
+
| [data-types.md](./data-types.md) | All shared TypeScript interfaces, explained |
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Quick Mental Model
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
Your project folder
|
|
29
|
+
│
|
|
30
|
+
▼
|
|
31
|
+
┌─────────────┐
|
|
32
|
+
│ Collector │ ← finds every eligible source file
|
|
33
|
+
└──────┬──────┘
|
|
34
|
+
▼
|
|
35
|
+
┌─────────────┐
|
|
36
|
+
│ Parser │ ← reads each file; extracts entities, imports, exports
|
|
37
|
+
└──────┬──────┘
|
|
38
|
+
▼
|
|
39
|
+
┌─────────────┐
|
|
40
|
+
│ Graph │ ← links entities together through their imports
|
|
41
|
+
└──────┬──────┘
|
|
42
|
+
▼
|
|
43
|
+
┌─────────────┐
|
|
44
|
+
│ Metrics │ ← computes in/out-degree and centrality for every node
|
|
45
|
+
└──────┬──────┘
|
|
46
|
+
▼
|
|
47
|
+
┌─────────────┐
|
|
48
|
+
│ Impact │ ← (optional) BFS from a target node → risk report
|
|
49
|
+
└──────┬──────┘
|
|
50
|
+
▼
|
|
51
|
+
┌─────────────┐
|
|
52
|
+
│ Output │ ← serialises everything to depgraph-output.json
|
|
53
|
+
└─────────────┘
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Each stage is self-contained. Data flows **forward only** — no stage reaches back to an earlier one.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Architecture Overview
|
|
2
|
+
|
|
3
|
+
> **File**: `src/main.ts` — the orchestration entry point
|
|
4
|
+
> **Role**: Wires together all six stages in sequential order and handles CLI argument parsing.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## The Pipeline at a Glance
|
|
9
|
+
|
|
10
|
+
DepGraph is a **linear, stage-based compiler**. There is no framework magic — it's a plain chain of function calls where the output of each stage becomes the input of the next.
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
collectFiles()
|
|
14
|
+
│ string[] (file paths)
|
|
15
|
+
▼
|
|
16
|
+
parseFiles()
|
|
17
|
+
│ ParsedFile[] (entities, imports, exports per file)
|
|
18
|
+
▼
|
|
19
|
+
buildGraph()
|
|
20
|
+
│ DepGraph (nodes + edges map)
|
|
21
|
+
▼
|
|
22
|
+
computeMetrics()
|
|
23
|
+
│ DepGraph (same graph, nodes now have inDegree / outDegree / centralityScore)
|
|
24
|
+
▼
|
|
25
|
+
simulateImpact() ← only runs when --impact flag is provided
|
|
26
|
+
│ ImpactReport
|
|
27
|
+
▼
|
|
28
|
+
writeOutput()
|
|
29
|
+
depgraph-output.json
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Every stage lives in its own file under `src/stages/`. They are pure functions — given the same input they always produce the same output, and they never touch the file system except for the collector (reading) and output (writing).
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Entry Point — `src/main.ts`
|
|
37
|
+
|
|
38
|
+
`main.ts` does four things and nothing else:
|
|
39
|
+
|
|
40
|
+
1. **Parses CLI flags** — `--output`, `--impact`, `--verbose`, `--no-color`, `--help`
|
|
41
|
+
2. **Runs the pipeline** inside a single `try/catch` block
|
|
42
|
+
3. **Prints progress** to stdout (banner, summary, impact table)
|
|
43
|
+
4. **Exits with code 1** on any unhandled error
|
|
44
|
+
|
|
45
|
+
### Key variables wired at startup
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
const projectDir = args[0]; // the path to scan
|
|
49
|
+
const outputPath = getFlag('--output'); // where to write the JSON
|
|
50
|
+
const impactTarget = getFlag('--impact'); // entity name to simulate
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Execution order in the `try` block
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
const files = collectFiles(projectDir); // Stage 1
|
|
57
|
+
const parsed = parseFiles(files); // Stage 2
|
|
58
|
+
const graph = buildGraph(parsed); // Stage 3
|
|
59
|
+
const metrics = computeMetrics(graph); // Stage 4
|
|
60
|
+
// Stage 5 — optional
|
|
61
|
+
const impact = impactTarget
|
|
62
|
+
? simulateImpact(metrics, impactTarget, impactDesc)
|
|
63
|
+
: undefined;
|
|
64
|
+
writeOutput(metrics, parsed, outputPath, impact); // Stage 6
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Design Principles
|
|
70
|
+
|
|
71
|
+
| Principle | How it's applied |
|
|
72
|
+
|---|---|
|
|
73
|
+
| **Single responsibility** | Each stage file exports exactly one primary function |
|
|
74
|
+
| **No hidden state** | All data passed explicitly between stages |
|
|
75
|
+
| **Fail loudly** | `process.exit(1)` on unrecoverable errors — no silent swallowing |
|
|
76
|
+
| **Zero runtime dependencies** | Only Node.js built-ins (`fs`, `path`) plus TypeScript |
|
|
77
|
+
| **Plugin language support** | Languages register themselves via the registry (see [language-registry.md](./language-registry.md)) |
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Adding a New Stage
|
|
82
|
+
|
|
83
|
+
If you need to insert a new processing step (e.g. a linting stage), follow this pattern:
|
|
84
|
+
|
|
85
|
+
1. Create `src/stages/my-stage.ts` and export a pure function.
|
|
86
|
+
2. Import and call it in `main.ts` after the stage it depends on.
|
|
87
|
+
3. Thread its output into downstream stages.
|
|
88
|
+
|
|
89
|
+
No registration, no magic — just a function call.
|