ucn 3.0.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.

Potentially problematic release.


This version of ucn might be problematic. Click here for more details.

Files changed (45) hide show
  1. package/.claude/skills/ucn/SKILL.md +77 -0
  2. package/LICENSE +21 -0
  3. package/README.md +135 -0
  4. package/cli/index.js +2437 -0
  5. package/core/discovery.js +513 -0
  6. package/core/imports.js +558 -0
  7. package/core/output.js +1274 -0
  8. package/core/parser.js +279 -0
  9. package/core/project.js +3261 -0
  10. package/index.js +52 -0
  11. package/languages/go.js +653 -0
  12. package/languages/index.js +267 -0
  13. package/languages/java.js +826 -0
  14. package/languages/javascript.js +1346 -0
  15. package/languages/python.js +667 -0
  16. package/languages/rust.js +950 -0
  17. package/languages/utils.js +457 -0
  18. package/package.json +42 -0
  19. package/test/fixtures/go/go.mod +3 -0
  20. package/test/fixtures/go/main.go +257 -0
  21. package/test/fixtures/go/service.go +187 -0
  22. package/test/fixtures/java/DataService.java +279 -0
  23. package/test/fixtures/java/Main.java +287 -0
  24. package/test/fixtures/java/Utils.java +199 -0
  25. package/test/fixtures/java/pom.xml +6 -0
  26. package/test/fixtures/javascript/main.js +109 -0
  27. package/test/fixtures/javascript/package.json +1 -0
  28. package/test/fixtures/javascript/service.js +88 -0
  29. package/test/fixtures/javascript/utils.js +67 -0
  30. package/test/fixtures/python/main.py +198 -0
  31. package/test/fixtures/python/pyproject.toml +3 -0
  32. package/test/fixtures/python/service.py +166 -0
  33. package/test/fixtures/python/utils.py +118 -0
  34. package/test/fixtures/rust/Cargo.toml +3 -0
  35. package/test/fixtures/rust/main.rs +253 -0
  36. package/test/fixtures/rust/service.rs +210 -0
  37. package/test/fixtures/rust/utils.rs +154 -0
  38. package/test/fixtures/typescript/main.ts +154 -0
  39. package/test/fixtures/typescript/package.json +1 -0
  40. package/test/fixtures/typescript/repository.ts +149 -0
  41. package/test/fixtures/typescript/types.ts +114 -0
  42. package/test/parser.test.js +3661 -0
  43. package/test/public-repos-test.js +477 -0
  44. package/test/systematic-test.js +619 -0
  45. package/ucn.js +8 -0
@@ -0,0 +1,558 @@
1
+ /**
2
+ * core/imports.js - Import/export parsing for dependency tracking
3
+ *
4
+ * Extracts import and export statements from source files
5
+ * to build dependency graphs.
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const { getParser, getLanguageModule } = require('../languages');
11
+
12
+ /**
13
+ * Import patterns by language
14
+ * @deprecated Use AST-based findImportsInCode() from language modules instead.
15
+ * Kept only as fallback for unsupported languages or when AST parsing fails.
16
+ */
17
+ const IMPORT_PATTERNS = {
18
+ javascript: {
19
+ importDefault: /import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g,
20
+ importNamed: /import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g,
21
+ importNamespace: /import\s*\*\s*as\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g,
22
+ require: /(?:const|let|var)\s+(?:\{[^}]+\}|(\w+))\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
23
+ exportNamed: /^\s*export\s+(?:async\s+)?(?:function|class|const|let|var|interface|type)\s+(\w+)/gm,
24
+ exportDefault: /^\s*export\s+default\s+(?:(?:async\s+)?(?:function|class)\s+)?(\w+)?/gm,
25
+ exportList: /^\s*export\s*\{([^}]+)\}/gm,
26
+ moduleExports: /^module\.exports\s*=\s*(?:\{([^}]+)\}|(\w+))/gm,
27
+ exportsNamed: /^exports\.(\w+)\s*=[^=]/gm,
28
+ importType: /import\s+type\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g,
29
+ importSideEffect: /import\s+['"]([^'"]+)['"]/g,
30
+ importDynamic: /(?:await\s+)?import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
31
+ reExportNamed: /^\s*export\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/gm,
32
+ reExportAll: /^\s*export\s*\*\s*from\s*['"]([^'"]+)['"]/gm
33
+ },
34
+ python: {
35
+ importModule: /^import\s+([\w.]+)(?:\s+as\s+(\w+))?/gm,
36
+ fromImport: /^from\s+([.\w]+)\s+import\s+(.+)/gm,
37
+ exportAll: /__all__\s*=\s*\[([^\]]+)\]/g
38
+ },
39
+ go: {
40
+ importSingle: /import\s+"([^"]+)"/g,
41
+ importBlock: /import\s*\(\s*([\s\S]*?)\s*\)/g,
42
+ exportedFunc: /^func\s+(?:\([^)]+\)\s+)?([A-Z]\w*)\s*\(/gm,
43
+ exportedType: /^type\s+([A-Z]\w*)\s+/gm
44
+ },
45
+ java: {
46
+ importStatement: /import\s+(?:static\s+)?([\w.]+(?:\.\*)?)\s*;/g,
47
+ exportedClass: /public\s+(?:abstract\s+)?(?:final\s+)?(?:class|interface|enum)\s+(\w+)/g
48
+ },
49
+ rust: {
50
+ useStatement: /^use\s+([^;]+);/gm,
51
+ modDecl: /^\s*mod\s+(\w+)\s*;/gm
52
+ }
53
+ };
54
+
55
+ /**
56
+ * Extract imports from file content using AST
57
+ *
58
+ * @param {string} content - File content
59
+ * @param {string} language - Language name
60
+ * @returns {{ imports: Array<{ module: string, names: string[], type: string, line: number }> }}
61
+ */
62
+ function extractImports(content, language) {
63
+ // Normalize language name for parser
64
+ const normalizedLang = (language === 'typescript' || language === 'tsx') ? 'javascript' : language;
65
+
66
+ // Try AST-based extraction first
67
+ const langModule = getLanguageModule(normalizedLang);
68
+ if (langModule && typeof langModule.findImportsInCode === 'function') {
69
+ try {
70
+ const parser = getParser(normalizedLang);
71
+ if (parser) {
72
+ const imports = langModule.findImportsInCode(content, parser);
73
+ return { imports };
74
+ }
75
+ } catch (e) {
76
+ // Fall through to regex-based extraction
77
+ }
78
+ }
79
+
80
+ // Fallback to regex-based extraction (deprecated)
81
+ const imports = [];
82
+ if (language === 'javascript' || language === 'typescript' || language === 'tsx') {
83
+ extractJSImports(content, imports);
84
+ } else if (language === 'python') {
85
+ extractPythonImports(content, imports);
86
+ } else if (language === 'go') {
87
+ extractGoImports(content, imports);
88
+ } else if (language === 'java') {
89
+ extractJavaImports(content, imports);
90
+ } else if (language === 'rust') {
91
+ extractRustImports(content, imports);
92
+ }
93
+
94
+ return { imports };
95
+ }
96
+
97
+ /**
98
+ * @deprecated Use AST-based findImportsInCode() from language modules.
99
+ */
100
+ function extractJSImports(content, imports) {
101
+ const patterns = IMPORT_PATTERNS.javascript;
102
+ let match;
103
+
104
+ // Default imports
105
+ let regex = new RegExp(patterns.importDefault.source, 'g');
106
+ while ((match = regex.exec(content)) !== null) {
107
+ imports.push({ module: match[2], names: [match[1]], type: 'default' });
108
+ }
109
+
110
+ // Named imports
111
+ regex = new RegExp(patterns.importNamed.source, 'g');
112
+ while ((match = regex.exec(content)) !== null) {
113
+ const names = match[1].split(',').map(n => n.trim().split(/\s+as\s+/)[0].trim()).filter(n => n);
114
+ imports.push({ module: match[2], names, type: 'named' });
115
+ }
116
+
117
+ // Namespace imports
118
+ regex = new RegExp(patterns.importNamespace.source, 'g');
119
+ while ((match = regex.exec(content)) !== null) {
120
+ imports.push({ module: match[2], names: [match[1]], type: 'namespace' });
121
+ }
122
+
123
+ // Require
124
+ regex = new RegExp(patterns.require.source, 'g');
125
+ while ((match = regex.exec(content)) !== null) {
126
+ imports.push({ module: match[2], names: match[1] ? [match[1]] : [], type: 'require' });
127
+ }
128
+
129
+ // Type imports
130
+ regex = new RegExp(patterns.importType.source, 'g');
131
+ while ((match = regex.exec(content)) !== null) {
132
+ const names = match[1].split(',').map(n => n.trim().split(/\s+as\s+/)[0].trim()).filter(n => n);
133
+ imports.push({ module: match[2], names, type: 'type' });
134
+ }
135
+
136
+ // Side-effect imports
137
+ regex = new RegExp(patterns.importSideEffect.source, 'g');
138
+ while ((match = regex.exec(content)) !== null) {
139
+ const module = match[1];
140
+ if (!imports.some(i => i.module === module)) {
141
+ imports.push({ module, names: [], type: 'side-effect' });
142
+ }
143
+ }
144
+
145
+ // Dynamic imports
146
+ regex = new RegExp(patterns.importDynamic.source, 'g');
147
+ while ((match = regex.exec(content)) !== null) {
148
+ const module = match[1];
149
+ if (!imports.some(i => i.module === module)) {
150
+ imports.push({ module, names: [], type: 'dynamic' });
151
+ }
152
+ }
153
+
154
+ // Re-exports
155
+ regex = new RegExp(patterns.reExportNamed.source, 'gm');
156
+ while ((match = regex.exec(content)) !== null) {
157
+ const names = match[1].split(',').map(n => n.trim().split(/\s+as\s+/)[0].trim()).filter(n => n);
158
+ imports.push({ module: match[2], names, type: 're-export' });
159
+ }
160
+
161
+ regex = new RegExp(patterns.reExportAll.source, 'gm');
162
+ while ((match = regex.exec(content)) !== null) {
163
+ imports.push({ module: match[1], names: ['*'], type: 're-export-all' });
164
+ }
165
+ }
166
+
167
+ /** @deprecated Use AST-based findImportsInCode() from language modules. */
168
+ function extractPythonImports(content, imports) {
169
+ const patterns = IMPORT_PATTERNS.python;
170
+ let match;
171
+
172
+ let regex = new RegExp(patterns.importModule.source, 'gm');
173
+ while ((match = regex.exec(content)) !== null) {
174
+ const moduleName = match[1];
175
+ const alias = match[2] || moduleName.split('.').pop();
176
+ imports.push({ module: moduleName, names: [alias], type: 'module' });
177
+ }
178
+
179
+ regex = new RegExp(patterns.fromImport.source, 'gm');
180
+ while ((match = regex.exec(content)) !== null) {
181
+ const moduleName = match[1];
182
+ const importList = match[2].trim();
183
+
184
+ if (importList === '*') {
185
+ imports.push({ module: moduleName, names: ['*'], type: 'star' });
186
+ } else {
187
+ const names = importList.split(',').map(n => n.trim().split(/\s+as\s+/)[0].trim()).filter(n => n && n !== '(');
188
+ imports.push({ module: moduleName, names, type: 'from' });
189
+ }
190
+ }
191
+ }
192
+
193
+ /** @deprecated Use AST-based findImportsInCode() from language modules. */
194
+ function extractGoImports(content, imports) {
195
+ const patterns = IMPORT_PATTERNS.go;
196
+ let match;
197
+
198
+ let regex = new RegExp(patterns.importSingle.source, 'g');
199
+ while ((match = regex.exec(content)) !== null) {
200
+ const pkg = match[1];
201
+ imports.push({ module: pkg, names: [path.basename(pkg)], type: 'single' });
202
+ }
203
+
204
+ regex = new RegExp(patterns.importBlock.source, 'g');
205
+ while ((match = regex.exec(content)) !== null) {
206
+ const block = match[1];
207
+ const pkgMatches = block.matchAll(/"([^"]+)"/g);
208
+ for (const pkgMatch of pkgMatches) {
209
+ const pkg = pkgMatch[1];
210
+ imports.push({ module: pkg, names: [path.basename(pkg)], type: 'block' });
211
+ }
212
+ }
213
+ }
214
+
215
+ /** @deprecated Use AST-based findImportsInCode() from language modules. */
216
+ function extractJavaImports(content, imports) {
217
+ const patterns = IMPORT_PATTERNS.java;
218
+ let match;
219
+
220
+ let regex = new RegExp(patterns.importStatement.source, 'g');
221
+ while ((match = regex.exec(content)) !== null) {
222
+ const fullImport = match[1];
223
+ const parts = fullImport.split('.');
224
+ const name = parts[parts.length - 1];
225
+ imports.push({ module: fullImport, names: name === '*' ? ['*'] : [name], type: 'import' });
226
+ }
227
+ }
228
+
229
+ /** @deprecated Use AST-based findImportsInCode() from language modules. */
230
+ function extractRustImports(content, imports) {
231
+ const patterns = IMPORT_PATTERNS.rust;
232
+ let match;
233
+
234
+ let regex = new RegExp(patterns.useStatement.source, 'gm');
235
+ while ((match = regex.exec(content)) !== null) {
236
+ let raw = match[1].trim().split('{')[0].trim().split(' as ')[0].trim().replace(/::$/, '');
237
+ if (raw) {
238
+ imports.push({ module: raw, names: [], type: 'use' });
239
+ }
240
+ }
241
+
242
+ regex = new RegExp(patterns.modDecl.source, 'gm');
243
+ while ((match = regex.exec(content)) !== null) {
244
+ imports.push({ module: `self::${match[1]}`, names: [match[1]], type: 'mod' });
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Extract exports from file content using AST
250
+ */
251
+ function extractExports(content, language) {
252
+ // Normalize language name for parser
253
+ const normalizedLang = (language === 'typescript' || language === 'tsx') ? 'javascript' : language;
254
+
255
+ // Try AST-based extraction first
256
+ const langModule = getLanguageModule(normalizedLang);
257
+ if (langModule && typeof langModule.findExportsInCode === 'function') {
258
+ try {
259
+ const parser = getParser(normalizedLang);
260
+ if (parser) {
261
+ const foundExports = langModule.findExportsInCode(content, parser);
262
+ return { exports: foundExports };
263
+ }
264
+ } catch (e) {
265
+ // Fall through to regex-based extraction
266
+ }
267
+ }
268
+
269
+ // Fallback to regex-based extraction (deprecated)
270
+ const foundExports = [];
271
+ if (language === 'javascript' || language === 'typescript' || language === 'tsx') {
272
+ extractJSExports(content, foundExports);
273
+ } else if (language === 'python') {
274
+ extractPythonExports(content, foundExports);
275
+ } else if (language === 'go') {
276
+ extractGoExports(content, foundExports);
277
+ } else if (language === 'java') {
278
+ extractJavaExports(content, foundExports);
279
+ }
280
+
281
+ return { exports: foundExports };
282
+ }
283
+
284
+ /** @deprecated Use AST-based findExportsInCode() from language modules. */
285
+ function extractJSExports(content, exports) {
286
+ const patterns = IMPORT_PATTERNS.javascript;
287
+ let match;
288
+
289
+ let regex = new RegExp(patterns.exportNamed.source, 'gm');
290
+ while ((match = regex.exec(content)) !== null) {
291
+ exports.push({ name: match[1], type: 'named' });
292
+ }
293
+
294
+ regex = new RegExp(patterns.exportDefault.source, 'gm');
295
+ while ((match = regex.exec(content)) !== null) {
296
+ exports.push({ name: match[1] || 'default', type: 'default' });
297
+ }
298
+
299
+ regex = new RegExp(patterns.exportList.source, 'gm');
300
+ while ((match = regex.exec(content)) !== null) {
301
+ const names = match[1].split(',').map(n => n.trim().split(/\s+as\s+/)[0].trim()).filter(n => n);
302
+ for (const name of names) {
303
+ exports.push({ name, type: 'list' });
304
+ }
305
+ }
306
+
307
+ regex = new RegExp(patterns.exportsNamed.source, 'gm');
308
+ while ((match = regex.exec(content)) !== null) {
309
+ exports.push({ name: match[1], type: 'commonjs-named' });
310
+ }
311
+
312
+ // module.exports = { a, b, c } or module.exports = identifier
313
+ regex = new RegExp(patterns.moduleExports.source, 'gm');
314
+ while ((match = regex.exec(content)) !== null) {
315
+ if (match[1]) {
316
+ // Object literal: module.exports = { a, b, c }
317
+ const names = match[1].split(',').map(n => n.trim().split(/\s*:\s*/)[0].trim()).filter(n => n && !n.includes('('));
318
+ for (const name of names) {
319
+ exports.push({ name, type: 'commonjs-object' });
320
+ }
321
+ } else if (match[2]) {
322
+ // Single identifier: module.exports = SomeClass
323
+ exports.push({ name: match[2], type: 'commonjs-default' });
324
+ }
325
+ }
326
+ }
327
+
328
+ /** @deprecated Use AST-based findExportsInCode() from language modules. */
329
+ function extractPythonExports(content, exports) {
330
+ let match;
331
+
332
+ // Check for __all__
333
+ let regex = new RegExp(IMPORT_PATTERNS.python.exportAll.source, 'g');
334
+ while ((match = regex.exec(content)) !== null) {
335
+ const names = match[1].split(',').map(n => n.trim().replace(/['"]/g, '')).filter(n => n);
336
+ for (const name of names) {
337
+ exports.push({ name, type: 'explicit' });
338
+ }
339
+ }
340
+
341
+ // If no __all__, look for public names
342
+ if (exports.length === 0) {
343
+ const funcRegex = /^def\s+([a-zA-Z]\w*)\s*\(/gm;
344
+ while ((match = funcRegex.exec(content)) !== null) {
345
+ if (!match[1].startsWith('_')) {
346
+ exports.push({ name: match[1], type: 'function' });
347
+ }
348
+ }
349
+
350
+ const classRegex = /^class\s+([a-zA-Z]\w*)/gm;
351
+ while ((match = classRegex.exec(content)) !== null) {
352
+ if (!match[1].startsWith('_')) {
353
+ exports.push({ name: match[1], type: 'class' });
354
+ }
355
+ }
356
+ }
357
+ }
358
+
359
+ /** @deprecated Use AST-based findExportsInCode() from language modules. */
360
+ function extractGoExports(content, exports) {
361
+ const patterns = IMPORT_PATTERNS.go;
362
+ let match;
363
+
364
+ let regex = new RegExp(patterns.exportedFunc.source, 'gm');
365
+ while ((match = regex.exec(content)) !== null) {
366
+ exports.push({ name: match[1], type: 'function' });
367
+ }
368
+
369
+ regex = new RegExp(patterns.exportedType.source, 'gm');
370
+ while ((match = regex.exec(content)) !== null) {
371
+ exports.push({ name: match[1], type: 'type' });
372
+ }
373
+ }
374
+
375
+ /** @deprecated Use AST-based findExportsInCode() from language modules. */
376
+ function extractJavaExports(content, exports) {
377
+ let match;
378
+ let regex = new RegExp(IMPORT_PATTERNS.java.exportedClass.source, 'g');
379
+ while ((match = regex.exec(content)) !== null) {
380
+ exports.push({ name: match[1], type: 'class' });
381
+ }
382
+ }
383
+
384
+ // Cache for tsconfig lookups
385
+ const tsconfigCache = new Map();
386
+
387
+ /**
388
+ * Resolve an import path to an actual file path
389
+ *
390
+ * @param {string} importPath - Import string
391
+ * @param {string} fromFile - File containing the import
392
+ * @param {object} config - Configuration { aliases, extensions, language, root }
393
+ * @returns {string|null} - Resolved absolute path or null if external
394
+ */
395
+ function resolveImport(importPath, fromFile, config = {}) {
396
+ const fromDir = path.dirname(fromFile);
397
+
398
+ // Strip query strings (e.g., ?raw, ?url)
399
+ importPath = importPath.split('?')[0];
400
+
401
+ // External packages (not relative or alias)
402
+ if (!importPath.startsWith('.') && !importPath.startsWith('/')) {
403
+ // Check aliases
404
+ if (config.aliases) {
405
+ for (const [alias, target] of Object.entries(config.aliases)) {
406
+ if (importPath.startsWith(alias)) {
407
+ const relativePath = importPath.slice(alias.length);
408
+ const targetPath = path.join(config.root || fromDir, target, relativePath);
409
+ return resolveFilePath(targetPath, config.extensions || getExtensions(config.language));
410
+ }
411
+ }
412
+ }
413
+
414
+ // Check tsconfig paths (JS/TS only)
415
+ if (config.language === 'javascript' || config.language === 'typescript' || config.language === 'tsx') {
416
+ const tsconfig = findTsConfig(fromDir, config.root);
417
+ if (tsconfig && tsconfig.compiledPaths) {
418
+ // Use pre-compiled regex patterns from cache
419
+ for (const { regex, targets } of tsconfig.compiledPaths) {
420
+ const match = importPath.match(regex);
421
+ if (match) {
422
+ for (const target of targets) {
423
+ const resolved = target.replace('*', match[1] || '');
424
+ const basePath = tsconfig.baseUrl || path.dirname(tsconfig.configPath);
425
+ const fullPath = path.join(basePath, resolved);
426
+ const result = resolveFilePath(fullPath, config.extensions || getExtensions(config.language));
427
+ if (result) return result;
428
+ }
429
+ }
430
+ }
431
+ }
432
+ }
433
+
434
+ return null; // External package
435
+ }
436
+
437
+ // Relative imports
438
+ const resolved = path.resolve(fromDir, importPath);
439
+ return resolveFilePath(resolved, config.extensions || getExtensions(config.language));
440
+ }
441
+
442
+ /**
443
+ * Try to resolve a path with various extensions
444
+ */
445
+ function resolveFilePath(basePath, extensions) {
446
+ // Check exact path
447
+ if (fs.existsSync(basePath) && fs.statSync(basePath).isFile()) {
448
+ return basePath;
449
+ }
450
+
451
+ // Try adding extensions
452
+ for (const ext of extensions) {
453
+ const withExt = basePath + ext;
454
+ if (fs.existsSync(withExt)) return withExt;
455
+ }
456
+
457
+ // Try index files
458
+ for (const ext of extensions) {
459
+ const indexPath = path.join(basePath, 'index' + ext);
460
+ if (fs.existsSync(indexPath)) return indexPath;
461
+ }
462
+
463
+ return null;
464
+ }
465
+
466
+ /**
467
+ * Get file extensions for a language
468
+ */
469
+ function getExtensions(language) {
470
+ switch (language) {
471
+ case 'javascript':
472
+ return ['.js', '.jsx', '.mjs', '.cjs'];
473
+ case 'typescript':
474
+ case 'tsx':
475
+ return ['.ts', '.tsx', '.js', '.jsx'];
476
+ case 'python':
477
+ return ['.py'];
478
+ case 'go':
479
+ return ['.go'];
480
+ case 'java':
481
+ return ['.java'];
482
+ case 'rust':
483
+ return ['.rs'];
484
+ default:
485
+ return ['.js', '.ts'];
486
+ }
487
+ }
488
+
489
+ /**
490
+ * Find and load tsconfig.json
491
+ */
492
+ function findTsConfig(fromDir, rootDir) {
493
+ const cacheKey = fromDir;
494
+ if (tsconfigCache.has(cacheKey)) {
495
+ return tsconfigCache.get(cacheKey);
496
+ }
497
+
498
+ let currentDir = fromDir;
499
+ const normalizedRoot = rootDir ? path.resolve(rootDir) : null;
500
+
501
+ while (true) {
502
+ const tsconfigPath = path.join(currentDir, 'tsconfig.json');
503
+ if (fs.existsSync(tsconfigPath)) {
504
+ try {
505
+ const content = fs.readFileSync(tsconfigPath, 'utf-8');
506
+ const cleanJson = stripJsonComments(content);
507
+ const config = JSON.parse(cleanJson);
508
+
509
+ const paths = config.compilerOptions?.paths || {};
510
+ // Pre-compile regex patterns for path aliases to avoid repeated compilation
511
+ const compiledPaths = Object.entries(paths).map(([pattern, targets]) => ({
512
+ pattern,
513
+ regex: new RegExp('^' + pattern.replace('*', '(.*)') + '$'),
514
+ targets
515
+ }));
516
+
517
+ const result = {
518
+ configPath: tsconfigPath,
519
+ baseUrl: config.compilerOptions?.baseUrl
520
+ ? path.resolve(path.dirname(tsconfigPath), config.compilerOptions.baseUrl)
521
+ : null,
522
+ paths,
523
+ compiledPaths
524
+ };
525
+
526
+ tsconfigCache.set(cacheKey, result);
527
+ return result;
528
+ } catch (e) {
529
+ // Skip malformed tsconfig
530
+ }
531
+ }
532
+
533
+ const parent = path.dirname(currentDir);
534
+ if (parent === currentDir) break;
535
+ if (normalizedRoot && !currentDir.startsWith(normalizedRoot)) break;
536
+ currentDir = parent;
537
+ }
538
+
539
+ tsconfigCache.set(cacheKey, null);
540
+ return null;
541
+ }
542
+
543
+ /**
544
+ * Strip JSON comments
545
+ */
546
+ function stripJsonComments(content) {
547
+ return content
548
+ .replace(/\/\*[\s\S]*?\*\//g, '')
549
+ .replace(/\/\/.*/g, '')
550
+ .replace(/,(\s*[}\]])/g, '$1');
551
+ }
552
+
553
+ module.exports = {
554
+ extractImports,
555
+ extractExports,
556
+ resolveImport,
557
+ IMPORT_PATTERNS
558
+ };