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,162 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import ts from 'typescript';
4
+
5
+ export class TSConfigLoader {
6
+ constructor(targetDir) {
7
+ this.targetDir = targetDir;
8
+ }
9
+
10
+ /**
11
+ * Lädt und parst die tsconfig.json.
12
+ * Unterstützt JSONC (Kommentare), extends, und project references.
13
+ * @param {string} [filename='tsconfig.json'] - Name der tsconfig-Datei
14
+ * @returns {Object|null} Parsed config oder null.
15
+ */
16
+ load(filename = 'tsconfig.json') {
17
+ const configPath = path.join(this.targetDir, filename);
18
+ if (!fs.existsSync(configPath)) return null;
19
+
20
+ try {
21
+ const content = fs.readFileSync(configPath, 'utf8');
22
+ const readResult = ts.readConfigFile(configPath, ts.sys.readFile);
23
+
24
+ if (readResult.error) {
25
+ return null;
26
+ }
27
+
28
+ const parsed = ts.parseJsonConfigFileContent(
29
+ readResult.config,
30
+ ts.sys,
31
+ this.targetDir
32
+ );
33
+
34
+ // Attach raw config for reference inspection
35
+ parsed._rawConfig = readResult.config;
36
+
37
+ return parsed;
38
+ } catch (e) {
39
+ return null;
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Extracts project references from tsconfig (composite monorepo support).
45
+ * @returns {string[]} Array of referenced tsconfig paths
46
+ */
47
+ getProjectReferences(filename = 'tsconfig.json') {
48
+ const configPath = path.join(this.targetDir, filename);
49
+ if (!fs.existsSync(configPath)) return [];
50
+
51
+ try {
52
+ const content = fs.readFileSync(configPath, 'utf8');
53
+ // Strip comments
54
+ const stripped = content.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
55
+ const raw = JSON.parse(stripped);
56
+ const refs = raw.references || [];
57
+ return refs.map(ref => {
58
+ const refPath = path.resolve(this.targetDir, ref.path);
59
+ // If it's a directory, look for tsconfig.json inside
60
+ if (fs.existsSync(refPath) && fs.statSync(refPath).isDirectory()) {
61
+ return path.join(refPath, 'tsconfig.json');
62
+ }
63
+ return refPath;
64
+ });
65
+ } catch (e) {
66
+ return [];
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Extracts the "extends" chain from tsconfig.
72
+ * @returns {string[]} Array of extended tsconfig paths
73
+ */
74
+ getExtendsChain(filename = 'tsconfig.json') {
75
+ const configPath = path.join(this.targetDir, filename);
76
+ if (!fs.existsSync(configPath)) return [];
77
+
78
+ const chain = [];
79
+ let currentPath = configPath;
80
+
81
+ for (let i = 0; i < 10; i++) { // limit depth to avoid infinite loops
82
+ try {
83
+ const content = fs.readFileSync(currentPath, 'utf8');
84
+ const stripped = content.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
85
+ const raw = JSON.parse(stripped);
86
+ if (!raw.extends) break;
87
+ const extendedPath = path.resolve(path.dirname(currentPath), raw.extends);
88
+ const resolvedPath = extendedPath.endsWith('.json') ? extendedPath : extendedPath + '.json';
89
+ if (!fs.existsSync(resolvedPath)) break;
90
+ chain.push(resolvedPath);
91
+ currentPath = resolvedPath;
92
+ } catch (e) {
93
+ break;
94
+ }
95
+ }
96
+
97
+ return chain;
98
+ }
99
+
100
+ /**
101
+ * Erstellt eine Mapping-Funktion für Aliases aus der tsconfig.
102
+ * Berücksichtigt baseUrl, paths und project references.
103
+ * @param {Object} parsedConfig
104
+ * @returns {Function} Mapper function.
105
+ */
106
+ getAliasMapper(parsedConfig) {
107
+ if (!parsedConfig || !parsedConfig.options) {
108
+ return (source) => source;
109
+ }
110
+
111
+ const { paths, baseUrl } = parsedConfig.options;
112
+ const base = baseUrl ? path.resolve(this.targetDir, baseUrl) : this.targetDir;
113
+
114
+ return (source) => {
115
+ // If no paths configured, still try baseUrl resolution
116
+ if (!paths) {
117
+ if (baseUrl && !source.startsWith('.') && !source.startsWith('/') && !source.startsWith('@')) {
118
+ const candidate = path.resolve(base, source);
119
+ const extensions = ['', '.ts', '.tsx', '.mjs', '.jsx'];
120
+ for (const ext of extensions) {
121
+ if (fs.existsSync(candidate + ext)) return candidate + ext;
122
+ }
123
+ }
124
+ return source;
125
+ }
126
+
127
+ for (const pattern in paths) {
128
+ const regexPattern = pattern.replace(/\*/g, '(.*)');
129
+ const regex = new RegExp(`^${regexPattern}$`);
130
+ const match = source.match(regex);
131
+
132
+ if (match) {
133
+ const replacements = paths[pattern];
134
+ for (const replacement of replacements) {
135
+ const resolvedReplacement = replacement.replace(/\*/g, match[1] || '');
136
+ const fullPath = path.resolve(base, resolvedReplacement);
137
+
138
+ // Check with common extensions
139
+ const extensions = ['', '.ts', '.tsx', '.mjs', '.jsx', '/index.ts', '/index.tsx', '/index.mjs'];
140
+ for (const ext of extensions) {
141
+ if (fs.existsSync(fullPath + ext)) {
142
+ return fullPath + ext;
143
+ }
144
+ }
145
+ }
146
+ }
147
+ }
148
+ return source;
149
+ };
150
+ }
151
+
152
+ /**
153
+ * Returns all include/exclude glob patterns from tsconfig.
154
+ */
155
+ getIncludeExcludePatterns(parsedConfig) {
156
+ if (!parsedConfig || !parsedConfig._rawConfig) return { include: [], exclude: [] };
157
+ return {
158
+ include: parsedConfig._rawConfig.include || [],
159
+ exclude: parsedConfig._rawConfig.exclude || []
160
+ };
161
+ }
162
+ }
@@ -1,208 +1,82 @@
1
1
  import fs from 'fs/promises';
2
2
  import path from 'path';
3
3
 
4
- /**
5
- * WorkspaceGraph
6
- *
7
- * Discovers and maps all packages in a monorepo/workspace setup.
8
- * Supported workspace manifests (in priority order):
9
- * 1. pnpm-workspace.yaml (pnpm)
10
- * 2. package.json "workspaces" field (npm / yarn / bun)
11
- * 3. lerna.json (Lerna)
12
- * 4. workspace.yaml (generic / custom toolchains)
13
- */
14
4
  export class WorkspaceGraph {
15
- constructor(context) {
16
- this.context = context;
17
-
18
- /** @type {Map<string, Object>} dirPath -> manifest data */
19
- this.packageManifests = new Map();
20
-
21
- /** @type {Map<string, string>} packageName -> dirPath */
22
- this.workspacePackages = new Map();
23
-
24
- /** @type {Map<string, Object>} packageName -> parsed tsconfig data */
25
- this.tsconfigPaths = new Map();
26
-
27
- /**
28
- * Per-package framework / tool config files.
29
- * @type {Map<string, Array<{fileName: string, filePath: string, content: string}>>}
30
- */
31
- this.packageConfigFiles = new Map();
32
-
33
- /**
34
- * Global pipeline / monorepo configs (turbo.json, nx.json, etc.)
35
- * @type {Map<string, Object>} fileName -> parsed content
36
- */
37
- this.monorepoConfigs = new Map();
5
+ constructor(context) {
6
+ this.context = context;
7
+ this.packageManifests = new Map(); // dirPath -> manifestData
8
+ this.workspacePackages = new Map(); // packageName -> dirPath
9
+ this.tsconfigPaths = new Map(); // packageName -> tsconfigData
38
10
  }
39
11
 
40
12
  async initializeWorkspaceMesh() {
41
- let workspaces = [];
42
-
43
- // 1. pnpm-workspace.yaml
44
- workspaces = await this._parseWorkspaceYaml('pnpm-workspace.yaml');
45
-
46
- // 2. package.json "workspaces"
47
- if (workspaces.length === 0) {
48
- try {
49
- const rootPkgPath = path.join(this.context.cwd, 'package.json');
50
- const rootPkg = JSON.parse(await fs.readFile(rootPkgPath, 'utf8'));
51
- if (rootPkg.workspaces) {
52
- workspaces = Array.isArray(rootPkg.workspaces)
53
- ? rootPkg.workspaces
54
- : rootPkg.workspaces.packages || [];
55
- }
56
- } catch (e) {}
57
- }
58
-
59
- // 3. lerna.json
60
- if (workspaces.length === 0) {
61
- try {
62
- const lernaPath = path.join(this.context.cwd, 'lerna.json');
63
- const lerna = JSON.parse(await fs.readFile(lernaPath, 'utf8'));
64
- workspaces = lerna.packages || [];
65
- } catch (e) {}
66
- }
67
-
68
- // 4. workspace.yaml
69
- if (workspaces.length === 0) {
70
- workspaces = await this._parseWorkspaceYaml('workspace.yaml');
71
- }
72
-
73
- await this._loadGlobalMonorepoConfigs();
74
-
75
- if (workspaces.length === 0) return;
76
-
77
- this.context.isWorkspaceEnabled = true;
78
- if (this.context.verbose) {
79
- console.log('[Workspace] Detected workspace patterns:', workspaces);
80
- }
81
-
82
- for (const pattern of workspaces) {
83
- const matches = await this._expandGlob(pattern, this.context.cwd);
84
- for (const matchDir of matches) {
85
- await this._loadPackageDir(matchDir);
86
- }
87
- }
88
- }
89
-
90
- async _loadGlobalMonorepoConfigs() {
91
- const configFiles = ['turbo.json', 'nx.json'];
92
- for (const file of configFiles) {
93
- try {
94
- const filePath = path.join(this.context.cwd, file);
95
- const content = JSON.parse(await fs.readFile(filePath, 'utf8'));
96
- this.monorepoConfigs.set(file, content);
97
- } catch (e) {}
98
- }
99
- }
100
-
101
- async _parseWorkspaceYaml(fileName) {
102
- const yamlPath = path.join(this.context.cwd, fileName);
13
+ const rootPkgPath = path.join(this.context.cwd, 'package.json');
103
14
  try {
104
- const raw = await fs.readFile(yamlPath, 'utf8');
105
- const patterns = [];
106
- const lines = raw.split('\n');
107
- let inPackages = false;
108
- for (const line of lines) {
109
- const trimmed = line.trim();
110
- if (!trimmed || trimmed.startsWith('#')) continue;
111
- if (/^packages\s*:/.test(trimmed)) {
112
- inPackages = true;
113
- const inlineMatch = trimmed.match(/^packages\s*:\s*\[([^\]]*)\]/);
114
- if (inlineMatch) {
115
- inPackages = false;
116
- for (const item of inlineMatch[1].split(',')) {
117
- const val = item.trim().replace(/^['"]|['"]$/g, '');
118
- if (val) patterns.push(val);
119
- }
120
- }
121
- continue;
122
- }
123
- if (inPackages) {
124
- if (line.match(/^[a-zA-Z0-9_-]/) && trimmed.endsWith(':')) {
125
- inPackages = false;
126
- continue;
15
+ const rootPkg = JSON.parse(await fs.readFile(rootPkgPath, 'utf8'));
16
+ let workspaces = [];
17
+
18
+ // 1. Detect Workspaces (npm/yarn/pnpm/lerna)
19
+ if (rootPkg.workspaces) {
20
+ workspaces = Array.isArray(rootPkg.workspaces) ? rootPkg.workspaces : rootPkg.workspaces.packages || [];
21
+ } else {
22
+ // Fallback for pnpm-workspace.yaml
23
+ const pnpmWorkspacePath = path.join(this.context.cwd, 'pnpm-workspace.yaml');
24
+ try {
25
+ const yaml = await fs.readFile(pnpmWorkspacePath, 'utf8');
26
+ const match = yaml.match(/packages:\n((?:\s+- .+\n?)+)/);
27
+ if (match) {
28
+ workspaces = match[1].split('\n')
29
+ .filter(line => line.trim().startsWith('-'))
30
+ .map(line => line.replace('-', '').trim().replace(/['"]/g, ''));
127
31
  }
128
- if (trimmed.startsWith('-')) {
129
- const val = trimmed.substring(1).trim().replace(/^['"]|['"]$/g, '');
130
- if (val) patterns.push(val);
32
+ } catch (e) {}
33
+ }
34
+
35
+ if (workspaces.length > 0) {
36
+ this.context.isWorkspaceEnabled = true;
37
+ if (this.context.verbose) console.log(`[Workspace] Detected workspaces:`, workspaces);
38
+
39
+ for (const pattern of workspaces) {
40
+ const matches = await this._expandGlob(pattern, this.context.cwd);
41
+ for (const matchDir of matches) {
42
+ const pkgPath = path.join(matchDir, 'package.json');
43
+ try {
44
+ const pkgData = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
45
+ const normalizedDir = matchDir.replace(/\\/g, '/');
46
+
47
+ this.packageManifests.set(normalizedDir, {
48
+ rootDirectory: normalizedDir,
49
+ manifestPath: pkgPath.replace(/\\/g, '/'),
50
+ name: pkgData.name,
51
+ dependencies: pkgData.dependencies || {},
52
+ devDependencies: pkgData.devDependencies || {},
53
+ peerDependencies: pkgData.peerDependencies || {},
54
+ scripts: pkgData.scripts || {},
55
+ entryPoints: this.calculatePackageExportsEntries(pkgData, normalizedDir)
56
+ });
57
+
58
+ if (pkgData.name) {
59
+ this.workspacePackages.set(pkgData.name, normalizedDir);
60
+ this.context.monorepoPackageRoots.add(normalizedDir);
61
+ }
62
+
63
+ // Also try to load tsconfig.json for this workspace
64
+ const tsconfigPath = path.join(matchDir, 'tsconfig.json');
65
+ try {
66
+ const tsconfigData = JSON.parse(await fs.readFile(tsconfigPath, 'utf8'));
67
+ if (pkgData.name) this.tsconfigPaths.set(pkgData.name, tsconfigData);
68
+ } catch(e) {}
69
+
70
+ } catch (e) {}
131
71
  }
132
72
  }
133
73
  }
134
- return patterns;
135
74
  } catch (e) {
136
- return [];
75
+ if (this.context.verbose) console.log('[Workspace] No root package.json found or invalid.');
137
76
  }
138
77
  }
139
78
 
140
- async _loadPackageDir(matchDir) {
141
- const normalizedDir = matchDir.replace(/\\/g, '/');
142
- const pkgPath = path.join(matchDir, 'package.json');
143
- let pkgData = null;
144
-
145
- try {
146
- pkgData = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
147
- } catch (e) {
148
- // UPGRADE: If no package.json, it's not a package – skip silently
149
- return;
150
- }
151
-
152
- const packageName = pkgData.name || path.basename(matchDir);
153
-
154
- this.packageManifests.set(normalizedDir, {
155
- rootDirectory: normalizedDir,
156
- manifestPath: pkgPath.replace(/\\/g, '/'),
157
- name: packageName,
158
- version: pkgData.version,
159
- dependencies: pkgData.dependencies || {},
160
- devDependencies: pkgData.devDependencies || {},
161
- peerDependencies: pkgData.peerDependencies || {},
162
- scripts: pkgData.scripts || {},
163
- entryPoints: await this.calculatePackageExportsEntries(pkgData, normalizedDir)
164
- });
165
-
166
- this.workspacePackages.set(packageName, normalizedDir);
167
- this.context.monorepoPackageRoots.add(normalizedDir);
168
-
169
- // tsconfig.json
170
- const tsconfigPath = path.join(matchDir, 'tsconfig.json');
171
- try {
172
- const tsconfigRaw = await fs.readFile(tsconfigPath, 'utf8');
173
- const stripped = tsconfigRaw.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
174
- const tsconfigData = JSON.parse(stripped);
175
- this.tsconfigPaths.set(packageName, tsconfigData);
176
- this.tsconfigPaths.set(normalizedDir, tsconfigData);
177
- } catch (e) {}
178
-
179
- await this._loadPackageConfigFiles(matchDir, normalizedDir, packageName);
180
- }
181
-
182
- async _loadPackageConfigFiles(packageDir, normalizedDir, packageName) {
183
- const configEntries = [];
184
- let dirEntries;
185
- try {
186
- dirEntries = await fs.readdir(packageDir, { withFileTypes: true });
187
- } catch (e) { return; }
188
-
189
- for (const entry of dirEntries) {
190
- if (!entry.isFile()) continue;
191
- const isConfig = /\.config\.(ts|js|mjs|cjs)$/.test(entry.name);
192
- const isJson = entry.name === 'package.json' || entry.name === 'tsconfig.json';
193
-
194
- if (!isConfig && !isJson) continue;
195
-
196
- const filePath = path.join(packageDir, entry.name).replace(/\\/g, '/');
197
- try {
198
- const content = await fs.readFile(filePath, 'utf8');
199
- configEntries.push({ fileName: entry.name, filePath, content });
200
- } catch (e) {}
201
- }
202
- if (configEntries.length > 0) this.packageConfigFiles.set(normalizedDir, configEntries);
203
- }
204
-
205
- async calculatePackageExportsEntries(pkgData, dirPath) {
79
+ calculatePackageExportsEntries(pkgData, dirPath) {
206
80
  const entries = [];
207
81
  const addEntry = (p) => {
208
82
  if (typeof p === 'string') entries.push(path.resolve(dirPath, p).replace(/\\/g, '/'));
@@ -213,7 +87,7 @@ export class WorkspaceGraph {
213
87
  if (pkgData.source) addEntry(pkgData.source);
214
88
  if (pkgData.types) addEntry(pkgData.types);
215
89
  if (pkgData.typings) addEntry(pkgData.typings);
216
-
90
+
217
91
  if (pkgData.bin) {
218
92
  if (typeof pkgData.bin === 'string') addEntry(pkgData.bin);
219
93
  else Object.values(pkgData.bin).forEach(addEntry);
@@ -221,33 +95,23 @@ export class WorkspaceGraph {
221
95
 
222
96
  if (pkgData.exports) {
223
97
  const traverseExports = (obj) => {
224
- if (typeof obj === 'string') addEntry(obj);
225
- else if (typeof obj === 'object' && obj !== null) {
98
+ if (typeof obj === 'string') {
99
+ addEntry(obj);
100
+ } else if (typeof obj === 'object' && obj !== null) {
226
101
  for (const key in obj) traverseExports(obj[key]);
227
102
  }
228
103
  };
229
104
  traverseExports(pkgData.exports);
230
105
  }
231
106
 
232
- // UPGRADE: If no explicit entry points found, look for standard index files (poly-extension)
233
- if (entries.length === 0) {
234
- const standardIndexFiles = ['index.js', 'index.ts', 'index.jsx', 'index.tsx', 'index.mjs', 'index.cjs'];
235
- for (const fileName of standardIndexFiles) {
236
- const fullPath = path.join(dirPath, fileName);
237
- try {
238
- await fs.access(fullPath);
239
- addEntry(fileName);
240
- break; // Take the first one found
241
- } catch {}
242
- }
243
- }
244
-
245
107
  return entries;
246
108
  }
247
109
 
248
110
  isLocalWorkspaceSpecifier(specifier) {
249
111
  if (!specifier) return false;
112
+ // Direct match
250
113
  if (this.workspacePackages.has(specifier)) return true;
114
+ // Sub-path match (e.g. @my-org/ui/components)
251
115
  for (const pkgName of this.workspacePackages.keys()) {
252
116
  if (specifier.startsWith(pkgName + '/')) return true;
253
117
  }
@@ -269,86 +133,51 @@ export class WorkspaceGraph {
269
133
  }
270
134
 
271
135
  markWorkspacePackagesAsUsed() {
272
- for (const [pkgName] of this.workspacePackages.entries()) {
136
+ for (const [pkgName, dirPath] of this.workspacePackages.entries()) {
273
137
  this.context.usedExternalPackages.add(pkgName);
274
138
  }
275
139
  }
276
140
 
277
- async findFrameworkConfigs() {
278
- const configFiles = new Set();
279
- await this._scanDirForConfigs(this.context.cwd, configFiles);
280
- for (const [dir, entries] of this.packageConfigFiles.entries()) {
281
- for (const { fileName, filePath } of entries) {
282
- if (/\.config\.(ts|js|mjs|cjs)$/.test(fileName)) configFiles.add(filePath);
283
- }
284
- }
285
- for (const root of this.context.monorepoPackageRoots) {
286
- const normalizedRoot = root.replace(/\\/g, '/');
287
- if (!this.packageConfigFiles.has(normalizedRoot)) await this._scanDirForConfigs(root, configFiles);
288
- }
289
- return Array.from(configFiles);
290
- }
291
-
292
- async _scanDirForConfigs(dir, configFiles) {
293
- try {
294
- const entries = await fs.readdir(dir, { withFileTypes: true });
295
- for (const entry of entries) {
296
- if (entry.isFile() && entry.name.includes('.config.') && /\.(ts|js|mjs|cjs)$/.test(entry.name)) {
297
- configFiles.add(path.join(dir, entry.name).replace(/\\/g, '/'));
298
- }
299
- }
300
- } catch (e) {}
301
- }
302
-
303
- getPackageConfigFiles(dirOrName) {
304
- const normalized = dirOrName.replace(/\\/g, '/');
305
- if (this.packageConfigFiles.has(normalized)) return this.packageConfigFiles.get(normalized);
306
- const dir = this.workspacePackages.get(dirOrName);
307
- if (dir) return this.packageConfigFiles.get(dir) || [];
308
- return [];
309
- }
310
-
311
- getPackageTsConfig(dirOrName) {
312
- const normalized = dirOrName.replace(/\\/g, '/');
313
- if (this.tsconfigPaths.has(normalized)) return this.tsconfigPaths.get(normalized);
314
- const dir = this.workspacePackages.get(dirOrName);
315
- if (dir) return this.tsconfigPaths.get(dir);
316
- return undefined;
317
- }
318
-
319
- getSummary() {
320
- return {
321
- packages: this.packageManifests.size,
322
- tsconfigsLoaded: this.tsconfigPaths.size,
323
- configFilesLoaded: Array.from(this.packageConfigFiles.values()).reduce((sum, arr) => sum + arr.length, 0),
324
- monorepoConfigs: Array.from(this.monorepoConfigs.keys()),
325
- packageNames: Array.from(this.workspacePackages.keys())
326
- };
327
- }
328
-
141
+ /**
142
+ * Expands a workspace glob pattern (e.g. 'packages/*') to absolute directory paths.
143
+ * Supports single-level wildcards and direct paths.
144
+ */
329
145
  async _expandGlob(pattern, cwd) {
330
146
  const results = [];
147
+
148
+ // Remove trailing slash
331
149
  const cleanPattern = pattern.replace(/\/$/, '');
150
+
151
+ // Handle simple wildcard patterns like 'packages/*' or 'apps/*'
332
152
  if (cleanPattern.includes('*')) {
333
153
  const parts = cleanPattern.split('/');
334
154
  const wildcardIndex = parts.findIndex(p => p.includes('*'));
335
155
  const baseDir = path.join(cwd, ...parts.slice(0, wildcardIndex));
156
+
336
157
  try {
337
158
  const entries = await fs.readdir(baseDir, { withFileTypes: true });
338
159
  for (const entry of entries) {
339
160
  if (entry.isDirectory() && !entry.name.startsWith('.')) {
340
161
  const fullPath = path.join(baseDir, entry.name);
341
- results.push(fullPath.replace(/\\/g, '/'));
162
+ // Check if it has a package.json
163
+ try {
164
+ await fs.access(path.join(fullPath, 'package.json'));
165
+ results.push(fullPath.replace(/\\/g, '/'));
166
+ } catch (e) {}
342
167
  }
343
168
  }
344
169
  } catch (e) {}
345
170
  } else {
171
+ // Direct path
346
172
  const fullPath = path.resolve(cwd, cleanPattern);
347
173
  try {
348
174
  const stat = await fs.stat(fullPath);
349
- if (stat.isDirectory()) results.push(fullPath.replace(/\\/g, '/'));
175
+ if (stat.isDirectory()) {
176
+ results.push(fullPath.replace(/\\/g, '/'));
177
+ }
350
178
  } catch (e) {}
351
179
  }
180
+
352
181
  return results;
353
182
  }
354
183
  }