@girardelli/architect 1.1.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.
Files changed (66) hide show
  1. package/CONTRIBUTING.md +140 -0
  2. package/LICENSE +21 -0
  3. package/PROJECT_STRUCTURE.txt +168 -0
  4. package/README.md +269 -0
  5. package/dist/analyzer.d.ts +17 -0
  6. package/dist/analyzer.d.ts.map +1 -0
  7. package/dist/analyzer.js +254 -0
  8. package/dist/analyzer.js.map +1 -0
  9. package/dist/anti-patterns.d.ts +17 -0
  10. package/dist/anti-patterns.d.ts.map +1 -0
  11. package/dist/anti-patterns.js +211 -0
  12. package/dist/anti-patterns.js.map +1 -0
  13. package/dist/cli.d.ts +15 -0
  14. package/dist/cli.d.ts.map +1 -0
  15. package/dist/cli.js +164 -0
  16. package/dist/cli.js.map +1 -0
  17. package/dist/config.d.ts +6 -0
  18. package/dist/config.d.ts.map +1 -0
  19. package/dist/config.js +73 -0
  20. package/dist/config.js.map +1 -0
  21. package/dist/diagram.d.ts +9 -0
  22. package/dist/diagram.d.ts.map +1 -0
  23. package/dist/diagram.js +116 -0
  24. package/dist/diagram.js.map +1 -0
  25. package/dist/html-reporter.d.ts +23 -0
  26. package/dist/html-reporter.d.ts.map +1 -0
  27. package/dist/html-reporter.js +454 -0
  28. package/dist/html-reporter.js.map +1 -0
  29. package/dist/index.d.ts +48 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +151 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/reporter.d.ts +13 -0
  34. package/dist/reporter.d.ts.map +1 -0
  35. package/dist/reporter.js +135 -0
  36. package/dist/reporter.js.map +1 -0
  37. package/dist/scanner.d.ts +25 -0
  38. package/dist/scanner.d.ts.map +1 -0
  39. package/dist/scanner.js +288 -0
  40. package/dist/scanner.js.map +1 -0
  41. package/dist/scorer.d.ts +15 -0
  42. package/dist/scorer.d.ts.map +1 -0
  43. package/dist/scorer.js +172 -0
  44. package/dist/scorer.js.map +1 -0
  45. package/dist/types.d.ts +106 -0
  46. package/dist/types.d.ts.map +1 -0
  47. package/dist/types.js +2 -0
  48. package/dist/types.js.map +1 -0
  49. package/examples/sample-report.md +207 -0
  50. package/jest.config.js +18 -0
  51. package/package.json +70 -0
  52. package/src/analyzer.ts +310 -0
  53. package/src/anti-patterns.ts +264 -0
  54. package/src/cli.ts +183 -0
  55. package/src/config.ts +82 -0
  56. package/src/diagram.ts +144 -0
  57. package/src/html-reporter.ts +485 -0
  58. package/src/index.ts +212 -0
  59. package/src/reporter.ts +166 -0
  60. package/src/scanner.ts +298 -0
  61. package/src/scorer.ts +193 -0
  62. package/src/types.ts +114 -0
  63. package/tests/anti-patterns.test.ts +94 -0
  64. package/tests/scanner.test.ts +55 -0
  65. package/tests/scorer.test.ts +80 -0
  66. package/tsconfig.json +24 -0
package/src/types.ts ADDED
@@ -0,0 +1,114 @@
1
+ export interface FileNode {
2
+ path: string;
3
+ name: string;
4
+ type: 'file' | 'directory';
5
+ extension?: string;
6
+ lines?: number;
7
+ language?: string;
8
+ children?: FileNode[];
9
+ imports?: string[];
10
+ exports?: string[];
11
+ }
12
+
13
+ export interface DependencyEdge {
14
+ from: string;
15
+ to: string;
16
+ type: 'import' | 'export' | 'inheritance' | 'composition';
17
+ weight: number;
18
+ }
19
+
20
+ export interface Layer {
21
+ name: 'API' | 'Service' | 'Data' | 'UI' | 'Infrastructure';
22
+ files: string[];
23
+ description: string;
24
+ }
25
+
26
+ export interface AntiPattern {
27
+ name: string;
28
+ severity: 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW';
29
+ location: string;
30
+ description: string;
31
+ suggestion: string;
32
+ affectedFiles?: string[];
33
+ metrics?: Record<string, number | string>;
34
+ }
35
+
36
+ export interface ScoreComponent {
37
+ name: string;
38
+ score: number;
39
+ maxScore: number;
40
+ weight: number;
41
+ explanation: string;
42
+ }
43
+
44
+ export interface ArchitectureScore {
45
+ overall: number;
46
+ components: ScoreComponent[];
47
+ breakdown: {
48
+ modularity: number;
49
+ coupling: number;
50
+ cohesion: number;
51
+ layering: number;
52
+ };
53
+ }
54
+
55
+ export interface ProjectInfo {
56
+ path: string;
57
+ name: string;
58
+ frameworks: string[];
59
+ totalFiles: number;
60
+ totalLines: number;
61
+ primaryLanguages: string[];
62
+ fileTree?: FileNode;
63
+ }
64
+
65
+ export interface AnalysisReport {
66
+ timestamp: string;
67
+ projectInfo: ProjectInfo;
68
+ score: ArchitectureScore;
69
+ antiPatterns: AntiPattern[];
70
+ layers: Layer[];
71
+ dependencyGraph: {
72
+ nodes: string[];
73
+ edges: DependencyEdge[];
74
+ };
75
+ suggestions: {
76
+ priority: 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW';
77
+ title: string;
78
+ description: string;
79
+ impact: string;
80
+ }[];
81
+ diagram: {
82
+ mermaid: string;
83
+ type: 'component' | 'layer' | 'dependency';
84
+ };
85
+ }
86
+
87
+ export interface ArchitectConfig {
88
+ ignore?: string[];
89
+ frameworks?: {
90
+ detect?: boolean;
91
+ };
92
+ antiPatterns?: {
93
+ godClass?: {
94
+ linesThreshold?: number;
95
+ methodsThreshold?: number;
96
+ };
97
+ shotgunSurgery?: {
98
+ changePropagationThreshold?: number;
99
+ };
100
+ };
101
+ score?: {
102
+ modularity?: number;
103
+ coupling?: number;
104
+ cohesion?: number;
105
+ layering?: number;
106
+ };
107
+ }
108
+
109
+ export interface ParsedImport {
110
+ source: string;
111
+ names: string[];
112
+ isDefault: boolean;
113
+ isNamespace: boolean;
114
+ }
@@ -0,0 +1,94 @@
1
+ import { AntiPatternDetector } from '../src/anti-patterns.js';
2
+ import { ArchitectConfig, FileNode, AntiPattern } from '../src/types.js';
3
+
4
+ describe('AntiPatternDetector', () => {
5
+ const mockConfig: ArchitectConfig = {
6
+ antiPatterns: {
7
+ godClass: {
8
+ linesThreshold: 500,
9
+ methodsThreshold: 10,
10
+ },
11
+ shotgunSurgery: {
12
+ changePropagationThreshold: 5,
13
+ },
14
+ },
15
+ };
16
+
17
+ const mockFileTree: FileNode = {
18
+ path: '/project',
19
+ name: 'project',
20
+ type: 'directory',
21
+ children: [
22
+ {
23
+ path: '/project/src/services/UserManager.ts',
24
+ name: 'UserManager.ts',
25
+ type: 'file',
26
+ extension: '.ts',
27
+ lines: 850,
28
+ },
29
+ {
30
+ path: '/project/src/utils/helper.ts',
31
+ name: 'helper.ts',
32
+ type: 'file',
33
+ extension: '.ts',
34
+ lines: 200,
35
+ },
36
+ ],
37
+ };
38
+
39
+ const mockDependencies = new Map<string, Set<string>>([
40
+ ['/project/src/services/UserManager.ts', new Set(['/project/src/utils/helper.ts'])],
41
+ ['/project/src/utils/helper.ts', new Set(['/project/src/services/UserManager.ts'])],
42
+ ]);
43
+
44
+ describe('detect', () => {
45
+ it('should detect anti-patterns in code', () => {
46
+ const detector = new AntiPatternDetector(mockConfig);
47
+ const patterns = detector.detect(mockFileTree, mockDependencies);
48
+
49
+ expect(Array.isArray(patterns)).toBe(true);
50
+ });
51
+
52
+ it('should identify God Classes', () => {
53
+ const detector = new AntiPatternDetector(mockConfig);
54
+ const patterns = detector.detect(mockFileTree, mockDependencies);
55
+
56
+ const godClasses = patterns.filter((p) => p.name === 'God Class');
57
+ expect(godClasses.length).toBeGreaterThanOrEqual(0);
58
+ });
59
+
60
+ it('should sort patterns by severity', () => {
61
+ const detector = new AntiPatternDetector(mockConfig);
62
+ const patterns = detector.detect(mockFileTree, mockDependencies);
63
+
64
+ if (patterns.length > 1) {
65
+ const severityOrder: Record<string, number> = {
66
+ CRITICAL: 0,
67
+ HIGH: 1,
68
+ MEDIUM: 2,
69
+ LOW: 3,
70
+ };
71
+
72
+ for (let i = 0; i < patterns.length - 1; i++) {
73
+ const current = severityOrder[patterns[i].severity];
74
+ const next = severityOrder[patterns[i + 1].severity];
75
+ expect(current).toBeLessThanOrEqual(next);
76
+ }
77
+ }
78
+ });
79
+ });
80
+
81
+ describe('circular dependency detection', () => {
82
+ it('should detect circular dependencies', () => {
83
+ const circularDeps = new Map<string, Set<string>>([
84
+ ['src/auth.ts', new Set(['src/cache.ts'])],
85
+ ['src/cache.ts', new Set(['src/auth.ts'])],
86
+ ]);
87
+
88
+ const detector = new AntiPatternDetector(mockConfig);
89
+ const patterns = detector.detect(mockFileTree, circularDeps);
90
+
91
+ expect(patterns.length).toBeGreaterThanOrEqual(0);
92
+ });
93
+ });
94
+ });
@@ -0,0 +1,55 @@
1
+ import { fileURLToPath } from 'url';
2
+ import path from 'path';
3
+ import { ProjectScanner } from '../src/scanner.js';
4
+ import { ArchitectConfig } from '../src/types.js';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+
9
+ describe('ProjectScanner', () => {
10
+ const mockConfig: ArchitectConfig = {
11
+ ignore: ['node_modules', 'dist', 'coverage'],
12
+ frameworks: { detect: true },
13
+ };
14
+
15
+ describe('scan', () => {
16
+ it('should scan a project directory and return project info', () => {
17
+ const scanner = new ProjectScanner(__dirname, mockConfig);
18
+ const info = scanner.scan();
19
+
20
+ expect(info).toBeDefined();
21
+ expect(info.path).toBe(__dirname);
22
+ expect(info.totalFiles).toBeGreaterThanOrEqual(0);
23
+ expect(info.totalLines).toBeGreaterThanOrEqual(0);
24
+ expect(Array.isArray(info.primaryLanguages)).toBe(true);
25
+ expect(Array.isArray(info.frameworks)).toBe(true);
26
+ });
27
+
28
+ it('should detect TypeScript files', () => {
29
+ const scanner = new ProjectScanner(__dirname, mockConfig);
30
+ const info = scanner.scan();
31
+
32
+ if (info.totalFiles > 0) {
33
+ expect(info.primaryLanguages.length).toBeGreaterThanOrEqual(0);
34
+ }
35
+ });
36
+
37
+ it('should build a file tree structure', () => {
38
+ const scanner = new ProjectScanner(__dirname, mockConfig);
39
+ const info = scanner.scan();
40
+
41
+ expect(info.fileTree).toBeDefined();
42
+ expect(info.fileTree?.type).toBe('directory');
43
+ expect(info.fileTree?.children).toBeDefined();
44
+ });
45
+ });
46
+
47
+ describe('framework detection', () => {
48
+ it('should detect frameworks from configuration files', () => {
49
+ const scanner = new ProjectScanner(__dirname, mockConfig);
50
+ const info = scanner.scan();
51
+
52
+ expect(Array.isArray(info.frameworks)).toBe(true);
53
+ });
54
+ });
55
+ });
@@ -0,0 +1,80 @@
1
+ import { ArchitectureScorer } from '../src/scorer.js';
2
+ import { DependencyEdge, AntiPattern } from '../src/types.js';
3
+
4
+ describe('ArchitectureScorer', () => {
5
+ const scorer = new ArchitectureScorer();
6
+
7
+ const mockEdges: DependencyEdge[] = [
8
+ { from: 'src/a.ts', to: 'src/b.ts', type: 'import', weight: 1 },
9
+ { from: 'src/b.ts', to: 'src/c.ts', type: 'import', weight: 1 },
10
+ { from: 'src/c.ts', to: 'src/d.ts', type: 'import', weight: 1 },
11
+ ];
12
+
13
+ const mockAntiPatterns: AntiPattern[] = [
14
+ {
15
+ name: 'God Class',
16
+ severity: 'CRITICAL',
17
+ location: 'src/Manager.ts',
18
+ description: 'Test',
19
+ suggestion: 'Test',
20
+ },
21
+ ];
22
+
23
+ describe('score', () => {
24
+ it('should return a score between 0 and 100', () => {
25
+ const result = scorer.score(mockEdges, mockAntiPatterns, 50);
26
+
27
+ expect(result.overall).toBeGreaterThanOrEqual(0);
28
+ expect(result.overall).toBeLessThanOrEqual(100);
29
+ });
30
+
31
+ it('should calculate component scores', () => {
32
+ const result = scorer.score(mockEdges, mockAntiPatterns, 50);
33
+
34
+ expect(result.components).toBeDefined();
35
+ expect(result.components.length).toBeGreaterThan(0);
36
+
37
+ for (const component of result.components) {
38
+ expect(component.score).toBeGreaterThanOrEqual(0);
39
+ expect(component.score).toBeLessThanOrEqual(100);
40
+ expect(component.weight).toBeGreaterThan(0);
41
+ }
42
+ });
43
+
44
+ it('should provide breakdown of component scores', () => {
45
+ const result = scorer.score(mockEdges, mockAntiPatterns, 50);
46
+
47
+ expect(result.breakdown).toBeDefined();
48
+ expect(result.breakdown.modularity).toBeGreaterThanOrEqual(0);
49
+ expect(result.breakdown.coupling).toBeGreaterThanOrEqual(0);
50
+ expect(result.breakdown.cohesion).toBeGreaterThanOrEqual(0);
51
+ expect(result.breakdown.layering).toBeGreaterThanOrEqual(0);
52
+ });
53
+
54
+ it('should handle empty edges', () => {
55
+ const result = scorer.score([], mockAntiPatterns, 10);
56
+
57
+ expect(result.overall).toBeGreaterThanOrEqual(0);
58
+ expect(result.overall).toBeLessThanOrEqual(100);
59
+ });
60
+
61
+ it('should penalize anti-patterns in layering score', () => {
62
+ const lotsOfPatterns: AntiPattern[] = Array(10)
63
+ .fill(null)
64
+ .map((_, i) => ({
65
+ name: 'Test Pattern',
66
+ severity: 'HIGH',
67
+ location: `src/file${i}.ts`,
68
+ description: 'Test',
69
+ suggestion: 'Test',
70
+ }));
71
+
72
+ const resultWithPatterns = scorer.score(mockEdges, lotsOfPatterns, 50);
73
+ const resultWithoutPatterns = scorer.score(mockEdges, [], 50);
74
+
75
+ expect(resultWithPatterns.breakdown.layering).toBeLessThanOrEqual(
76
+ resultWithoutPatterns.breakdown.layering
77
+ );
78
+ });
79
+ });
80
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ES2020",
5
+ "lib": ["ES2020"],
6
+ "moduleResolution": "bundler",
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "declaration": true,
15
+ "declarationMap": true,
16
+ "sourceMap": true,
17
+ "noUnusedLocals": false,
18
+ "noUnusedParameters": false,
19
+ "noImplicitReturns": true,
20
+ "noFallthroughCasesInSwitch": true
21
+ },
22
+ "include": ["src/**/*"],
23
+ "exclude": ["node_modules", "dist", "tests"]
24
+ }