@ts-stack/cycle-detector 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/dist/index.js CHANGED
@@ -2,13 +2,13 @@
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import ts from 'typescript';
5
+ // Global shared structures for maximum performance
5
6
  const graph = new Map();
6
- const visited = new Map();
7
- const currentStack = [];
8
- const detectedCycles = [];
9
- /**
10
- * Parses command line arguments to separate options from entry point patterns.
11
- */
7
+ const allUniqueCycles = [];
8
+ const globalDetectedCycles = new Set();
9
+ const packageMetaCache = new Map();
10
+ const compilerOptionsCache = new Map();
11
+ let globalProjectPath;
12
12
  function parseArgs() {
13
13
  const args = [...process.argv.slice(2)];
14
14
  let projectPath;
@@ -24,10 +24,6 @@ function parseArgs() {
24
24
  }
25
25
  return { entryPatterns, projectPath };
26
26
  }
27
- /**
28
- * Checks if an import/export declaration is type-only.
29
- * Type-only imports are stripped during compilation and don't cause runtime cycles.
30
- */
31
27
  function isRuntimeImport(node) {
32
28
  if (ts.isExportDeclaration(node)) {
33
29
  if (!node.moduleSpecifier)
@@ -36,10 +32,9 @@ function isRuntimeImport(node) {
36
32
  }
37
33
  if (ts.isImportDeclaration(node)) {
38
34
  if (!node.importClause)
39
- return true; // Side-effect import: import './foo'
40
- if (node.importClause.isTypeOnly)
41
- return false; // import type { X } from './foo'
42
- // Check individual specifiers: import { type A, B } from './foo'
35
+ return true;
36
+ if (node.importClause.phaseModifier)
37
+ return false;
43
38
  const namedBindings = node.importClause.namedBindings;
44
39
  if (namedBindings && ts.isNamedImports(namedBindings)) {
45
40
  return !namedBindings.elements.every((el) => el.isTypeOnly);
@@ -48,92 +43,199 @@ function isRuntimeImport(node) {
48
43
  }
49
44
  return false;
50
45
  }
51
- /**
52
- * Resolves module paths using the TypeScript Compiler API.
53
- * Respects tsconfig paths/aliases and ESM extensions.
54
- */
55
- function resolveModule(moduleName, containingFile, options) {
56
- const result = ts.resolveModuleName(moduleName, containingFile, options, ts.sys);
57
- if (result.resolvedModule && !result.resolvedModule.isExternalLibraryImport) {
58
- return result.resolvedModule.resolvedFileName;
46
+ function getCompilerOptionsForFile(filePath) {
47
+ const currentDir = path.dirname(filePath);
48
+ const cachedOptions = compilerOptionsCache.get(currentDir);
49
+ if (cachedOptions !== undefined) {
50
+ return cachedOptions;
59
51
  }
60
- return null;
52
+ const configPath = ts.findConfigFile(currentDir, ts.sys.fileExists, 'tsconfig.json') || globalProjectPath;
53
+ if (configPath) {
54
+ const resolvedConfigPath = path.resolve(configPath);
55
+ const configDir = path.dirname(resolvedConfigPath);
56
+ const cachedConfig = compilerOptionsCache.get(resolvedConfigPath);
57
+ if (cachedConfig !== undefined) {
58
+ compilerOptionsCache.set(currentDir, cachedConfig);
59
+ return cachedConfig;
60
+ }
61
+ try {
62
+ const configFile = ts.readConfigFile(resolvedConfigPath, ts.sys.readFile);
63
+ const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, configDir);
64
+ const options = parsedConfig.options || {};
65
+ compilerOptionsCache.set(resolvedConfigPath, options);
66
+ compilerOptionsCache.set(currentDir, options);
67
+ return options;
68
+ }
69
+ catch {
70
+ // Fallback
71
+ }
72
+ }
73
+ return {};
61
74
  }
62
- /**
63
- * Analyzes a single entry point, builds its dependency graph, and detects cycles.
64
- */
65
- function analyzeEntryPoint(entryPoint, projectPath) {
66
- graph.clear();
67
- visited.clear();
68
- currentStack.length = 0;
69
- detectedCycles.length = 0;
70
- // Locate tsconfig.json automatically if not explicitly provided
71
- const configPath = projectPath
72
- ? path.resolve(projectPath)
73
- : ts.findConfigFile(path.dirname(entryPoint), ts.sys.fileExists, 'tsconfig.json');
74
- let compilerOptions = {};
75
- if (configPath && fs.existsSync(configPath)) {
76
- const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
77
- const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, path.dirname(configPath));
78
- compilerOptions = parsedConfig.options;
79
- }
80
- // Recursive AST parsing to build the graph
81
- function parseFile(filePath) {
82
- if (graph.has(filePath) || !fs.existsSync(filePath))
83
- return;
84
- graph.set(filePath, []);
85
- const content = fs.readFileSync(filePath, 'utf8');
86
- const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
87
- const imports = [];
88
- function walk(node) {
89
- if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
90
- if (isRuntimeImport(node)) {
91
- const specifier = node.moduleSpecifier;
92
- if (specifier && ts.isStringLiteral(specifier)) {
93
- const resolved = resolveModule(specifier.text, filePath, compilerOptions);
94
- if (resolved && !imports.includes(resolved))
95
- imports.push(resolved);
75
+ function getPackageMeta(filePath) {
76
+ let currentDir = path.dirname(filePath);
77
+ const visitedDirs = [];
78
+ while (currentDir && currentDir !== path.parse(currentDir).root) {
79
+ const cached = packageMetaCache.get(currentDir);
80
+ if (cached !== undefined) {
81
+ for (const d of visitedDirs)
82
+ packageMetaCache.set(d, cached);
83
+ return cached;
84
+ }
85
+ const pkgJsonPath = path.join(currentDir, 'package.json');
86
+ if (fs.existsSync(pkgJsonPath)) {
87
+ try {
88
+ const content = fs.readFileSync(pkgJsonPath, 'utf8');
89
+ const pkg = JSON.parse(content);
90
+ let outDirName = 'dist';
91
+ const mainField = pkg.main || pkg.types || pkg.typings || '';
92
+ if (mainField) {
93
+ const parts = path.normalize(mainField).split(path.sep);
94
+ if (parts.length > 1 && parts[0] !== '.' && parts[0] !== '..') {
95
+ outDirName = parts[0];
96
+ }
97
+ else if (parts.length > 2 && (parts[0] === '.' || parts[0] === '..')) {
98
+ outDirName = parts[1];
96
99
  }
97
100
  }
101
+ let srcDirName = 'src';
102
+ if (fs.existsSync(path.join(currentDir, 'source')))
103
+ srcDirName = 'source';
104
+ else if (fs.existsSync(path.join(currentDir, 'lib')))
105
+ srcDirName = 'lib';
106
+ const meta = { pkgDir: currentDir, srcDirName, outDirName };
107
+ packageMetaCache.set(currentDir, meta);
108
+ for (const d of visitedDirs)
109
+ packageMetaCache.set(d, meta);
110
+ return meta;
111
+ }
112
+ catch {
113
+ packageMetaCache.set(currentDir, null);
114
+ for (const d of visitedDirs)
115
+ packageMetaCache.set(d, null);
116
+ return null;
98
117
  }
99
- ts.forEachChild(node, walk);
100
118
  }
101
- walk(sourceFile);
102
- graph.set(filePath, imports);
103
- for (const dep of imports)
104
- parseFile(dep);
119
+ visitedDirs.push(currentDir);
120
+ currentDir = path.dirname(currentDir);
105
121
  }
106
- // Graph coloring DFS algorithm for cycle detection
107
- function findCycles(node) {
108
- visited.set(node, 'VISITING');
109
- currentStack.push(node);
110
- for (const neighbor of graph.get(node) || []) {
111
- const state = visited.get(neighbor);
112
- if (state === 'VISITING') {
113
- const startIdx = currentStack.indexOf(neighbor);
114
- const cycle = currentStack.slice(startIdx);
115
- cycle.push(neighbor);
116
- detectedCycles.push(cycle);
122
+ return null;
123
+ }
124
+ function resolveModule(moduleName, containingFile, options) {
125
+ const result = ts.resolveModuleName(moduleName, containingFile, options, ts.sys);
126
+ if (!result.resolvedModule)
127
+ return null;
128
+ const resolvedFileName = path.resolve(result.resolvedModule.resolvedFileName);
129
+ if (resolvedFileName.includes(`${path.sep}node_modules${path.sep}`)) {
130
+ return null;
131
+ }
132
+ const meta = getPackageMeta(resolvedFileName);
133
+ if (meta) {
134
+ const { pkgDir, srcDirName, outDirName } = meta;
135
+ const srcDirPath = path.join(pkgDir, srcDirName);
136
+ if (resolvedFileName.startsWith(srcDirPath + path.sep)) {
137
+ return resolvedFileName;
138
+ }
139
+ const outDirPath = path.join(pkgDir, outDirName);
140
+ if (resolvedFileName.startsWith(outDirPath + path.sep) || resolvedFileName === outDirPath) {
141
+ const relativeToOut = path.relative(outDirPath, resolvedFileName);
142
+ let baseName = relativeToOut;
143
+ if (baseName.endsWith('.d.ts'))
144
+ baseName = baseName.slice(0, -5);
145
+ else if (baseName.endsWith('.d.mts'))
146
+ baseName = baseName.slice(0, -6);
147
+ else if (baseName.endsWith('.d.cts'))
148
+ baseName = baseName.slice(0, -6);
149
+ else if (baseName.endsWith('.js'))
150
+ baseName = baseName.slice(0, -3);
151
+ else if (baseName.endsWith('.mjs'))
152
+ baseName = baseName.slice(0, -4);
153
+ else if (baseName.endsWith('.cjs'))
154
+ baseName = baseName.slice(0, -4);
155
+ else if (baseName.endsWith('.jsx'))
156
+ baseName = baseName.slice(0, -4);
157
+ const extensions = ['.ts', '.tsx', '.mts', '.cts'];
158
+ for (const ext of extensions) {
159
+ const targetSrcFile = path.join(srcDirPath, baseName + ext);
160
+ if (fs.existsSync(targetSrcFile)) {
161
+ return targetSrcFile;
162
+ }
117
163
  }
118
- else if (!state) {
119
- findCycles(neighbor);
164
+ }
165
+ }
166
+ if (result.resolvedModule.isExternalLibraryImport) {
167
+ return null;
168
+ }
169
+ return resolvedFileName;
170
+ }
171
+ function getCanonicalCycleKey(cycle) {
172
+ const nodes = cycle.slice(0, -1);
173
+ if (nodes.length === 0)
174
+ return '';
175
+ let minIdx = 0;
176
+ for (let i = 1; i < nodes.length; i++) {
177
+ if (nodes[i] < nodes[minIdx]) {
178
+ minIdx = i;
179
+ }
180
+ }
181
+ const rotated = [...nodes.slice(minIdx), ...nodes.slice(0, minIdx)];
182
+ rotated.push(rotated[0]);
183
+ return rotated.join('|');
184
+ }
185
+ function parseFile(filePath) {
186
+ if (graph.has(filePath) || !fs.existsSync(filePath))
187
+ return;
188
+ graph.set(filePath, []);
189
+ const options = getCompilerOptionsForFile(filePath);
190
+ const content = fs.readFileSync(filePath, 'utf8');
191
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
192
+ const imports = [];
193
+ function walk(node) {
194
+ if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
195
+ if (isRuntimeImport(node)) {
196
+ const specifier = node.moduleSpecifier;
197
+ if (specifier && ts.isStringLiteral(specifier)) {
198
+ const resolved = resolveModule(specifier.text, filePath, options);
199
+ if (resolved && !imports.includes(resolved))
200
+ imports.push(resolved);
201
+ }
120
202
  }
121
203
  }
122
- currentStack.pop();
123
- visited.set(node, 'VISITED');
204
+ ts.forEachChild(node, walk);
124
205
  }
125
- parseFile(entryPoint);
126
- findCycles(entryPoint);
127
- return detectedCycles;
206
+ walk(sourceFile);
207
+ graph.set(filePath, imports);
208
+ for (const dep of imports)
209
+ parseFile(dep);
210
+ }
211
+ /**
212
+ * Checks if a specific target file is reachable from a starting entry point in the graph.
213
+ */
214
+ function canReach(start, target) {
215
+ const seen = new Set();
216
+ const stack = [start];
217
+ while (stack.length > 0) {
218
+ const current = stack.pop();
219
+ if (current === target)
220
+ return true;
221
+ if (seen.has(current))
222
+ continue;
223
+ seen.add(current);
224
+ const deps = graph.get(current) || [];
225
+ for (const dep of deps) {
226
+ stack.push(dep);
227
+ }
228
+ }
229
+ return false;
128
230
  }
129
231
  function main() {
130
232
  const { entryPatterns, projectPath } = parseArgs();
131
- if (entryPatterns.length == 0) {
132
- console.error('❌ Error: Please specify at least one entry point or glob pattern (e.g., packages/*/src/index.ts)');
233
+ globalProjectPath = projectPath;
234
+ if (entryPatterns.length === 0) {
235
+ console.error('❌ Error: Please specify at least one entry point or glob pattern.');
133
236
  process.exit(1);
134
237
  }
135
238
  const entryPoints = [];
136
- // Expand glob patterns or fall back to direct paths
137
239
  for (const pattern of entryPatterns) {
138
240
  const matches = fs.globSync ? fs.globSync(pattern) : [pattern];
139
241
  for (const match of matches) {
@@ -152,25 +254,86 @@ function main() {
152
254
  console.error('❌ Error: No entry files found matching the provided paths or patterns.');
153
255
  process.exit(1);
154
256
  }
155
- console.log(`🔍 Found ${entryPoints.length} entry point(s) for analysis...\n`);
257
+ console.log(`🔍 Found ${entryPoints.length} entry point(s) for analysis. Building graph...\n`);
258
+ // Phase 1: Deep parse all files globally across all entry points
259
+ for (const entryPoint of entryPoints) {
260
+ parseFile(entryPoint);
261
+ }
262
+ // Phase 2: Traverse the global graph to discover all unique cycles
263
+ const visited = new Map();
264
+ const currentStack = [];
265
+ function findCycles(node) {
266
+ visited.set(node, 'VISITING');
267
+ currentStack.push(node);
268
+ for (const neighbor of graph.get(node) || []) {
269
+ const state = visited.get(neighbor);
270
+ if (state === 'VISITING') {
271
+ const startIdx = currentStack.indexOf(neighbor);
272
+ const cycle = currentStack.slice(startIdx);
273
+ cycle.push(neighbor);
274
+ const key = getCanonicalCycleKey(cycle);
275
+ if (!globalDetectedCycles.has(key)) {
276
+ globalDetectedCycles.add(key);
277
+ allUniqueCycles.push(cycle);
278
+ }
279
+ }
280
+ else if (!state) {
281
+ findCycles(neighbor);
282
+ }
283
+ }
284
+ currentStack.pop();
285
+ visited.set(node, 'VISITED');
286
+ }
287
+ for (const entryPoint of entryPoints) {
288
+ if (!visited.has(entryPoint)) {
289
+ findCycles(entryPoint);
290
+ }
291
+ }
292
+ // Phase 3: Intelligently distribute and attribute cycles to their native entry points
293
+ const entryPointCycles = new Map();
294
+ for (const ep of entryPoints) {
295
+ entryPointCycles.set(ep, []);
296
+ }
297
+ for (const cycle of allUniqueCycles) {
298
+ const firstFile = cycle[0];
299
+ // Find the entrypoint whose source folder directly hosts this file
300
+ const matchedEp = entryPoints.find((ep) => {
301
+ const epDir = path.dirname(ep);
302
+ return firstFile.startsWith(epDir + path.sep) || firstFile === ep;
303
+ });
304
+ if (matchedEp) {
305
+ entryPointCycles.get(matchedEp).push(cycle);
306
+ }
307
+ else {
308
+ // Cross-package cycle or edge case: assign to the first entry point that can reach it
309
+ const reachingEp = entryPoints.find((ep) => canReach(ep, firstFile));
310
+ if (reachingEp) {
311
+ entryPointCycles.get(reachingEp).push(cycle);
312
+ }
313
+ else {
314
+ entryPointCycles.get(entryPoints[0]).push(cycle);
315
+ }
316
+ }
317
+ }
318
+ // Phase 4: Output the clean, perfectly targeted report
156
319
  let globalHasCycles = false;
157
320
  for (const entryPoint of entryPoints) {
158
- const relativeEntry = path.relative(process.cwd(), entryPoint);
159
- const cycles = analyzeEntryPoint(entryPoint, projectPath);
321
+ const absoluteEntry = path.resolve(entryPoint);
322
+ const cycles = entryPointCycles.get(entryPoint) || [];
160
323
  if (cycles.length > 0) {
161
324
  globalHasCycles = true;
162
- console.error(`❌ [${relativeEntry}] — Found ${cycles.length} circular dependencies:`);
325
+ console.error(`❌ [${absoluteEntry}] — Found ${cycles.length} circular dependencies:`);
163
326
  cycles.forEach((cycle, index) => {
164
- const readableCycle = cycle.map((p) => path.relative(process.cwd(), p)).join(' -> ');
327
+ const readableCycle = cycle.map((p) => path.resolve(p)).join('\n -> ');
165
328
  console.error(` ${index + 1}) ${readableCycle}`);
166
329
  });
167
330
  console.error('');
168
331
  }
169
332
  else {
170
- console.log(`✅ [${relativeEntry}] — Clean!`);
333
+ console.log(`✅ [${absoluteEntry}] — Clean!`);
171
334
  }
172
335
  }
173
- if (globalHasCycles) {
336
+ if (globalHasCycles || globalDetectedCycles.size > 0) {
174
337
  console.error('💥 Validation failed. Circular dependencies detected.');
175
338
  process.exit(1);
176
339
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ts-stack/cycle-detector",
3
3
  "type": "module",
4
- "version": "1.0.2",
4
+ "version": "1.0.3",
5
5
  "bin": {
6
6
  "cycle-detector": "./dist/index.js"
7
7
  },
@@ -45,6 +45,7 @@
45
45
  "nodemon": "^3.1.14",
46
46
  "prettier": "^3.8.4",
47
47
  "rimraf": "^5.0.10",
48
+ "ts-node": "^10.9.2",
48
49
  "typescript": "^5.9.3",
49
50
  "typescript-eslint": "^8.62.0"
50
51
  },
package/src/index.ts CHANGED
@@ -6,14 +6,16 @@ import ts from 'typescript';
6
6
 
7
7
  type NodeState = 'VISITING' | 'VISITED';
8
8
 
9
+ // Global shared structures for maximum performance
9
10
  const graph = new Map<string, string[]>();
10
- const visited = new Map<string, NodeState>();
11
- const currentStack: string[] = [];
12
- const detectedCycles: string[][] = [];
11
+ const allUniqueCycles: string[][] = [];
12
+ const globalDetectedCycles = new Set<string>();
13
+
14
+ const packageMetaCache = new Map<string, { pkgDir: string; srcDirName: string; outDirName: string; } | null>();
15
+ const compilerOptionsCache = new Map<string, ts.CompilerOptions>();
16
+
17
+ let globalProjectPath: string | undefined;
13
18
 
14
- /**
15
- * Parses command line arguments to separate options from entry point patterns.
16
- */
17
19
  function parseArgs() {
18
20
  const args = [...process.argv.slice(2)];
19
21
  let projectPath: string | undefined;
@@ -31,10 +33,6 @@ function parseArgs() {
31
33
  return { entryPatterns, projectPath };
32
34
  }
33
35
 
34
- /**
35
- * Checks if an import/export declaration is type-only.
36
- * Type-only imports are stripped during compilation and don't cause runtime cycles.
37
- */
38
36
  function isRuntimeImport(node: ts.ImportDeclaration | ts.ExportDeclaration): boolean {
39
37
  if (ts.isExportDeclaration(node)) {
40
38
  if (!node.moduleSpecifier) return false;
@@ -42,10 +40,9 @@ function isRuntimeImport(node: ts.ImportDeclaration | ts.ExportDeclaration): boo
42
40
  }
43
41
 
44
42
  if (ts.isImportDeclaration(node)) {
45
- if (!node.importClause) return true; // Side-effect import: import './foo'
46
- if (node.importClause.isTypeOnly) return false; // import type { X } from './foo'
43
+ if (!node.importClause) return true;
44
+ if (node.importClause.phaseModifier) return false;
47
45
 
48
- // Check individual specifiers: import { type A, B } from './foo'
49
46
  const namedBindings = node.importClause.namedBindings;
50
47
  if (namedBindings && ts.isNamedImports(namedBindings)) {
51
48
  return !namedBindings.elements.every((el) => el.isTypeOnly);
@@ -56,106 +53,217 @@ function isRuntimeImport(node: ts.ImportDeclaration | ts.ExportDeclaration): boo
56
53
  return false;
57
54
  }
58
55
 
59
- /**
60
- * Resolves module paths using the TypeScript Compiler API.
61
- * Respects tsconfig paths/aliases and ESM extensions.
62
- */
63
- function resolveModule(moduleName: string, containingFile: string, options: ts.CompilerOptions): string | null {
64
- const result = ts.resolveModuleName(moduleName, containingFile, options, ts.sys);
65
- if (result.resolvedModule && !result.resolvedModule.isExternalLibraryImport) {
66
- return result.resolvedModule.resolvedFileName;
56
+ function getCompilerOptionsForFile(filePath: string): ts.CompilerOptions {
57
+ const currentDir = path.dirname(filePath);
58
+
59
+ const cachedOptions = compilerOptionsCache.get(currentDir);
60
+ if (cachedOptions !== undefined) {
61
+ return cachedOptions;
67
62
  }
68
- return null;
63
+
64
+ const configPath = ts.findConfigFile(currentDir, ts.sys.fileExists, 'tsconfig.json') || globalProjectPath;
65
+
66
+ if (configPath) {
67
+ const resolvedConfigPath = path.resolve(configPath);
68
+ const configDir = path.dirname(resolvedConfigPath);
69
+
70
+ const cachedConfig = compilerOptionsCache.get(resolvedConfigPath);
71
+ if (cachedConfig !== undefined) {
72
+ compilerOptionsCache.set(currentDir, cachedConfig);
73
+ return cachedConfig;
74
+ }
75
+
76
+ try {
77
+ const configFile = ts.readConfigFile(resolvedConfigPath, ts.sys.readFile);
78
+ const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, configDir);
79
+ const options = parsedConfig.options || {};
80
+
81
+ compilerOptionsCache.set(resolvedConfigPath, options);
82
+ compilerOptionsCache.set(currentDir, options);
83
+ return options;
84
+ } catch {
85
+ // Fallback
86
+ }
87
+ }
88
+
89
+ return {};
69
90
  }
70
91
 
71
- /**
72
- * Analyzes a single entry point, builds its dependency graph, and detects cycles.
73
- */
74
- function analyzeEntryPoint(entryPoint: string, projectPath?: string): string[][] {
75
- graph.clear();
76
- visited.clear();
77
- currentStack.length = 0;
78
- detectedCycles.length = 0;
79
-
80
- // Locate tsconfig.json automatically if not explicitly provided
81
- const configPath = projectPath
82
- ? path.resolve(projectPath)
83
- : ts.findConfigFile(path.dirname(entryPoint), ts.sys.fileExists, 'tsconfig.json');
84
-
85
- let compilerOptions: ts.CompilerOptions = {};
86
-
87
- if (configPath && fs.existsSync(configPath)) {
88
- const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
89
- const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, path.dirname(configPath));
90
- compilerOptions = parsedConfig.options;
91
- }
92
-
93
- // Recursive AST parsing to build the graph
94
- function parseFile(filePath: string) {
95
- if (graph.has(filePath) || !fs.existsSync(filePath)) return;
96
- graph.set(filePath, []);
97
-
98
- const content = fs.readFileSync(filePath, 'utf8');
99
- const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
100
- const imports: string[] = [];
101
-
102
- function walk(node: ts.Node) {
103
- if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
104
- if (isRuntimeImport(node)) {
105
- const specifier = node.moduleSpecifier;
106
- if (specifier && ts.isStringLiteral(specifier)) {
107
- const resolved = resolveModule(specifier.text, filePath, compilerOptions);
108
- if (resolved && !imports.includes(resolved)) imports.push(resolved);
92
+ function getPackageMeta(filePath: string) {
93
+ let currentDir = path.dirname(filePath);
94
+ const visitedDirs: string[] = [];
95
+
96
+ while (currentDir && currentDir !== path.parse(currentDir).root) {
97
+ const cached = packageMetaCache.get(currentDir);
98
+ if (cached !== undefined) {
99
+ for (const d of visitedDirs) packageMetaCache.set(d, cached);
100
+ return cached;
101
+ }
102
+
103
+ const pkgJsonPath = path.join(currentDir, 'package.json');
104
+ if (fs.existsSync(pkgJsonPath)) {
105
+ try {
106
+ const content = fs.readFileSync(pkgJsonPath, 'utf8');
107
+ const pkg = JSON.parse(content);
108
+
109
+ let outDirName = 'dist';
110
+ const mainField = pkg.main || pkg.types || pkg.typings || '';
111
+ if (mainField) {
112
+ const parts = path.normalize(mainField).split(path.sep);
113
+ if (parts.length > 1 && parts[0] !== '.' && parts[0] !== '..') {
114
+ outDirName = parts[0];
115
+ } else if (parts.length > 2 && (parts[0] === '.' || parts[0] === '..')) {
116
+ outDirName = parts[1];
109
117
  }
110
118
  }
119
+
120
+ let srcDirName = 'src';
121
+ if (fs.existsSync(path.join(currentDir, 'source'))) srcDirName = 'source';
122
+ else if (fs.existsSync(path.join(currentDir, 'lib'))) srcDirName = 'lib';
123
+
124
+ const meta = { pkgDir: currentDir, srcDirName, outDirName };
125
+ packageMetaCache.set(currentDir, meta);
126
+ for (const d of visitedDirs) packageMetaCache.set(d, meta);
127
+ return meta;
128
+ } catch {
129
+ packageMetaCache.set(currentDir, null);
130
+ for (const d of visitedDirs) packageMetaCache.set(d, null);
131
+ return null;
111
132
  }
112
- ts.forEachChild(node, walk);
113
133
  }
114
134
 
115
- walk(sourceFile);
116
- graph.set(filePath, imports);
135
+ visitedDirs.push(currentDir);
136
+ currentDir = path.dirname(currentDir);
137
+ }
117
138
 
118
- for (const dep of imports) parseFile(dep);
139
+ return null;
140
+ }
141
+
142
+ function resolveModule(moduleName: string, containingFile: string, options: ts.CompilerOptions): string | null {
143
+ const result = ts.resolveModuleName(moduleName, containingFile, options, ts.sys);
144
+ if (!result.resolvedModule) return null;
145
+
146
+ const resolvedFileName = path.resolve(result.resolvedModule.resolvedFileName);
147
+
148
+ if (resolvedFileName.includes(`${path.sep}node_modules${path.sep}`)) {
149
+ return null;
119
150
  }
120
151
 
121
- // Graph coloring DFS algorithm for cycle detection
122
- function findCycles(node: string) {
123
- visited.set(node, 'VISITING');
124
- currentStack.push(node);
152
+ const meta = getPackageMeta(resolvedFileName);
153
+ if (meta) {
154
+ const { pkgDir, srcDirName, outDirName } = meta;
155
+ const srcDirPath = path.join(pkgDir, srcDirName);
125
156
 
126
- for (const neighbor of graph.get(node) || []) {
127
- const state = visited.get(neighbor);
128
- if (state === 'VISITING') {
129
- const startIdx = currentStack.indexOf(neighbor);
130
- const cycle = currentStack.slice(startIdx);
131
- cycle.push(neighbor);
132
- detectedCycles.push(cycle);
133
- } else if (!state) {
134
- findCycles(neighbor);
157
+ if (resolvedFileName.startsWith(srcDirPath + path.sep)) {
158
+ return resolvedFileName;
159
+ }
160
+
161
+ const outDirPath = path.join(pkgDir, outDirName);
162
+ if (resolvedFileName.startsWith(outDirPath + path.sep) || resolvedFileName === outDirPath) {
163
+ const relativeToOut = path.relative(outDirPath, resolvedFileName);
164
+ let baseName = relativeToOut;
165
+
166
+ if (baseName.endsWith('.d.ts')) baseName = baseName.slice(0, -5);
167
+ else if (baseName.endsWith('.d.mts')) baseName = baseName.slice(0, -6);
168
+ else if (baseName.endsWith('.d.cts')) baseName = baseName.slice(0, -6);
169
+ else if (baseName.endsWith('.js')) baseName = baseName.slice(0, -3);
170
+ else if (baseName.endsWith('.mjs')) baseName = baseName.slice(0, -4);
171
+ else if (baseName.endsWith('.cjs')) baseName = baseName.slice(0, -4);
172
+ else if (baseName.endsWith('.jsx')) baseName = baseName.slice(0, -4);
173
+
174
+ const extensions = ['.ts', '.tsx', '.mts', '.cts'];
175
+ for (const ext of extensions) {
176
+ const targetSrcFile = path.join(srcDirPath, baseName + ext);
177
+ if (fs.existsSync(targetSrcFile)) {
178
+ return targetSrcFile;
179
+ }
135
180
  }
136
181
  }
182
+ }
137
183
 
138
- currentStack.pop();
139
- visited.set(node, 'VISITED');
184
+ if (result.resolvedModule.isExternalLibraryImport) {
185
+ return null;
186
+ }
187
+
188
+ return resolvedFileName;
189
+ }
190
+
191
+ function getCanonicalCycleKey(cycle: string[]): string {
192
+ const nodes = cycle.slice(0, -1);
193
+ if (nodes.length === 0) return '';
194
+
195
+ let minIdx = 0;
196
+ for (let i = 1; i < nodes.length; i++) {
197
+ if (nodes[i] < nodes[minIdx]) {
198
+ minIdx = i;
199
+ }
200
+ }
201
+
202
+ const rotated = [...nodes.slice(minIdx), ...nodes.slice(0, minIdx)];
203
+ rotated.push(rotated[0]);
204
+ return rotated.join('|');
205
+ }
206
+
207
+ function parseFile(filePath: string) {
208
+ if (graph.has(filePath) || !fs.existsSync(filePath)) return;
209
+ graph.set(filePath, []);
210
+
211
+ const options = getCompilerOptionsForFile(filePath);
212
+ const content = fs.readFileSync(filePath, 'utf8');
213
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
214
+ const imports: string[] = [];
215
+
216
+ function walk(node: ts.Node) {
217
+ if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
218
+ if (isRuntimeImport(node)) {
219
+ const specifier = node.moduleSpecifier;
220
+ if (specifier && ts.isStringLiteral(specifier)) {
221
+ const resolved = resolveModule(specifier.text, filePath, options);
222
+ if (resolved && !imports.includes(resolved)) imports.push(resolved);
223
+ }
224
+ }
225
+ }
226
+ ts.forEachChild(node, walk);
140
227
  }
141
228
 
142
- parseFile(entryPoint);
143
- findCycles(entryPoint);
229
+ walk(sourceFile);
230
+ graph.set(filePath, imports);
144
231
 
145
- return detectedCycles;
232
+ for (const dep of imports) parseFile(dep);
233
+ }
234
+
235
+ /**
236
+ * Checks if a specific target file is reachable from a starting entry point in the graph.
237
+ */
238
+ function canReach(start: string, target: string): boolean {
239
+ const seen = new Set<string>();
240
+ const stack = [start];
241
+
242
+ while (stack.length > 0) {
243
+ const current = stack.pop()!;
244
+ if (current === target) return true;
245
+ if (seen.has(current)) continue;
246
+ seen.add(current);
247
+
248
+ const deps = graph.get(current) || [];
249
+ for (const dep of deps) {
250
+ stack.push(dep);
251
+ }
252
+ }
253
+ return false;
146
254
  }
147
255
 
148
256
  function main() {
149
257
  const { entryPatterns, projectPath } = parseArgs();
258
+ globalProjectPath = projectPath;
150
259
 
151
- if (entryPatterns.length == 0) {
152
- console.error('❌ Error: Please specify at least one entry point or glob pattern (e.g., packages/*/src/index.ts)');
260
+ if (entryPatterns.length === 0) {
261
+ console.error('❌ Error: Please specify at least one entry point or glob pattern.');
153
262
  process.exit(1);
154
263
  }
155
264
 
156
265
  const entryPoints: string[] = [];
157
266
 
158
- // Expand glob patterns or fall back to direct paths
159
267
  for (const pattern of entryPatterns) {
160
268
  const matches = fs.globSync ? fs.globSync(pattern) : [pattern];
161
269
 
@@ -179,28 +287,97 @@ function main() {
179
287
  process.exit(1);
180
288
  }
181
289
 
182
- console.log(`🔍 Found ${entryPoints.length} entry point(s) for analysis...\n`);
290
+ console.log(`🔍 Found ${entryPoints.length} entry point(s) for analysis. Building graph...\n`);
291
+
292
+ // Phase 1: Deep parse all files globally across all entry points
293
+ for (const entryPoint of entryPoints) {
294
+ parseFile(entryPoint);
295
+ }
296
+
297
+ // Phase 2: Traverse the global graph to discover all unique cycles
298
+ const visited = new Map<string, NodeState>();
299
+ const currentStack: string[] = [];
300
+
301
+ function findCycles(node: string) {
302
+ visited.set(node, 'VISITING');
303
+ currentStack.push(node);
304
+
305
+ for (const neighbor of graph.get(node) || []) {
306
+ const state = visited.get(neighbor);
307
+ if (state === 'VISITING') {
308
+ const startIdx = currentStack.indexOf(neighbor);
309
+ const cycle = currentStack.slice(startIdx);
310
+ cycle.push(neighbor);
311
+
312
+ const key = getCanonicalCycleKey(cycle);
313
+ if (!globalDetectedCycles.has(key)) {
314
+ globalDetectedCycles.add(key);
315
+ allUniqueCycles.push(cycle);
316
+ }
317
+ } else if (!state) {
318
+ findCycles(neighbor);
319
+ }
320
+ }
321
+
322
+ currentStack.pop();
323
+ visited.set(node, 'VISITED');
324
+ }
325
+
326
+ for (const entryPoint of entryPoints) {
327
+ if (!visited.has(entryPoint)) {
328
+ findCycles(entryPoint);
329
+ }
330
+ }
331
+
332
+ // Phase 3: Intelligently distribute and attribute cycles to their native entry points
333
+ const entryPointCycles = new Map<string, string[][]>();
334
+ for (const ep of entryPoints) {
335
+ entryPointCycles.set(ep, []);
336
+ }
337
+
338
+ for (const cycle of allUniqueCycles) {
339
+ const firstFile = cycle[0];
340
+
341
+ // Find the entrypoint whose source folder directly hosts this file
342
+ const matchedEp = entryPoints.find((ep) => {
343
+ const epDir = path.dirname(ep);
344
+ return firstFile.startsWith(epDir + path.sep) || firstFile === ep;
345
+ });
346
+
347
+ if (matchedEp) {
348
+ entryPointCycles.get(matchedEp)!.push(cycle);
349
+ } else {
350
+ // Cross-package cycle or edge case: assign to the first entry point that can reach it
351
+ const reachingEp = entryPoints.find((ep) => canReach(ep, firstFile));
352
+ if (reachingEp) {
353
+ entryPointCycles.get(reachingEp)!.push(cycle);
354
+ } else {
355
+ entryPointCycles.get(entryPoints[0])!.push(cycle);
356
+ }
357
+ }
358
+ }
183
359
 
360
+ // Phase 4: Output the clean, perfectly targeted report
184
361
  let globalHasCycles = false;
185
362
 
186
363
  for (const entryPoint of entryPoints) {
187
- const relativeEntry = path.relative(process.cwd(), entryPoint);
188
- const cycles = analyzeEntryPoint(entryPoint, projectPath);
364
+ const absoluteEntry = path.resolve(entryPoint);
365
+ const cycles = entryPointCycles.get(entryPoint) || [];
189
366
 
190
367
  if (cycles.length > 0) {
191
368
  globalHasCycles = true;
192
- console.error(`❌ [${relativeEntry}] — Found ${cycles.length} circular dependencies:`);
369
+ console.error(`❌ [${absoluteEntry}] — Found ${cycles.length} circular dependencies:`);
193
370
  cycles.forEach((cycle, index) => {
194
- const readableCycle = cycle.map((p) => path.relative(process.cwd(), p)).join(' -> ');
371
+ const readableCycle = cycle.map((p) => path.resolve(p)).join('\n -> ');
195
372
  console.error(` ${index + 1}) ${readableCycle}`);
196
373
  });
197
374
  console.error('');
198
375
  } else {
199
- console.log(`✅ [${relativeEntry}] — Clean!`);
376
+ console.log(`✅ [${absoluteEntry}] — Clean!`);
200
377
  }
201
378
  }
202
379
 
203
- if (globalHasCycles) {
380
+ if (globalHasCycles || globalDetectedCycles.size > 0) {
204
381
  console.error('💥 Validation failed. Circular dependencies detected.');
205
382
  process.exit(1);
206
383
  } else {