tlc-claude-code 1.2.29 → 1.4.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/dashboard/dist/components/AuditPane.d.ts +30 -0
- package/dashboard/dist/components/AuditPane.js +127 -0
- package/dashboard/dist/components/AuditPane.test.d.ts +1 -0
- package/dashboard/dist/components/AuditPane.test.js +339 -0
- package/dashboard/dist/components/CompliancePane.d.ts +39 -0
- package/dashboard/dist/components/CompliancePane.js +96 -0
- package/dashboard/dist/components/CompliancePane.test.d.ts +1 -0
- package/dashboard/dist/components/CompliancePane.test.js +183 -0
- package/dashboard/dist/components/SSOPane.d.ts +36 -0
- package/dashboard/dist/components/SSOPane.js +71 -0
- package/dashboard/dist/components/SSOPane.test.d.ts +1 -0
- package/dashboard/dist/components/SSOPane.test.js +155 -0
- package/dashboard/dist/components/UsagePane.d.ts +13 -0
- package/dashboard/dist/components/UsagePane.js +51 -0
- package/dashboard/dist/components/UsagePane.test.d.ts +1 -0
- package/dashboard/dist/components/UsagePane.test.js +142 -0
- package/dashboard/dist/components/WorkspaceDocsPane.d.ts +19 -0
- package/dashboard/dist/components/WorkspaceDocsPane.js +130 -0
- package/dashboard/dist/components/WorkspaceDocsPane.test.d.ts +1 -0
- package/dashboard/dist/components/WorkspaceDocsPane.test.js +242 -0
- package/dashboard/dist/components/WorkspacePane.d.ts +18 -0
- package/dashboard/dist/components/WorkspacePane.js +17 -0
- package/dashboard/dist/components/WorkspacePane.test.d.ts +1 -0
- package/dashboard/dist/components/WorkspacePane.test.js +84 -0
- package/dashboard/dist/components/ZeroRetentionPane.d.ts +44 -0
- package/dashboard/dist/components/ZeroRetentionPane.js +83 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.d.ts +1 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.js +160 -0
- package/package.json +1 -1
- package/server/lib/access-control-doc.js +541 -0
- package/server/lib/access-control-doc.test.js +672 -0
- package/server/lib/adr-generator.js +423 -0
- package/server/lib/adr-generator.test.js +586 -0
- package/server/lib/agent-progress-monitor.js +223 -0
- package/server/lib/agent-progress-monitor.test.js +202 -0
- package/server/lib/architecture-command.js +450 -0
- package/server/lib/architecture-command.test.js +754 -0
- package/server/lib/ast-analyzer.js +324 -0
- package/server/lib/ast-analyzer.test.js +437 -0
- package/server/lib/audit-attribution.js +191 -0
- package/server/lib/audit-attribution.test.js +359 -0
- package/server/lib/audit-classifier.js +202 -0
- package/server/lib/audit-classifier.test.js +209 -0
- package/server/lib/audit-command.js +275 -0
- package/server/lib/audit-command.test.js +325 -0
- package/server/lib/audit-exporter.js +380 -0
- package/server/lib/audit-exporter.test.js +464 -0
- package/server/lib/audit-logger.js +236 -0
- package/server/lib/audit-logger.test.js +364 -0
- package/server/lib/audit-query.js +257 -0
- package/server/lib/audit-query.test.js +352 -0
- package/server/lib/audit-storage.js +269 -0
- package/server/lib/audit-storage.test.js +272 -0
- package/server/lib/auth-system.test.js +4 -1
- package/server/lib/boundary-detector.js +427 -0
- package/server/lib/boundary-detector.test.js +320 -0
- package/server/lib/budget-alerts.js +138 -0
- package/server/lib/budget-alerts.test.js +235 -0
- package/server/lib/bulk-repo-init.js +342 -0
- package/server/lib/bulk-repo-init.test.js +388 -0
- package/server/lib/candidates-tracker.js +210 -0
- package/server/lib/candidates-tracker.test.js +300 -0
- package/server/lib/checkpoint-manager.js +251 -0
- package/server/lib/checkpoint-manager.test.js +474 -0
- package/server/lib/circular-detector.js +337 -0
- package/server/lib/circular-detector.test.js +353 -0
- package/server/lib/cohesion-analyzer.js +310 -0
- package/server/lib/cohesion-analyzer.test.js +447 -0
- package/server/lib/compliance-checklist.js +866 -0
- package/server/lib/compliance-checklist.test.js +476 -0
- package/server/lib/compliance-command.js +616 -0
- package/server/lib/compliance-command.test.js +551 -0
- package/server/lib/compliance-reporter.js +692 -0
- package/server/lib/compliance-reporter.test.js +707 -0
- package/server/lib/contract-testing.js +625 -0
- package/server/lib/contract-testing.test.js +342 -0
- package/server/lib/conversion-planner.js +469 -0
- package/server/lib/conversion-planner.test.js +361 -0
- package/server/lib/convert-command.js +351 -0
- package/server/lib/convert-command.test.js +608 -0
- package/server/lib/coupling-calculator.js +189 -0
- package/server/lib/coupling-calculator.test.js +509 -0
- package/server/lib/data-flow-doc.js +665 -0
- package/server/lib/data-flow-doc.test.js +659 -0
- package/server/lib/dependency-graph.js +367 -0
- package/server/lib/dependency-graph.test.js +516 -0
- package/server/lib/duplication-detector.js +349 -0
- package/server/lib/duplication-detector.test.js +401 -0
- package/server/lib/ephemeral-storage.js +249 -0
- package/server/lib/ephemeral-storage.test.js +254 -0
- package/server/lib/evidence-collector.js +627 -0
- package/server/lib/evidence-collector.test.js +901 -0
- package/server/lib/example-service.js +616 -0
- package/server/lib/example-service.test.js +397 -0
- package/server/lib/flow-diagram-generator.js +474 -0
- package/server/lib/flow-diagram-generator.test.js +446 -0
- package/server/lib/idp-manager.js +626 -0
- package/server/lib/idp-manager.test.js +587 -0
- package/server/lib/impact-scorer.js +184 -0
- package/server/lib/impact-scorer.test.js +211 -0
- package/server/lib/memory-exclusion.js +326 -0
- package/server/lib/memory-exclusion.test.js +241 -0
- package/server/lib/mermaid-generator.js +358 -0
- package/server/lib/mermaid-generator.test.js +301 -0
- package/server/lib/messaging-patterns.js +750 -0
- package/server/lib/messaging-patterns.test.js +213 -0
- package/server/lib/mfa-handler.js +452 -0
- package/server/lib/mfa-handler.test.js +490 -0
- package/server/lib/microservice-template.js +386 -0
- package/server/lib/microservice-template.test.js +325 -0
- package/server/lib/new-project-microservice.js +450 -0
- package/server/lib/new-project-microservice.test.js +600 -0
- package/server/lib/oauth-flow.js +375 -0
- package/server/lib/oauth-flow.test.js +487 -0
- package/server/lib/oauth-registry.js +190 -0
- package/server/lib/oauth-registry.test.js +306 -0
- package/server/lib/readme-generator.js +490 -0
- package/server/lib/readme-generator.test.js +493 -0
- package/server/lib/refactor-command.js +326 -0
- package/server/lib/refactor-command.test.js +528 -0
- package/server/lib/refactor-executor.js +254 -0
- package/server/lib/refactor-executor.test.js +305 -0
- package/server/lib/refactor-observer.js +292 -0
- package/server/lib/refactor-observer.test.js +422 -0
- package/server/lib/refactor-progress.js +193 -0
- package/server/lib/refactor-progress.test.js +251 -0
- package/server/lib/refactor-reporter.js +237 -0
- package/server/lib/refactor-reporter.test.js +247 -0
- package/server/lib/repo-dependency-tracker.js +261 -0
- package/server/lib/repo-dependency-tracker.test.js +350 -0
- package/server/lib/retention-policy.js +281 -0
- package/server/lib/retention-policy.test.js +486 -0
- package/server/lib/role-mapper.js +236 -0
- package/server/lib/role-mapper.test.js +395 -0
- package/server/lib/saml-provider.js +765 -0
- package/server/lib/saml-provider.test.js +643 -0
- package/server/lib/security-policy-generator.js +682 -0
- package/server/lib/security-policy-generator.test.js +544 -0
- package/server/lib/semantic-analyzer.js +198 -0
- package/server/lib/semantic-analyzer.test.js +474 -0
- package/server/lib/sensitive-detector.js +112 -0
- package/server/lib/sensitive-detector.test.js +209 -0
- package/server/lib/service-interaction-diagram.js +700 -0
- package/server/lib/service-interaction-diagram.test.js +638 -0
- package/server/lib/service-scaffold.js +486 -0
- package/server/lib/service-scaffold.test.js +373 -0
- package/server/lib/service-summary.js +553 -0
- package/server/lib/service-summary.test.js +619 -0
- package/server/lib/session-purge.js +460 -0
- package/server/lib/session-purge.test.js +312 -0
- package/server/lib/shared-kernel.js +578 -0
- package/server/lib/shared-kernel.test.js +255 -0
- package/server/lib/sso-command.js +544 -0
- package/server/lib/sso-command.test.js +552 -0
- package/server/lib/sso-session.js +492 -0
- package/server/lib/sso-session.test.js +670 -0
- package/server/lib/traefik-config.js +282 -0
- package/server/lib/traefik-config.test.js +312 -0
- package/server/lib/usage-command.js +218 -0
- package/server/lib/usage-command.test.js +391 -0
- package/server/lib/usage-formatter.js +192 -0
- package/server/lib/usage-formatter.test.js +267 -0
- package/server/lib/usage-history.js +122 -0
- package/server/lib/usage-history.test.js +206 -0
- package/server/lib/workspace-command.js +249 -0
- package/server/lib/workspace-command.test.js +264 -0
- package/server/lib/workspace-config.js +270 -0
- package/server/lib/workspace-config.test.js +312 -0
- package/server/lib/workspace-docs-command.js +547 -0
- package/server/lib/workspace-docs-command.test.js +692 -0
- package/server/lib/workspace-memory.js +451 -0
- package/server/lib/workspace-memory.test.js +403 -0
- package/server/lib/workspace-scanner.js +452 -0
- package/server/lib/workspace-scanner.test.js +677 -0
- package/server/lib/workspace-test-runner.js +315 -0
- package/server/lib/workspace-test-runner.test.js +294 -0
- package/server/lib/zero-retention-command.js +439 -0
- package/server/lib/zero-retention-command.test.js +448 -0
- package/server/lib/zero-retention.js +322 -0
- package/server/lib/zero-retention.test.js +258 -0
- package/server/package-lock.json +14 -0
- package/server/package.json +1 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refactor Reporter Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
|
|
7
|
+
describe('RefactorReporter', () => {
|
|
8
|
+
describe('plain English summaries', () => {
|
|
9
|
+
it('generates plain English: Extracted X from Y', async () => {
|
|
10
|
+
const { RefactorReporter } = await import('./refactor-reporter.js');
|
|
11
|
+
const reporter = new RefactorReporter();
|
|
12
|
+
|
|
13
|
+
const summary = reporter.describeChange({
|
|
14
|
+
type: 'extract',
|
|
15
|
+
name: 'validateEmail',
|
|
16
|
+
source: 'createUser',
|
|
17
|
+
lines: 15,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
expect(summary).toContain('Extracted');
|
|
21
|
+
expect(summary).toContain('validateEmail');
|
|
22
|
+
expect(summary).toContain('createUser');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('generates summary for rename', async () => {
|
|
26
|
+
const { RefactorReporter } = await import('./refactor-reporter.js');
|
|
27
|
+
const reporter = new RefactorReporter();
|
|
28
|
+
|
|
29
|
+
const summary = reporter.describeChange({
|
|
30
|
+
type: 'rename',
|
|
31
|
+
oldName: 'x',
|
|
32
|
+
newName: 'userCount',
|
|
33
|
+
filesAffected: 3,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
expect(summary).toContain('Renamed');
|
|
37
|
+
expect(summary).toContain('x');
|
|
38
|
+
expect(summary).toContain('userCount');
|
|
39
|
+
expect(summary).toContain('3');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('generates summary for split', async () => {
|
|
43
|
+
const { RefactorReporter } = await import('./refactor-reporter.js');
|
|
44
|
+
const reporter = new RefactorReporter();
|
|
45
|
+
|
|
46
|
+
const summary = reporter.describeChange({
|
|
47
|
+
type: 'split',
|
|
48
|
+
source: 'handlers.js',
|
|
49
|
+
targets: ['userHandlers.js', 'authHandlers.js'],
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
expect(summary).toContain('Split');
|
|
53
|
+
expect(summary).toContain('handlers.js');
|
|
54
|
+
expect(summary).toContain('2');
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('diff generation', () => {
|
|
59
|
+
it('generates unified diff format', async () => {
|
|
60
|
+
const { RefactorReporter } = await import('./refactor-reporter.js');
|
|
61
|
+
const reporter = new RefactorReporter();
|
|
62
|
+
|
|
63
|
+
const diff = reporter.generateDiff({
|
|
64
|
+
file: 'test.js',
|
|
65
|
+
before: 'const x = 1;',
|
|
66
|
+
after: 'const count = 1;',
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
expect(diff).toContain('--- a/test.js');
|
|
70
|
+
expect(diff).toContain('+++ b/test.js');
|
|
71
|
+
expect(diff).toContain('-const x = 1;');
|
|
72
|
+
expect(diff).toContain('+const count = 1;');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('handles empty before/after', async () => {
|
|
76
|
+
const { RefactorReporter } = await import('./refactor-reporter.js');
|
|
77
|
+
const reporter = new RefactorReporter();
|
|
78
|
+
|
|
79
|
+
const diff = reporter.generateDiff({
|
|
80
|
+
file: 'test.js',
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
expect(diff).toBe('');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('Mermaid diagrams', () => {
|
|
88
|
+
it('generates Mermaid diagram for function relationships', async () => {
|
|
89
|
+
const { RefactorReporter } = await import('./refactor-reporter.js');
|
|
90
|
+
const reporter = new RefactorReporter();
|
|
91
|
+
|
|
92
|
+
const diagram = reporter.generateMermaidDiagram([
|
|
93
|
+
{ type: 'extract', source: 'main', name: 'helper' },
|
|
94
|
+
{ type: 'extract', source: 'main', name: 'utils' },
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
expect(diagram).toContain('graph TD');
|
|
98
|
+
expect(diagram).toContain('main --> helper');
|
|
99
|
+
expect(diagram).toContain('main --> utils');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('handles split operations', async () => {
|
|
103
|
+
const { RefactorReporter } = await import('./refactor-reporter.js');
|
|
104
|
+
const reporter = new RefactorReporter();
|
|
105
|
+
|
|
106
|
+
const diagram = reporter.generateMermaidDiagram([
|
|
107
|
+
{ type: 'split', source: 'handlers', targets: [{ name: 'userHandlers' }, { name: 'authHandlers' }] },
|
|
108
|
+
]);
|
|
109
|
+
|
|
110
|
+
expect(diagram).toContain('handlers --> userHandlers');
|
|
111
|
+
expect(diagram).toContain('handlers --> authHandlers');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('returns empty for non-structural changes', async () => {
|
|
115
|
+
const { RefactorReporter } = await import('./refactor-reporter.js');
|
|
116
|
+
const reporter = new RefactorReporter();
|
|
117
|
+
|
|
118
|
+
const diagram = reporter.generateMermaidDiagram([
|
|
119
|
+
{ type: 'rename', oldName: 'x', newName: 'y' },
|
|
120
|
+
]);
|
|
121
|
+
|
|
122
|
+
expect(diagram).toBe('');
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('Markdown output', () => {
|
|
127
|
+
it('outputs valid Markdown', async () => {
|
|
128
|
+
const { RefactorReporter } = await import('./refactor-reporter.js');
|
|
129
|
+
const reporter = new RefactorReporter();
|
|
130
|
+
|
|
131
|
+
const md = reporter.toMarkdown([
|
|
132
|
+
{ type: 'extract', source: 'main.js', name: 'helper' },
|
|
133
|
+
]);
|
|
134
|
+
|
|
135
|
+
expect(md).toContain('# Refactoring Report');
|
|
136
|
+
expect(md).toContain('## Summary');
|
|
137
|
+
expect(md).toContain('## Changes');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('includes collapsible diff sections', async () => {
|
|
141
|
+
const { RefactorReporter } = await import('./refactor-reporter.js');
|
|
142
|
+
const reporter = new RefactorReporter();
|
|
143
|
+
|
|
144
|
+
const md = reporter.toMarkdown([
|
|
145
|
+
{ type: 'rename', oldName: 'x', newName: 'y', file: 'test.js', before: 'x', after: 'y' },
|
|
146
|
+
]);
|
|
147
|
+
|
|
148
|
+
expect(md).toContain('<details>');
|
|
149
|
+
expect(md).toContain('View Diff');
|
|
150
|
+
expect(md).toContain('</details>');
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('JSON output', () => {
|
|
155
|
+
it('outputs valid JSON', async () => {
|
|
156
|
+
const { RefactorReporter } = await import('./refactor-reporter.js');
|
|
157
|
+
const reporter = new RefactorReporter();
|
|
158
|
+
|
|
159
|
+
const json = reporter.toJson([
|
|
160
|
+
{ type: 'extract', source: 'main.js', name: 'helper' },
|
|
161
|
+
]);
|
|
162
|
+
|
|
163
|
+
const parsed = JSON.parse(json);
|
|
164
|
+
expect(parsed.summary).toBeDefined();
|
|
165
|
+
expect(parsed.changes).toBeDefined();
|
|
166
|
+
expect(parsed.generatedAt).toBeDefined();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('includes descriptions in JSON', async () => {
|
|
170
|
+
const { RefactorReporter } = await import('./refactor-reporter.js');
|
|
171
|
+
const reporter = new RefactorReporter();
|
|
172
|
+
|
|
173
|
+
const json = reporter.toJson([
|
|
174
|
+
{ type: 'rename', oldName: 'x', newName: 'count' },
|
|
175
|
+
]);
|
|
176
|
+
|
|
177
|
+
const parsed = JSON.parse(json);
|
|
178
|
+
expect(parsed.changes[0].description).toContain('Renamed');
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe('HTML output', () => {
|
|
183
|
+
it('outputs valid HTML', async () => {
|
|
184
|
+
const { RefactorReporter } = await import('./refactor-reporter.js');
|
|
185
|
+
const reporter = new RefactorReporter();
|
|
186
|
+
|
|
187
|
+
const html = reporter.toHtml([
|
|
188
|
+
{ type: 'extract', source: 'main.js', name: 'helper' },
|
|
189
|
+
]);
|
|
190
|
+
|
|
191
|
+
expect(html).toContain('<!DOCTYPE html>');
|
|
192
|
+
expect(html).toContain('<html>');
|
|
193
|
+
expect(html).toContain('</html>');
|
|
194
|
+
expect(html).toContain('<title>Refactoring Report</title>');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('escapes HTML entities', async () => {
|
|
198
|
+
const { RefactorReporter } = await import('./refactor-reporter.js');
|
|
199
|
+
const reporter = new RefactorReporter();
|
|
200
|
+
|
|
201
|
+
const escaped = reporter.escapeHtml('<script>alert("xss")</script>');
|
|
202
|
+
|
|
203
|
+
expect(escaped).not.toContain('<script>');
|
|
204
|
+
expect(escaped).toContain('<script>');
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe('before/after comparisons', () => {
|
|
209
|
+
it('includes before/after code blocks', async () => {
|
|
210
|
+
const { RefactorReporter } = await import('./refactor-reporter.js');
|
|
211
|
+
const reporter = new RefactorReporter();
|
|
212
|
+
|
|
213
|
+
const comparison = reporter.generateComparison({
|
|
214
|
+
file: 'test.js',
|
|
215
|
+
type: 'rename',
|
|
216
|
+
before: 'const x = 1;',
|
|
217
|
+
after: 'const count = 1;',
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
expect(comparison.before).toBe('const x = 1;');
|
|
221
|
+
expect(comparison.after).toBe('const count = 1;');
|
|
222
|
+
expect(comparison.file).toBe('test.js');
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe('generate method', () => {
|
|
227
|
+
it('defaults to markdown format', async () => {
|
|
228
|
+
const { RefactorReporter } = await import('./refactor-reporter.js');
|
|
229
|
+
const reporter = new RefactorReporter();
|
|
230
|
+
|
|
231
|
+
const output = reporter.generate([{ type: 'extract', name: 'test', source: 'main' }]);
|
|
232
|
+
|
|
233
|
+
expect(output).toContain('# Refactoring Report');
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('respects format parameter', async () => {
|
|
237
|
+
const { RefactorReporter } = await import('./refactor-reporter.js');
|
|
238
|
+
const reporter = new RefactorReporter();
|
|
239
|
+
|
|
240
|
+
const json = reporter.generate([{ type: 'extract', name: 'test', source: 'main' }], 'json');
|
|
241
|
+
expect(() => JSON.parse(json)).not.toThrow();
|
|
242
|
+
|
|
243
|
+
const html = reporter.generate([{ type: 'extract', name: 'test', source: 'main' }], 'html');
|
|
244
|
+
expect(html).toContain('<!DOCTYPE html>');
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
});
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repo Dependency Tracker - Track dependencies between repos in a workspace
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
class RepoDependencyTracker {
|
|
9
|
+
constructor(workspaceRoot, repos) {
|
|
10
|
+
this.workspaceRoot = workspaceRoot;
|
|
11
|
+
this.repos = repos;
|
|
12
|
+
this.repoPackages = new Map(); // repo -> package.json data
|
|
13
|
+
this.repoNames = new Map(); // package name -> repo directory
|
|
14
|
+
this.dependencyGraph = {}; // repo -> [dependencies]
|
|
15
|
+
|
|
16
|
+
this.loadRepoData();
|
|
17
|
+
this.buildDependencyGraph();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Load package.json for all repos
|
|
22
|
+
*/
|
|
23
|
+
loadRepoData() {
|
|
24
|
+
for (const repo of this.repos) {
|
|
25
|
+
const pkgPath = path.join(this.workspaceRoot, repo, 'package.json');
|
|
26
|
+
|
|
27
|
+
if (fs.existsSync(pkgPath)) {
|
|
28
|
+
try {
|
|
29
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
30
|
+
this.repoPackages.set(repo, pkg);
|
|
31
|
+
if (pkg.name) {
|
|
32
|
+
this.repoNames.set(pkg.name, repo);
|
|
33
|
+
}
|
|
34
|
+
} catch (err) {
|
|
35
|
+
// Invalid JSON, skip
|
|
36
|
+
this.repoPackages.set(repo, {});
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
this.repoPackages.set(repo, {});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Build dependency graph from repo data
|
|
46
|
+
*/
|
|
47
|
+
buildDependencyGraph() {
|
|
48
|
+
for (const repo of this.repos) {
|
|
49
|
+
this.dependencyGraph[repo] = [];
|
|
50
|
+
const pkg = this.repoPackages.get(repo) || {};
|
|
51
|
+
|
|
52
|
+
const allDeps = {
|
|
53
|
+
...pkg.dependencies,
|
|
54
|
+
...pkg.devDependencies,
|
|
55
|
+
...pkg.peerDependencies,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
for (const [depName, depVersion] of Object.entries(allDeps || {})) {
|
|
59
|
+
// Check for workspace: protocol
|
|
60
|
+
if (typeof depVersion === 'string' && depVersion.startsWith('workspace:')) {
|
|
61
|
+
const depRepo = this.repoNames.get(depName);
|
|
62
|
+
if (depRepo && depRepo !== repo) {
|
|
63
|
+
this.dependencyGraph[repo].push(depRepo);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Check for file: protocol pointing to another repo
|
|
68
|
+
if (typeof depVersion === 'string' && depVersion.startsWith('file:')) {
|
|
69
|
+
const filePath = depVersion.slice(5); // Remove 'file:'
|
|
70
|
+
const resolvedPath = path.resolve(path.join(this.workspaceRoot, repo), filePath);
|
|
71
|
+
const relativePath = path.relative(this.workspaceRoot, resolvedPath);
|
|
72
|
+
|
|
73
|
+
// Check if it points to one of our repos
|
|
74
|
+
for (const otherRepo of this.repos) {
|
|
75
|
+
if (relativePath === otherRepo || relativePath.startsWith(otherRepo + path.sep)) {
|
|
76
|
+
if (otherRepo !== repo) {
|
|
77
|
+
this.dependencyGraph[repo].push(otherRepo);
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get dependencies of a repo
|
|
89
|
+
* @param {string} repo - Repo directory name
|
|
90
|
+
* @returns {string[]} Array of repo names this repo depends on
|
|
91
|
+
*/
|
|
92
|
+
getDependencies(repo) {
|
|
93
|
+
return this.dependencyGraph[repo] || [];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Check if repoA depends on repoB
|
|
98
|
+
* @param {string} repoA
|
|
99
|
+
* @param {string} repoB
|
|
100
|
+
* @returns {boolean}
|
|
101
|
+
*/
|
|
102
|
+
dependsOn(repoA, repoB) {
|
|
103
|
+
const deps = this.getDependencies(repoA);
|
|
104
|
+
return deps.includes(repoB);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get repos that depend on the given repo
|
|
109
|
+
* @param {string} repo
|
|
110
|
+
* @returns {string[]}
|
|
111
|
+
*/
|
|
112
|
+
getDependents(repo) {
|
|
113
|
+
const dependents = [];
|
|
114
|
+
|
|
115
|
+
for (const [r, deps] of Object.entries(this.dependencyGraph)) {
|
|
116
|
+
if (deps.includes(repo)) {
|
|
117
|
+
dependents.push(r);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return dependents;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get all repos affected when the given repo changes (transitive dependents)
|
|
126
|
+
* @param {string} repo
|
|
127
|
+
* @returns {string[]}
|
|
128
|
+
*/
|
|
129
|
+
getAffectedRepos(repo) {
|
|
130
|
+
const affected = new Set();
|
|
131
|
+
const queue = [repo];
|
|
132
|
+
const visited = new Set([repo]);
|
|
133
|
+
|
|
134
|
+
while (queue.length > 0) {
|
|
135
|
+
const current = queue.shift();
|
|
136
|
+
const dependents = this.getDependents(current);
|
|
137
|
+
|
|
138
|
+
for (const dep of dependents) {
|
|
139
|
+
if (!visited.has(dep)) {
|
|
140
|
+
visited.add(dep);
|
|
141
|
+
affected.add(dep);
|
|
142
|
+
queue.push(dep);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return Array.from(affected);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get topological order for build/test runs
|
|
152
|
+
* @returns {string[]}
|
|
153
|
+
*/
|
|
154
|
+
getTopologicalOrder() {
|
|
155
|
+
const visited = new Set();
|
|
156
|
+
const result = [];
|
|
157
|
+
|
|
158
|
+
const visit = (repo) => {
|
|
159
|
+
if (visited.has(repo)) return;
|
|
160
|
+
visited.add(repo);
|
|
161
|
+
|
|
162
|
+
const deps = this.getDependencies(repo);
|
|
163
|
+
for (const dep of deps) {
|
|
164
|
+
visit(dep);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
result.push(repo);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
for (const repo of this.repos) {
|
|
171
|
+
visit(repo);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Detect circular dependencies
|
|
179
|
+
* @returns {string[][]} Array of cycles found
|
|
180
|
+
*/
|
|
181
|
+
detectCircularDependencies() {
|
|
182
|
+
const cycles = [];
|
|
183
|
+
const visited = new Set();
|
|
184
|
+
const recursionStack = new Set();
|
|
185
|
+
const path = [];
|
|
186
|
+
|
|
187
|
+
const dfs = (repo) => {
|
|
188
|
+
visited.add(repo);
|
|
189
|
+
recursionStack.add(repo);
|
|
190
|
+
path.push(repo);
|
|
191
|
+
|
|
192
|
+
const deps = this.getDependencies(repo);
|
|
193
|
+
for (const dep of deps) {
|
|
194
|
+
if (!visited.has(dep)) {
|
|
195
|
+
const cycle = dfs(dep);
|
|
196
|
+
if (cycle) return cycle;
|
|
197
|
+
} else if (recursionStack.has(dep)) {
|
|
198
|
+
// Found cycle
|
|
199
|
+
const cycleStart = path.indexOf(dep);
|
|
200
|
+
const cycle = path.slice(cycleStart);
|
|
201
|
+
cycle.push(dep); // Complete the cycle
|
|
202
|
+
cycles.push(cycle);
|
|
203
|
+
return cycle;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
path.pop();
|
|
208
|
+
recursionStack.delete(repo);
|
|
209
|
+
return null;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
for (const repo of this.repos) {
|
|
213
|
+
if (!visited.has(repo)) {
|
|
214
|
+
dfs(repo);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return cycles;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Generate Mermaid diagram of dependencies
|
|
223
|
+
* @returns {string}
|
|
224
|
+
*/
|
|
225
|
+
generateMermaidDiagram() {
|
|
226
|
+
const lines = ['graph TD'];
|
|
227
|
+
|
|
228
|
+
// Add all nodes
|
|
229
|
+
for (const repo of this.repos) {
|
|
230
|
+
lines.push(` ${this.sanitizeId(repo)}[${repo}]`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Add edges
|
|
234
|
+
for (const [repo, deps] of Object.entries(this.dependencyGraph)) {
|
|
235
|
+
for (const dep of deps) {
|
|
236
|
+
lines.push(` ${this.sanitizeId(repo)} --> ${this.sanitizeId(dep)}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return lines.join('\n');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Sanitize repo name for Mermaid ID
|
|
245
|
+
*/
|
|
246
|
+
sanitizeId(name) {
|
|
247
|
+
return name.replace(/[^a-zA-Z0-9]/g, '_');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Get the full dependency graph
|
|
252
|
+
* @returns {Object}
|
|
253
|
+
*/
|
|
254
|
+
getDependencyGraph() {
|
|
255
|
+
return { ...this.dependencyGraph };
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
module.exports = {
|
|
260
|
+
RepoDependencyTracker,
|
|
261
|
+
};
|