entkapp 5.4.0 → 5.6.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 (76) hide show
  1. package/README.md +7 -1
  2. package/bin/cli.js +117 -58
  3. package/bin/cli.mjs +175 -0
  4. package/index.cjs +18 -0
  5. package/index.mjs +51 -0
  6. package/package.json +7 -6
  7. package/src/EngineContext.js +0 -6
  8. package/src/EngineContext.mjs +428 -0
  9. package/src/Initializer.mjs +82 -0
  10. package/src/analyzers/CodeSmellAnalyzer.mjs +106 -0
  11. package/src/analyzers/OxcAnalyzer.mjs +11 -0
  12. package/src/api/HeadlessAPI.js +31 -16
  13. package/src/api/HeadlessAPI.mjs +369 -0
  14. package/src/api/PluginSDK.mjs +135 -0
  15. package/src/ast/ASTAnalyzer.js +23 -97
  16. package/src/ast/ASTAnalyzer.mjs +742 -0
  17. package/src/ast/AdvancedAnalysis.mjs +586 -0
  18. package/src/ast/BarrelParser.mjs +230 -0
  19. package/src/ast/DeadCodeDetector.js +7 -5
  20. package/src/ast/DeadCodeDetector.mjs +92 -0
  21. package/src/ast/MagicDetector.js +43 -23
  22. package/src/ast/MagicDetector.mjs +203 -0
  23. package/src/ast/OxcAnalyzer.js +27 -190
  24. package/src/ast/OxcAnalyzer.mjs +188 -0
  25. package/src/ast/SecretScanner.mjs +374 -0
  26. package/src/healing/GitSandbox.mjs +82 -0
  27. package/src/healing/SelfHealer.mjs +48 -0
  28. package/src/index.js +41 -88
  29. package/src/index.mjs +1176 -0
  30. package/src/performance/GraphCache.js +0 -27
  31. package/src/performance/GraphCache.mjs +108 -0
  32. package/src/performance/SupplyChainGuard.mjs +92 -0
  33. package/src/performance/WorkerPool.js +4 -22
  34. package/src/performance/WorkerPool.mjs +132 -0
  35. package/src/performance/WorkerTaskRunner.js +0 -26
  36. package/src/performance/WorkerTaskRunner.mjs +144 -0
  37. package/src/plugins/BasePlugin.js +27 -16
  38. package/src/plugins/BasePlugin.mjs +240 -0
  39. package/src/plugins/PluginRegistry.js +0 -44
  40. package/src/plugins/PluginRegistry.mjs +203 -0
  41. package/src/plugins/ecosystems/BackendServices.mjs +197 -0
  42. package/src/plugins/ecosystems/GenericPlugins.mjs +142 -0
  43. package/src/plugins/ecosystems/ModernFrameworks.mjs +162 -0
  44. package/src/plugins/ecosystems/MorePlugins.mjs +562 -0
  45. package/src/plugins/ecosystems/NewPlugins.mjs +526 -0
  46. package/src/plugins/ecosystems/NextJsPlugin.mjs +45 -0
  47. package/src/plugins/ecosystems/PluginLoader.mjs +193 -0
  48. package/src/plugins/ecosystems/TypeScriptPlugin.mjs +56 -0
  49. package/src/plugins/ecosystems/UltimateBundle.js +0 -259
  50. package/src/plugins/ecosystems/UltimateBundle.mjs +1182 -0
  51. package/src/refractor/ImpactAnalyzer.mjs +92 -0
  52. package/src/refractor/SourceRewriter.mjs +86 -0
  53. package/src/refractor/TransactionManager.mjs +5 -0
  54. package/src/refractor/TypeIntegrity.mjs +4 -0
  55. package/src/resolution/BuildOrchestrator.mjs +46 -0
  56. package/src/resolution/CircularDetector.mjs +91 -0
  57. package/src/resolution/ConfigGenerator.mjs +83 -0
  58. package/src/resolution/ConfigLoader.js +26 -190
  59. package/src/resolution/ConfigLoader.mjs +94 -0
  60. package/src/resolution/DepencyResolver.mjs +66 -0
  61. package/src/resolution/DependencyFixer.mjs +88 -0
  62. package/src/resolution/DependencyProfiler.mjs +286 -0
  63. package/src/resolution/EntryPointDetector.mjs +134 -0
  64. package/src/resolution/FrameworkConfigParser.mjs +119 -0
  65. package/src/resolution/GraphAnalyzer.js +1 -65
  66. package/src/resolution/GraphAnalyzer.mjs +80 -0
  67. package/src/resolution/MigrationAnalyzer.mjs +60 -0
  68. package/src/resolution/PathMapper.js +0 -81
  69. package/src/resolution/PathMapper.mjs +82 -0
  70. package/src/resolution/TSConfigLoader.js +1 -3
  71. package/src/resolution/TSConfigLoader.mjs +162 -0
  72. package/src/resolution/WorkSpaceGraph.js +89 -260
  73. package/src/resolution/WorkSpaceGraph.mjs +183 -0
  74. package/src/resolution/WorkspaceDiagnostic.mjs +139 -0
  75. package/test.js +76 -0
  76. package/wrangler.toml +6 -0
@@ -0,0 +1,119 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+
4
+ /**
5
+ * Lightweight Static Analysis Parser for Vite / Vitest / Framework Configurations.
6
+ * Extracts 'resolve.alias' and 'build.lib.entry' without executing the file.
7
+ * Version 5.4.0: Added poly-extension path resolution.
8
+ */
9
+ export class FrameworkConfigParser {
10
+ constructor(context) {
11
+ this.context = context;
12
+ }
13
+
14
+ /**
15
+ * Parses a Framework configuration file and extracts structural metadata.
16
+ * @param {string} content - Raw source code of the config file
17
+ * @param {string} filePath - Absolute path to the config file
18
+ * @returns {Object} { aliases: Map<string, string>, entries: Set<string> }
19
+ */
20
+ parse(content, filePath) {
21
+ const results = {
22
+ aliases: new Map(),
23
+ entries: new Set()
24
+ };
25
+
26
+ if (!content) return results;
27
+
28
+ const configDir = path.dirname(filePath);
29
+
30
+ // 1. Extract Aliases from resolve: { alias: { ... } } or alias: [ ... ]
31
+ this._extractAliases(content, configDir, results.aliases);
32
+
33
+ // 2. Extract Entry Points from build: { lib: { entry: '...' } } or rollupOptions: { input: '...' }
34
+ this._extractEntries(content, configDir, results.entries);
35
+
36
+ return results;
37
+ }
38
+
39
+ _extractAliases(content, configDir, aliasMap) {
40
+ const objAliasPatterns = [
41
+ /alias\s*:\s*\{([\s\S]*?)\}/g,
42
+ /resolve\s*:\s*\{[\s\S]*?alias\s*:\s*\{([\s\S]*?)\}/g
43
+ ];
44
+
45
+ for (const pattern of objAliasPatterns) {
46
+ let match;
47
+ while ((match = pattern.exec(content)) !== null) {
48
+ const inner = match[1];
49
+ const pairPattern = /(?:['"]?)(@?[a-zA-Z0-9_\-\/*]+)(?:['"]?)\s*:\s*(?:['"]([^'"]+)['"]|(path\.(?:resolve|join)\([\s\S]*?\)))/g;
50
+ let pair;
51
+ while ((pair = pairPattern.exec(inner)) !== null) {
52
+ const [_, key, stringVal, callVal] = pair;
53
+ aliasMap.set(key, this._resolveValue(stringVal || callVal, configDir));
54
+ }
55
+ }
56
+ }
57
+
58
+ const arrAliasPattern = /alias\s*:\s*\[([\s\S]*?)\]/;
59
+ const arrMatch = arrAliasPattern.exec(content);
60
+ if (arrMatch) {
61
+ const inner = arrMatch[1];
62
+ const findPattern = /\{\s*find\s*:\s*['"]([^'"]+)['"]\s*,\s*replacement\s*:\s*['"]([^'"]+)['"]\s*\}/g;
63
+ let findMatch;
64
+ while ((findMatch = findPattern.exec(inner)) !== null) {
65
+ const [_, key, value] = findMatch;
66
+ aliasMap.set(key, this._resolveValue(value, configDir));
67
+ }
68
+ }
69
+ }
70
+
71
+ _extractEntries(content, configDir, entrySet) {
72
+ const libEntryPattern = /entry\s*:\s*['"]([^'"]+)['"]/;
73
+ const libMatch = libEntryPattern.exec(content);
74
+ if (libMatch) {
75
+ entrySet.add(this._resolveValue(libMatch[1], configDir));
76
+ }
77
+
78
+ const inputPattern = /input\s*:\s*(?:['"]([^'"]+)['"]|\[([\s\S]*?)\])/;
79
+ const inputMatch = inputPattern.exec(content);
80
+ if (inputMatch) {
81
+ if (inputMatch[1]) {
82
+ entrySet.add(this._resolveValue(inputMatch[1], configDir));
83
+ } else if (inputMatch[2]) {
84
+ const paths = inputMatch[2].match(/['"]([^'"]+)['"]/g);
85
+ if (paths) {
86
+ paths.forEach(p => entrySet.add(this._resolveValue(p.replace(/['"]/g, ''), configDir)));
87
+ }
88
+ }
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Resolves a path value and applies poly-extension fallback if the file doesn't exist.
94
+ */
95
+ _resolveValue(val, configDir) {
96
+ const pathCallPattern = /path\.(?:resolve|join)\s*\(\s*(?:__dirname\s*,\s*)?['"]([^'"]+)['"]\s*\)/;
97
+ const pathMatch = pathCallPattern.exec(val);
98
+ if (pathMatch) {
99
+ val = pathMatch[1];
100
+ }
101
+
102
+ if (val.startsWith('.') || val.startsWith('/') || /^[a-zA-Z]:/.test(val)) {
103
+ let resolved = path.resolve(configDir, val).replace(/\\/g, '/');
104
+
105
+ // UPGRADE: Poly-extension fallback
106
+ if (!fs.existsSync(resolved)) {
107
+ const base = resolved.replace(/\.[a-zA-Z0-9]+$/, '');
108
+ const extensions = ['.ts', '.mjs', '.tsx', '.jsx', '.mjs', '.cjs'];
109
+ for (const ext of extensions) {
110
+ if (fs.existsSync(base + ext)) {
111
+ return (base + ext).replace(/\\/g, '/');
112
+ }
113
+ }
114
+ }
115
+ return resolved;
116
+ }
117
+ return val;
118
+ }
119
+ }
@@ -10,36 +10,6 @@ export class GraphAnalyzer {
10
10
  constructor(context) {
11
11
  this.context = context;
12
12
  this.cwd = context.cwd;
13
- // UPGRADE 5.4.3: Default Boundary Rules
14
- this.boundaryRules = context.config?.boundaries || [
15
- { from: 'packages/shared-*', to: 'apps/*', allow: false, message: 'Shared packages must not import from applications.' },
16
- { from: '*', to: '**/internal/**', allow: false, message: 'Private internal utilities must not be imported externally.' }
17
- ];
18
- }
19
-
20
- /**
21
- * UPGRADE 5.4.3: Boundary Enforcement
22
- * Checks if an import violates defined architectural boundaries.
23
- */
24
- checkBoundaries(fromPath, toPath) {
25
- const relFrom = path.relative(this.cwd, fromPath).replace(/\\/g, '/');
26
- const relTo = path.relative(this.cwd, toPath).replace(/\\/g, '/');
27
-
28
- for (const rule of this.boundaryRules) {
29
- const fromMatch = this._globMatch(relFrom, rule.from);
30
- const toMatch = this._globMatch(relTo, rule.to);
31
-
32
- if (fromMatch && toMatch && !rule.allow) {
33
- return { violated: true, message: rule.message };
34
- }
35
- }
36
- return { violated: false };
37
- }
38
-
39
- _globMatch(str, pattern) {
40
- if (pattern === '*') return true;
41
- const regex = new RegExp('^' + pattern.replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*') + '$');
42
- return regex.test(str);
43
13
  }
44
14
 
45
15
  /**
@@ -93,41 +63,7 @@ export class GraphAnalyzer {
93
63
  }
94
64
  }
95
65
 
96
- const unusedImports = [];
97
- const boundaryViolations = [];
98
-
99
- for (const [filePath, node] of graph.entries()) {
100
- if (!reachable.has(filePath)) continue;
101
-
102
- // UPGRADE 5.4.3: Check boundaries for every import
103
- for (const impPath of node.explicitImports) {
104
- if (graph.has(impPath)) {
105
- const violation = this.checkBoundaries(filePath, impPath);
106
- if (violation.violated) {
107
- boundaryViolations.push({
108
- file: path.relative(this.cwd, filePath).replace(/\\/g, '/'),
109
- target: path.relative(this.cwd, impPath).replace(/\\/g, '/'),
110
- message: violation.message
111
- });
112
- }
113
- }
114
- }
115
-
116
- if (node.localImportBindings) {
117
- for (const [localName, meta] of node.localImportBindings.entries()) {
118
- // Check if this local binding is used anywhere in the file
119
- if (!node.instantiatedIdentifiers.has(localName)) {
120
- unusedImports.push({
121
- file: path.relative(this.cwd, filePath).replace(/\\/g, '/'),
122
- specifier: meta.specifier,
123
- symbol: meta.originalName === '*' ? 'Namespace' : meta.originalName
124
- });
125
- }
126
- }
127
- }
128
- }
129
-
130
- return { deadFiles, zombieExports, unusedImports, boundaryViolations };
66
+ return { deadFiles, zombieExports };
131
67
  }
132
68
 
133
69
  _walk(filePath, reachable) {
@@ -0,0 +1,80 @@
1
+ import path from 'path';
2
+
3
+ /**
4
+ * ============================================================================
5
+ * Dead Code & Zombie Export Hunter v5.1.0
6
+ * ============================================================================
7
+ * Identifies unused files and exported symbols that are never imported.
8
+ */
9
+ export class GraphAnalyzer {
10
+ constructor(context) {
11
+ this.context = context;
12
+ this.cwd = context.cwd;
13
+ }
14
+
15
+ /**
16
+ * Performs a full reachability analysis from entry points.
17
+ */
18
+ async findDeadCode() {
19
+ const graph = this.context.projectGraph;
20
+ const reachable = new Set();
21
+ const entries = [];
22
+
23
+ // 1. Identify all confirmed entry points
24
+ for (const [filePath, node] of graph.entries()) {
25
+ if (node.isEntry) {
26
+ entries.push(filePath);
27
+ this._walk(filePath, reachable);
28
+ }
29
+ }
30
+
31
+ const deadFiles = [];
32
+ const zombieExports = [];
33
+
34
+ // 2. Find unreachable files
35
+ for (const filePath of graph.keys()) {
36
+ if (!reachable.has(filePath)) {
37
+ deadFiles.push(path.relative(this.cwd, filePath).replace(/\\/g, '/'));
38
+ } else {
39
+ // 3. Find Zombie Exports in reachable files
40
+ const node = graph.get(filePath);
41
+ if (node.isEntry) continue; // Skip entries as their exports are intended for external use
42
+
43
+ for (const [symbol, meta] of node.internalExports.entries()) {
44
+ if (symbol === 'default' || symbol === '*') continue;
45
+
46
+ let isUsed = false;
47
+ // Check if any other node imports this symbol
48
+ for (const [otherPath, otherNode] of graph.entries()) {
49
+ if (otherNode.importedSymbols && otherNode.importedSymbols.has(symbol)) {
50
+ // This is a simplified check; in reality, we'd check if it's imported FROM this file
51
+ isUsed = true;
52
+ break;
53
+ }
54
+ }
55
+
56
+ if (!isUsed) {
57
+ zombieExports.push({
58
+ file: path.relative(this.cwd, filePath).replace(/\\/g, '/'),
59
+ symbol: symbol
60
+ });
61
+ }
62
+ }
63
+ }
64
+ }
65
+
66
+ return { deadFiles, zombieExports };
67
+ }
68
+
69
+ _walk(filePath, reachable) {
70
+ if (reachable.has(filePath)) return;
71
+ reachable.add(filePath);
72
+
73
+ const node = this.context.projectGraph.get(filePath);
74
+ if (!node) return;
75
+
76
+ for (const impPath of node.explicitImports) {
77
+ this._walk(impPath, reachable);
78
+ }
79
+ }
80
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * ============================================================================
3
+ * Migration Path Analyzer v5.1.0
4
+ * ============================================================================
5
+ * Suggests modern alternatives for legacy tools and frameworks.
6
+ */
7
+ export class MigrationAnalyzer {
8
+ constructor(context) {
9
+ this.context = context;
10
+ this.migrations = [
11
+ {
12
+ legacy: 'webpack',
13
+ modern: 'vite',
14
+ reason: 'Vite offers significantly faster HMR and build times using ES modules during development.'
15
+ },
16
+ {
17
+ legacy: 'jest',
18
+ modern: 'vitest',
19
+ reason: 'Vitest is faster, has native ESM support, and shares configuration with Vite.'
20
+ },
21
+ {
22
+ legacy: 'moment',
23
+ modern: 'dayjs',
24
+ reason: 'Day.js is a 2KB alternative to Moment.js with the same modern API.'
25
+ },
26
+ {
27
+ legacy: 'axios',
28
+ modern: 'fetch',
29
+ reason: 'Native fetch is now widely supported and requires no external dependency for simple use cases.'
30
+ },
31
+ {
32
+ legacy: 'ts-node',
33
+ modern: 'tsx',
34
+ reason: 'tsx is faster and has better ESM support for running TypeScript files.'
35
+ },
36
+ {
37
+ legacy: 'eslint-plugin-prettier',
38
+ modern: 'eslint-config-prettier',
39
+ reason: 'Running Prettier as an ESLint rule is slow. It is recommended to run them separately or use a config that turns off conflicting rules.'
40
+ }
41
+ ];
42
+ }
43
+
44
+ async analyze(activePlugins) {
45
+ const suggestions = [];
46
+ const activeNames = activePlugins.map(p => p.name);
47
+
48
+ for (const m of this.migrations) {
49
+ if (activeNames.includes(m.legacy)) {
50
+ suggestions.push({
51
+ from: m.legacy,
52
+ to: m.modern,
53
+ message: `Migration Suggestion: Consider moving from "${m.legacy}" to "${m.modern}". ${m.reason}`
54
+ });
55
+ }
56
+ }
57
+
58
+ return suggestions;
59
+ }
60
+ }
@@ -6,22 +6,15 @@ export class PathMapper {
6
6
  constructor(context) {
7
7
  this.context = context;
8
8
  this.aliasMappers = []; // list of alias mapping functions
9
- // UPGRADE: Raw alias patterns for fast "is this an alias?" checks without file-existence tests
10
- this._aliasPatterns = []; // Array of { regex }
11
9
  }
12
10
 
13
11
  async loadMappings(tsconfigFilename = 'tsconfig.json') {
14
- // UPGRADE: Reset on reload to avoid duplicate entries when called twice (workspace re-load)
15
- this.aliasMappers = [];
16
- this._aliasPatterns = [];
17
-
18
12
  // Load root tsconfig
19
13
  const loader = new TSConfigLoader(this.context.cwd);
20
14
  const config = loader.load();
21
15
  if (config) {
22
16
  const mapper = loader.getAliasMapper(config);
23
17
  this.aliasMappers.push(mapper);
24
- this._collectAliasPatterns(config, loader);
25
18
  if (this.context.verbose) console.log(`[PathMapper] Loaded root tsconfig aliases`);
26
19
  }
27
20
 
@@ -33,86 +26,12 @@ export class PathMapper {
33
26
  if (wsConfig) {
34
27
  const wsMapper = wsLoader.getAliasMapper(wsConfig);
35
28
  this.aliasMappers.push(wsMapper);
36
- this._collectAliasPatterns(wsConfig, wsLoader);
37
29
  if (this.context.verbose) console.log(`[PathMapper] Loaded workspace tsconfig aliases from ${root}`);
38
30
  }
39
31
  }
40
32
  }
41
33
  }
42
34
 
43
- /**
44
- * UPGRADE: Collect raw alias patterns from a parsed tsconfig so we can answer
45
- * "is this specifier a tsconfig alias?" without needing the target file to exist on disk.
46
- * Handles patterns like "@shared/*", "@idk/*", "~/*", etc.
47
- * @param {Object} parsedConfig - Result of TSConfigLoader.load()
48
- * @param {TSConfigLoader} loader
49
- */
50
- _collectAliasPatterns(parsedConfig, loader) {
51
- if (!parsedConfig || !parsedConfig.options) return;
52
- const { paths, baseUrl } = parsedConfig.options;
53
-
54
- if (paths) {
55
- for (const pattern in paths) {
56
- // Convert tsconfig glob pattern to a regex
57
- // e.g. "@shared/*" -> /^@shared\// or "@shared" -> /^@shared$/
58
- const escaped = pattern
59
- .replace(/[.+^${}()|[\]\\]/g, '\\$&') // escape regex special chars except *
60
- .replace(/\\\*/g, '.*'); // replace escaped \* with .*
61
- try {
62
- this._aliasPatterns.push({ regex: new RegExp('^' + escaped + '$') });
63
- // Also add a prefix variant so "@shared/foo/bar" matches "@shared/*"
64
- const prefixEscaped = pattern.replace(/\*.*$/, '').replace(/[.+^${}()|[\]\\]/g, '\\$&');
65
- if (prefixEscaped && prefixEscaped !== escaped) {
66
- this._aliasPatterns.push({ regex: new RegExp('^' + prefixEscaped) });
67
- }
68
- } catch (e) { /* ignore invalid regex */ }
69
- }
70
- }
71
- }
72
-
73
- /**
74
- * UPGRADE: Returns true if the given specifier matches any tsconfig path alias pattern.
75
- * This check does NOT require the target file to exist on disk.
76
- * Used by the unlisted-dependency audit to skip tsconfig-aliased imports.
77
- * @param {string} specifier
78
- * @returns {boolean}
79
- */
80
- isTsconfigAlias(specifier) {
81
- if (!specifier || typeof specifier !== 'string') return false;
82
- for (const { regex } of this._aliasPatterns) {
83
- if (regex.test(specifier)) return true;
84
- }
85
- return false;
86
- }
87
-
88
- /**
89
- * UPGRADE: Manually register a dynamic alias (e.g. from vite.config.js).
90
- * @param {string} key - Alias key (e.g. "@shared")
91
- * @param {string} target - Absolute path or relative path to resolve
92
- */
93
- addAlias(key, target) {
94
- if (!key || !target) return;
95
-
96
- // 1. Add to aliasMappers for resolvePath()
97
- const mapper = (p) => {
98
- if (p === key) return target;
99
- if (p.startsWith(key + '/')) {
100
- return path.join(target, p.substring(key.length + 1)).replace(/\\/g, '/');
101
- }
102
- return p;
103
- };
104
- this.aliasMappers.push(mapper);
105
-
106
- // 2. Add to _aliasPatterns for isTsconfigAlias() checks
107
- const escaped = key.replace(/[.+^${}()|[\]\\]/g, '\\$&');
108
- try {
109
- this._aliasPatterns.push({ regex: new RegExp('^' + escaped + '$') });
110
- this._aliasPatterns.push({ regex: new RegExp('^' + escaped + '/') });
111
- } catch (e) {}
112
-
113
- if (this.context.verbose) console.log(`[PathMapper] Added dynamic alias: ${key} -> ${target}`);
114
- }
115
-
116
35
  /**
117
36
  * Resolves physical module paths on disk, translating modern .js imports
118
37
  * back to their actual TypeScript source files.
@@ -0,0 +1,82 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { TSConfigLoader } from './TSConfigLoader.mjs';
4
+
5
+ export class PathMapper {
6
+ constructor(context) {
7
+ this.context = context;
8
+ this.aliasMappers = []; // list of alias mapping functions
9
+ }
10
+
11
+ async loadMappings(tsconfigFilename = 'tsconfig.json') {
12
+ // Load root tsconfig
13
+ const loader = new TSConfigLoader(this.context.cwd);
14
+ const config = loader.load();
15
+ if (config) {
16
+ const mapper = loader.getAliasMapper(config);
17
+ this.aliasMappers.push(mapper);
18
+ if (this.context.verbose) console.log(`[PathMapper] Loaded root tsconfig aliases`);
19
+ }
20
+
21
+ // Load workspace tsconfigs if available
22
+ if (this.context.isWorkspaceEnabled && this.context.monorepoPackageRoots) {
23
+ for (const root of this.context.monorepoPackageRoots) {
24
+ const wsLoader = new TSConfigLoader(root);
25
+ const wsConfig = wsLoader.load();
26
+ if (wsConfig) {
27
+ const wsMapper = wsLoader.getAliasMapper(wsConfig);
28
+ this.aliasMappers.push(wsMapper);
29
+ if (this.context.verbose) console.log(`[PathMapper] Loaded workspace tsconfig aliases from ${root}`);
30
+ }
31
+ }
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Resolves physical module paths on disk, translating modern .js imports
37
+ * back to their actual TypeScript source files.
38
+ * @param {string} p - The target module specifier or absolute path
39
+ */
40
+ resolvePath(p) {
41
+ if (!p || typeof p !== 'string') return p;
42
+
43
+ let resolvedP = p;
44
+
45
+ // Try alias mappers first
46
+ for (const mapper of this.aliasMappers) {
47
+ const mapped = mapper(resolvedP);
48
+ if (mapped !== resolvedP && fs.existsSync(mapped)) {
49
+ resolvedP = mapped;
50
+ break;
51
+ }
52
+ }
53
+
54
+ // FIX 1: If the import ends with .js, translate it to .ts for the search
55
+ if (resolvedP.endsWith('.mjs')) {
56
+ const tsPath = resolvedP.slice(0, -3) + '.ts';
57
+ if (fs.existsSync(tsPath)) return tsPath;
58
+ }
59
+
60
+ // FIX 2: If the import ends with .jsx, translate it to .tsx for the search
61
+ if (resolvedP.endsWith('.jsx')) {
62
+ const tsxPath = resolvedP.slice(0, -4) + '.tsx';
63
+ if (fs.existsSync(tsxPath)) return tsxPath;
64
+ }
65
+
66
+ // FIX 3: Support for directory imports (z.B. ./adapters -> ./adapters/index.ts)
67
+ try {
68
+ const stat = fs.statSync(resolvedP);
69
+ if (stat.isDirectory()) {
70
+ const extensions = ['.ts', '.tsx', '.mjs', '.jsx'];
71
+ for (const ext of extensions) {
72
+ const indexPath = path.join(resolvedP, `index${ext}`);
73
+ if (fs.existsSync(indexPath)) return indexPath;
74
+ }
75
+ }
76
+ } catch {
77
+ // File does not exist or is not a directory, continue with default
78
+ }
79
+
80
+ return resolvedP;
81
+ }
82
+ }
@@ -109,9 +109,7 @@ export class TSConfigLoader {
109
109
  }
110
110
 
111
111
  const { paths, baseUrl } = parsedConfig.options;
112
- // In ts.parseJsonConfigFileContent, baseUrl is already resolved to an absolute path if it was present
113
- // If not, we fall back to targetDir
114
- const base = baseUrl ? baseUrl : this.targetDir;
112
+ const base = baseUrl ? path.resolve(this.targetDir, baseUrl) : this.targetDir;
115
113
 
116
114
  return (source) => {
117
115
  // If no paths configured, still try baseUrl resolution