swynx-lite 1.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.
Files changed (36) hide show
  1. package/README.md +113 -0
  2. package/bin/swynx-lite +3 -0
  3. package/package.json +47 -0
  4. package/src/clean.mjs +280 -0
  5. package/src/cli.mjs +264 -0
  6. package/src/config.mjs +121 -0
  7. package/src/output/console.mjs +298 -0
  8. package/src/output/json.mjs +76 -0
  9. package/src/output/progress.mjs +57 -0
  10. package/src/scan.mjs +143 -0
  11. package/src/security.mjs +62 -0
  12. package/src/shared/fixer/barrel-cleaner.mjs +192 -0
  13. package/src/shared/fixer/import-cleaner.mjs +237 -0
  14. package/src/shared/fixer/quarantine.mjs +218 -0
  15. package/src/shared/scanner/analysers/buildSystems.mjs +647 -0
  16. package/src/shared/scanner/analysers/configParsers.mjs +1086 -0
  17. package/src/shared/scanner/analysers/deadcode.mjs +6194 -0
  18. package/src/shared/scanner/analysers/entryPointDetector.mjs +634 -0
  19. package/src/shared/scanner/analysers/generatedCode.mjs +297 -0
  20. package/src/shared/scanner/analysers/imports.mjs +60 -0
  21. package/src/shared/scanner/discovery.mjs +240 -0
  22. package/src/shared/scanner/parse-worker.mjs +82 -0
  23. package/src/shared/scanner/parsers/assets.mjs +44 -0
  24. package/src/shared/scanner/parsers/csharp.mjs +400 -0
  25. package/src/shared/scanner/parsers/css.mjs +60 -0
  26. package/src/shared/scanner/parsers/go.mjs +445 -0
  27. package/src/shared/scanner/parsers/java.mjs +364 -0
  28. package/src/shared/scanner/parsers/javascript.mjs +823 -0
  29. package/src/shared/scanner/parsers/kotlin.mjs +350 -0
  30. package/src/shared/scanner/parsers/python.mjs +497 -0
  31. package/src/shared/scanner/parsers/registry.mjs +233 -0
  32. package/src/shared/scanner/parsers/rust.mjs +427 -0
  33. package/src/shared/scanner/scan-dead-code.mjs +316 -0
  34. package/src/shared/security/patterns.mjs +349 -0
  35. package/src/shared/security/proximity.mjs +84 -0
  36. package/src/shared/security/scanner.mjs +269 -0
@@ -0,0 +1,647 @@
1
+ // src/scanner/analysers/buildSystems.mjs
2
+ // Enterprise build system detection for monorepos and multi-language projects
3
+
4
+ import { readFileSync, existsSync, readdirSync } from 'fs';
5
+ import { join, dirname, basename } from 'path';
6
+ import { globSync } from 'glob';
7
+
8
+ /**
9
+ * Detected build system info
10
+ * @typedef {Object} BuildSystemInfo
11
+ * @property {string} type - Build system type (gradle, maven, bazel, etc.)
12
+ * @property {string} configFile - Path to config file
13
+ * @property {string[]} packages - Detected package/module directories
14
+ * @property {Object} metadata - Additional metadata
15
+ */
16
+
17
+ /**
18
+ * Detect all build systems in a project
19
+ * @param {string} projectPath - Path to project root
20
+ * @returns {BuildSystemInfo[]} - Array of detected build systems
21
+ */
22
+ export function detectBuildSystems(projectPath) {
23
+ if (!projectPath || !existsSync(projectPath)) return [];
24
+
25
+ const systems = [];
26
+
27
+ // JavaScript/TypeScript (already partially handled)
28
+ systems.push(...detectTurborepo(projectPath));
29
+
30
+ // JVM
31
+ systems.push(...detectGradle(projectPath));
32
+ systems.push(...detectMaven(projectPath));
33
+
34
+ // Bazel/Buck/Pants
35
+ systems.push(...detectBazel(projectPath));
36
+ systems.push(...detectBuck(projectPath));
37
+ systems.push(...detectPants(projectPath));
38
+
39
+ // Go
40
+ systems.push(...detectGoWorkspace(projectPath));
41
+
42
+ // .NET
43
+ systems.push(...detectDotNet(projectPath));
44
+
45
+ // Rust
46
+ systems.push(...detectCargo(projectPath));
47
+
48
+ // Python
49
+ systems.push(...detectPythonProject(projectPath));
50
+
51
+ return systems;
52
+ }
53
+
54
+ /**
55
+ * Get all package directories from detected build systems
56
+ * @param {string} projectPath - Path to project root
57
+ * @returns {string[]} - Array of package directory paths (relative)
58
+ */
59
+ export function getPackageDirectories(projectPath) {
60
+ const systems = detectBuildSystems(projectPath);
61
+ const dirs = new Set();
62
+
63
+ for (const system of systems) {
64
+ for (const pkg of system.packages || []) {
65
+ dirs.add(pkg);
66
+ }
67
+ }
68
+
69
+ return [...dirs];
70
+ }
71
+
72
+ // ═══════════════════════════════════════════════════════════════════════════
73
+ // Turborepo
74
+ // ═══════════════════════════════════════════════════════════════════════════
75
+
76
+ function detectTurborepo(projectPath) {
77
+ const turboPath = join(projectPath, 'turbo.json');
78
+ if (!existsSync(turboPath)) return [];
79
+
80
+ try {
81
+ const content = readFileSync(turboPath, 'utf-8');
82
+ const turbo = JSON.parse(content);
83
+
84
+ // Turborepo uses package.json workspaces for packages
85
+ // turbo.json defines the pipeline
86
+ const pipelines = Object.keys(turbo.pipeline || turbo.tasks || {});
87
+
88
+ return [{
89
+ type: 'turborepo',
90
+ configFile: 'turbo.json',
91
+ packages: [], // Packages come from package.json workspaces
92
+ metadata: { pipelines }
93
+ }];
94
+ } catch {
95
+ return [];
96
+ }
97
+ }
98
+
99
+ // ═══════════════════════════════════════════════════════════════════════════
100
+ // Gradle (Java/Kotlin)
101
+ // ═══════════════════════════════════════════════════════════════════════════
102
+
103
+ function detectGradle(projectPath) {
104
+ const settingsFiles = ['settings.gradle', 'settings.gradle.kts'];
105
+ const results = [];
106
+
107
+ for (const settingsFile of settingsFiles) {
108
+ const settingsPath = join(projectPath, settingsFile);
109
+ if (!existsSync(settingsPath)) continue;
110
+
111
+ try {
112
+ const content = readFileSync(settingsPath, 'utf-8');
113
+ const packages = [];
114
+
115
+ // Parse include statements
116
+ // include ':app', ':core', ':shared:utils'
117
+ // include(":app", ":core")
118
+ const includePatterns = [
119
+ /include\s*\(\s*['"]([^'"]+)['"]/g, // include(":app")
120
+ /include\s+['"]([^'"]+)['"]/g, // include ':app'
121
+ /include\s*\(\s*([^)]+)\)/g, // include(":app", ":core")
122
+ ];
123
+
124
+ for (const pattern of includePatterns) {
125
+ let match;
126
+ while ((match = pattern.exec(content)) !== null) {
127
+ const modules = match[1].split(/[,\s]+/).map(m =>
128
+ m.replace(/['"]/g, '').replace(/^:/, '').replace(/:/g, '/')
129
+ ).filter(m => m);
130
+ packages.push(...modules);
131
+ }
132
+ }
133
+
134
+ // Parse includeFlat
135
+ const flatPattern = /includeFlat\s+['"]([^'"]+)['"]/g;
136
+ let match;
137
+ while ((match = flatPattern.exec(content)) !== null) {
138
+ packages.push(`../${match[1]}`);
139
+ }
140
+
141
+ results.push({
142
+ type: 'gradle',
143
+ configFile: settingsFile,
144
+ packages: [...new Set(packages)],
145
+ metadata: { isKotlinDsl: settingsFile.endsWith('.kts') }
146
+ });
147
+ } catch {
148
+ // Ignore parse errors
149
+ }
150
+ }
151
+
152
+ return results;
153
+ }
154
+
155
+ // ═══════════════════════════════════════════════════════════════════════════
156
+ // Maven (Java)
157
+ // ═══════════════════════════════════════════════════════════════════════════
158
+
159
+ function detectMaven(projectPath) {
160
+ const pomPath = join(projectPath, 'pom.xml');
161
+ if (!existsSync(pomPath)) return [];
162
+
163
+ try {
164
+ const content = readFileSync(pomPath, 'utf-8');
165
+ const packages = [];
166
+
167
+ // Parse <modules> section
168
+ // <modules>
169
+ // <module>core</module>
170
+ // <module>api</module>
171
+ // </modules>
172
+ const modulesMatch = content.match(/<modules>([\s\S]*?)<\/modules>/);
173
+ if (modulesMatch) {
174
+ const modulePattern = /<module>([^<]+)<\/module>/g;
175
+ let match;
176
+ while ((match = modulePattern.exec(modulesMatch[1])) !== null) {
177
+ packages.push(match[1].trim());
178
+ }
179
+ }
180
+
181
+ // Recursively check for submodule pom.xml files
182
+ for (const pkg of [...packages]) {
183
+ const subPomPath = join(projectPath, pkg, 'pom.xml');
184
+ if (existsSync(subPomPath)) {
185
+ try {
186
+ const subContent = readFileSync(subPomPath, 'utf-8');
187
+ const subModulesMatch = subContent.match(/<modules>([\s\S]*?)<\/modules>/);
188
+ if (subModulesMatch) {
189
+ const modulePattern = /<module>([^<]+)<\/module>/g;
190
+ let match;
191
+ while ((match = modulePattern.exec(subModulesMatch[1])) !== null) {
192
+ packages.push(`${pkg}/${match[1].trim()}`);
193
+ }
194
+ }
195
+ } catch {
196
+ // Ignore
197
+ }
198
+ }
199
+ }
200
+
201
+ // Extract artifactId for metadata
202
+ const artifactIdMatch = content.match(/<artifactId>([^<]+)<\/artifactId>/);
203
+ const groupIdMatch = content.match(/<groupId>([^<]+)<\/groupId>/);
204
+
205
+ return [{
206
+ type: 'maven',
207
+ configFile: 'pom.xml',
208
+ packages: [...new Set(packages)],
209
+ metadata: {
210
+ artifactId: artifactIdMatch?.[1],
211
+ groupId: groupIdMatch?.[1]
212
+ }
213
+ }];
214
+ } catch {
215
+ return [];
216
+ }
217
+ }
218
+
219
+ // ═══════════════════════════════════════════════════════════════════════════
220
+ // Bazel
221
+ // ═══════════════════════════════════════════════════════════════════════════
222
+
223
+ function detectBazel(projectPath) {
224
+ const workspaceFiles = ['WORKSPACE', 'WORKSPACE.bazel', 'MODULE.bazel'];
225
+ let foundConfig = null;
226
+
227
+ for (const wsFile of workspaceFiles) {
228
+ const wsPath = join(projectPath, wsFile);
229
+ if (existsSync(wsPath)) {
230
+ foundConfig = wsFile;
231
+ break;
232
+ }
233
+ }
234
+
235
+ if (!foundConfig) return [];
236
+
237
+ // Find all BUILD files to identify packages
238
+ const packages = [];
239
+ try {
240
+ const buildFiles = globSync('**/BUILD{,.bazel}', {
241
+ cwd: projectPath,
242
+ ignore: ['bazel-*/**', 'node_modules/**', '.git/**']
243
+ });
244
+
245
+ for (const buildFile of buildFiles) {
246
+ const dir = dirname(buildFile);
247
+ if (dir !== '.') {
248
+ packages.push(dir);
249
+ }
250
+ }
251
+ } catch {
252
+ // Ignore glob errors
253
+ }
254
+
255
+ return [{
256
+ type: 'bazel',
257
+ configFile: foundConfig,
258
+ packages,
259
+ metadata: { isBzlmod: foundConfig === 'MODULE.bazel' }
260
+ }];
261
+ }
262
+
263
+ // ═══════════════════════════════════════════════════════════════════════════
264
+ // Buck/Buck2 (Meta)
265
+ // ═══════════════════════════════════════════════════════════════════════════
266
+
267
+ function detectBuck(projectPath) {
268
+ const buckConfigs = ['.buckconfig'];
269
+ let foundConfig = null;
270
+
271
+ for (const cfg of buckConfigs) {
272
+ if (existsSync(join(projectPath, cfg))) {
273
+ foundConfig = cfg;
274
+ break;
275
+ }
276
+ }
277
+
278
+ if (!foundConfig) return [];
279
+
280
+ // Find all BUCK files
281
+ const packages = [];
282
+ try {
283
+ const buckFiles = globSync('**/BUCK{,.v2}', {
284
+ cwd: projectPath,
285
+ ignore: ['buck-out/**', 'node_modules/**', '.git/**']
286
+ });
287
+
288
+ for (const buckFile of buckFiles) {
289
+ const dir = dirname(buckFile);
290
+ if (dir !== '.') {
291
+ packages.push(dir);
292
+ }
293
+ }
294
+ } catch {
295
+ // Ignore glob errors
296
+ }
297
+
298
+ return [{
299
+ type: 'buck',
300
+ configFile: foundConfig,
301
+ packages,
302
+ metadata: {}
303
+ }];
304
+ }
305
+
306
+ // ═══════════════════════════════════════════════════════════════════════════
307
+ // Pants
308
+ // ═══════════════════════════════════════════════════════════════════════════
309
+
310
+ function detectPants(projectPath) {
311
+ const pantsPath = join(projectPath, 'pants.toml');
312
+ if (!existsSync(pantsPath)) return [];
313
+
314
+ const packages = [];
315
+
316
+ try {
317
+ const content = readFileSync(pantsPath, 'utf-8');
318
+
319
+ // Parse source_roots from pants.toml
320
+ // [source]
321
+ // root_patterns = ["src/*", "tests/*"]
322
+ const rootPatternsMatch = content.match(/root_patterns\s*=\s*\[(.*?)\]/s);
323
+ if (rootPatternsMatch) {
324
+ const patterns = rootPatternsMatch[1].match(/["']([^"']+)["']/g);
325
+ if (patterns) {
326
+ for (const p of patterns) {
327
+ const pattern = p.replace(/["']/g, '');
328
+ const baseDir = pattern.replace(/\/?\*.*$/, '');
329
+ if (baseDir && existsSync(join(projectPath, baseDir))) {
330
+ try {
331
+ const entries = readdirSync(join(projectPath, baseDir), { withFileTypes: true });
332
+ for (const entry of entries) {
333
+ if (entry.isDirectory()) {
334
+ packages.push(`${baseDir}/${entry.name}`);
335
+ }
336
+ }
337
+ } catch {
338
+ // Ignore
339
+ }
340
+ }
341
+ }
342
+ }
343
+ }
344
+ } catch {
345
+ // Ignore parse errors
346
+ }
347
+
348
+ // Also find BUILD files (Pants uses same format as Bazel)
349
+ try {
350
+ const buildFiles = globSync('**/BUILD', {
351
+ cwd: projectPath,
352
+ ignore: ['dist/**', 'node_modules/**', '.git/**', '.pants.d/**']
353
+ });
354
+
355
+ for (const buildFile of buildFiles) {
356
+ const dir = dirname(buildFile);
357
+ if (dir !== '.' && !packages.includes(dir)) {
358
+ packages.push(dir);
359
+ }
360
+ }
361
+ } catch {
362
+ // Ignore glob errors
363
+ }
364
+
365
+ return [{
366
+ type: 'pants',
367
+ configFile: 'pants.toml',
368
+ packages: [...new Set(packages)],
369
+ metadata: {}
370
+ }];
371
+ }
372
+
373
+ // ═══════════════════════════════════════════════════════════════════════════
374
+ // Go Workspaces
375
+ // ═══════════════════════════════════════════════════════════════════════════
376
+
377
+ function detectGoWorkspace(projectPath) {
378
+ const goWorkPath = join(projectPath, 'go.work');
379
+ if (!existsSync(goWorkPath)) return [];
380
+
381
+ try {
382
+ const content = readFileSync(goWorkPath, 'utf-8');
383
+ const packages = [];
384
+
385
+ // Parse use directives
386
+ // use (
387
+ // ./cmd/server
388
+ // ./pkg/utils
389
+ // )
390
+ // or: use ./cmd/server
391
+ const useBlockMatch = content.match(/use\s*\(([\s\S]*?)\)/);
392
+ if (useBlockMatch) {
393
+ const lines = useBlockMatch[1].split('\n');
394
+ for (const line of lines) {
395
+ const trimmed = line.trim();
396
+ if (trimmed && !trimmed.startsWith('//')) {
397
+ packages.push(trimmed.replace(/^\.\//, ''));
398
+ }
399
+ }
400
+ }
401
+
402
+ // Single use directive
403
+ const singleUsePattern = /^use\s+(\S+)/gm;
404
+ let match;
405
+ while ((match = singleUsePattern.exec(content)) !== null) {
406
+ if (!match[1].startsWith('(')) {
407
+ packages.push(match[1].replace(/^\.\//, ''));
408
+ }
409
+ }
410
+
411
+ return [{
412
+ type: 'go-workspace',
413
+ configFile: 'go.work',
414
+ packages: [...new Set(packages)],
415
+ metadata: {}
416
+ }];
417
+ } catch {
418
+ return [];
419
+ }
420
+ }
421
+
422
+ // ═══════════════════════════════════════════════════════════════════════════
423
+ // .NET Solutions
424
+ // ═══════════════════════════════════════════════════════════════════════════
425
+
426
+ function detectDotNet(projectPath) {
427
+ const results = [];
428
+
429
+ try {
430
+ // Find .sln files
431
+ const slnFiles = globSync('*.sln', { cwd: projectPath });
432
+
433
+ for (const slnFile of slnFiles) {
434
+ const slnPath = join(projectPath, slnFile);
435
+ const content = readFileSync(slnPath, 'utf-8');
436
+ const packages = [];
437
+
438
+ // Parse Project lines
439
+ // Project("{GUID}") = "ProjectName", "path\to\project.csproj", "{GUID}"
440
+ const projectPattern = /Project\("[^"]+"\)\s*=\s*"([^"]+)",\s*"([^"]+)",\s*"[^"]+"/g;
441
+ let match;
442
+ while ((match = projectPattern.exec(content)) !== null) {
443
+ const projectPath = match[2].replace(/\\/g, '/');
444
+ // Get directory containing the .csproj
445
+ const projectDir = dirname(projectPath);
446
+ if (projectDir && projectDir !== '.') {
447
+ packages.push(projectDir);
448
+ }
449
+ }
450
+
451
+ results.push({
452
+ type: 'dotnet-solution',
453
+ configFile: slnFile,
454
+ packages: [...new Set(packages)],
455
+ metadata: {}
456
+ });
457
+ }
458
+ } catch {
459
+ // Ignore errors
460
+ }
461
+
462
+ // Also detect Directory.Build.props for SDK-style projects
463
+ if (existsSync(join(projectPath, 'Directory.Build.props'))) {
464
+ // Find all .csproj files
465
+ try {
466
+ const csprojFiles = globSync('**/*.csproj', {
467
+ cwd: projectPath,
468
+ ignore: ['**/bin/**', '**/obj/**', 'node_modules/**']
469
+ });
470
+
471
+ const packages = csprojFiles.map(f => dirname(f)).filter(d => d !== '.');
472
+
473
+ if (packages.length > 0 && !results.some(r => r.type === 'dotnet-solution')) {
474
+ results.push({
475
+ type: 'dotnet-sdk',
476
+ configFile: 'Directory.Build.props',
477
+ packages: [...new Set(packages)],
478
+ metadata: {}
479
+ });
480
+ }
481
+ } catch {
482
+ // Ignore
483
+ }
484
+ }
485
+
486
+ return results;
487
+ }
488
+
489
+ // ═══════════════════════════════════════════════════════════════════════════
490
+ // Cargo Workspaces (Rust)
491
+ // ═══════════════════════════════════════════════════════════════════════════
492
+
493
+ function detectCargo(projectPath) {
494
+ const cargoPath = join(projectPath, 'Cargo.toml');
495
+ if (!existsSync(cargoPath)) return [];
496
+
497
+ try {
498
+ const content = readFileSync(cargoPath, 'utf-8');
499
+ const packages = [];
500
+
501
+ // Check for [workspace] section
502
+ if (!content.includes('[workspace]')) {
503
+ // Single crate, not a workspace
504
+ return [{
505
+ type: 'cargo-single',
506
+ configFile: 'Cargo.toml',
507
+ packages: [],
508
+ metadata: {}
509
+ }];
510
+ }
511
+
512
+ // Parse members
513
+ // [workspace]
514
+ // members = ["crates/*", "tools/cli"]
515
+ const membersMatch = content.match(/members\s*=\s*\[([\s\S]*?)\]/);
516
+ if (membersMatch) {
517
+ const memberStrings = membersMatch[1].match(/["']([^"']+)["']/g);
518
+ if (memberStrings) {
519
+ for (const memberStr of memberStrings) {
520
+ const member = memberStr.replace(/["']/g, '');
521
+ if (member.includes('*')) {
522
+ // Glob pattern - expand it
523
+ const baseDir = member.replace(/\/?\*.*$/, '');
524
+ if (existsSync(join(projectPath, baseDir))) {
525
+ try {
526
+ const entries = readdirSync(join(projectPath, baseDir), { withFileTypes: true });
527
+ for (const entry of entries) {
528
+ if (entry.isDirectory() && existsSync(join(projectPath, baseDir, entry.name, 'Cargo.toml'))) {
529
+ packages.push(`${baseDir}/${entry.name}`);
530
+ }
531
+ }
532
+ } catch {
533
+ // Ignore
534
+ }
535
+ }
536
+ } else {
537
+ packages.push(member);
538
+ }
539
+ }
540
+ }
541
+ }
542
+
543
+ // Parse exclude (these should NOT be packages)
544
+ const excludeMatch = content.match(/exclude\s*=\s*\[([\s\S]*?)\]/);
545
+ const excludes = new Set();
546
+ if (excludeMatch) {
547
+ const excludeStrings = excludeMatch[1].match(/["']([^"']+)["']/g);
548
+ if (excludeStrings) {
549
+ for (const excStr of excludeStrings) {
550
+ excludes.add(excStr.replace(/["']/g, ''));
551
+ }
552
+ }
553
+ }
554
+
555
+ return [{
556
+ type: 'cargo-workspace',
557
+ configFile: 'Cargo.toml',
558
+ packages: packages.filter(p => !excludes.has(p)),
559
+ metadata: { excludes: [...excludes] }
560
+ }];
561
+ } catch {
562
+ return [];
563
+ }
564
+ }
565
+
566
+ // ═══════════════════════════════════════════════════════════════════════════
567
+ // Python Projects
568
+ // ═══════════════════════════════════════════════════════════════════════════
569
+
570
+ function detectPythonProject(projectPath) {
571
+ const results = [];
572
+
573
+ // Check pyproject.toml (PEP 518)
574
+ const pyprojectPath = join(projectPath, 'pyproject.toml');
575
+ if (existsSync(pyprojectPath)) {
576
+ try {
577
+ const content = readFileSync(pyprojectPath, 'utf-8');
578
+ const packages = [];
579
+
580
+ // Check for Poetry workspaces (experimental)
581
+ // [tool.poetry.packages]
582
+ // or src layout detection
583
+ if (existsSync(join(projectPath, 'src'))) {
584
+ try {
585
+ const srcEntries = readdirSync(join(projectPath, 'src'), { withFileTypes: true });
586
+ for (const entry of srcEntries) {
587
+ if (entry.isDirectory() && !entry.name.startsWith('_')) {
588
+ packages.push(`src/${entry.name}`);
589
+ }
590
+ }
591
+ } catch {
592
+ // Ignore
593
+ }
594
+ }
595
+
596
+ // Check for package names in [tool.poetry] or [project]
597
+ const nameMatch = content.match(/name\s*=\s*["']([^"']+)["']/);
598
+
599
+ results.push({
600
+ type: 'python-pyproject',
601
+ configFile: 'pyproject.toml',
602
+ packages,
603
+ metadata: { name: nameMatch?.[1] }
604
+ });
605
+ } catch {
606
+ // Ignore
607
+ }
608
+ }
609
+
610
+ // Check setup.py
611
+ const setupPyPath = join(projectPath, 'setup.py');
612
+ if (existsSync(setupPyPath)) {
613
+ results.push({
614
+ type: 'python-setup',
615
+ configFile: 'setup.py',
616
+ packages: existsSync(join(projectPath, 'src')) ? ['src'] : [],
617
+ metadata: {}
618
+ });
619
+ }
620
+
621
+ return results;
622
+ }
623
+
624
+ /**
625
+ * Merge packages from all build systems into the monorepo detection
626
+ * This is called from deadcode.mjs extractPathAliases
627
+ * @param {string} projectPath - Path to project root
628
+ * @returns {Array<{dir: string, prefix: string}>} - Config dirs for alias extraction
629
+ */
630
+ export function getConfigDirsFromBuildSystems(projectPath) {
631
+ const systems = detectBuildSystems(projectPath);
632
+ const configDirs = [];
633
+
634
+ for (const system of systems) {
635
+ for (const pkg of system.packages || []) {
636
+ configDirs.push({ dir: pkg, prefix: `${pkg}/` });
637
+ }
638
+ }
639
+
640
+ return configDirs;
641
+ }
642
+
643
+ export default {
644
+ detectBuildSystems,
645
+ getPackageDirectories,
646
+ getConfigDirsFromBuildSystems
647
+ };