@kb-labs/quality-core 0.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.
package/dist/index.js ADDED
@@ -0,0 +1,1921 @@
1
+ import fs8 from 'fs';
2
+ import path8, { join } from 'path';
3
+ import { readFile, stat, access } from 'fs/promises';
4
+ import globby3 from 'globby';
5
+ import { exec, execSync } from 'child_process';
6
+ import { promisify } from 'util';
7
+ import ts from 'typescript';
8
+
9
+ // src/graph/dependency-graph.ts
10
+ function buildDependencyGraph(rootDir) {
11
+ const nodes = /* @__PURE__ */ new Map();
12
+ const workspacePackages = /* @__PURE__ */ new Set();
13
+ if (!fs8.existsSync(rootDir)) {
14
+ return { nodes, workspacePackages };
15
+ }
16
+ const entries = fs8.readdirSync(rootDir, { withFileTypes: true });
17
+ for (const entry of entries) {
18
+ if (!entry.isDirectory() || !entry.name.startsWith("kb-labs-")) {
19
+ continue;
20
+ }
21
+ const repoPath = path8.join(rootDir, entry.name);
22
+ const packagesDir = path8.join(repoPath, "packages");
23
+ if (!fs8.existsSync(packagesDir)) {
24
+ continue;
25
+ }
26
+ const packageDirs = fs8.readdirSync(packagesDir, { withFileTypes: true });
27
+ for (const pkgDir of packageDirs) {
28
+ if (!pkgDir.isDirectory()) {
29
+ continue;
30
+ }
31
+ const packageJsonPath = path8.join(packagesDir, pkgDir.name, "package.json");
32
+ if (fs8.existsSync(packageJsonPath)) {
33
+ const pkgJson = JSON.parse(fs8.readFileSync(packageJsonPath, "utf-8"));
34
+ const packageName = pkgJson.name;
35
+ if (packageName) {
36
+ workspacePackages.add(packageName);
37
+ }
38
+ }
39
+ }
40
+ }
41
+ for (const entry of entries) {
42
+ if (!entry.isDirectory() || !entry.name.startsWith("kb-labs-")) {
43
+ continue;
44
+ }
45
+ const repoPath = path8.join(rootDir, entry.name);
46
+ const packagesDir = path8.join(repoPath, "packages");
47
+ if (!fs8.existsSync(packagesDir)) {
48
+ continue;
49
+ }
50
+ const packageDirs = fs8.readdirSync(packagesDir, { withFileTypes: true });
51
+ for (const pkgDir of packageDirs) {
52
+ if (!pkgDir.isDirectory()) {
53
+ continue;
54
+ }
55
+ const packageJsonPath = path8.join(packagesDir, pkgDir.name, "package.json");
56
+ if (fs8.existsSync(packageJsonPath)) {
57
+ const pkgJson = JSON.parse(fs8.readFileSync(packageJsonPath, "utf-8"));
58
+ const packageName = pkgJson.name;
59
+ if (!packageName) {
60
+ continue;
61
+ }
62
+ const deps = /* @__PURE__ */ new Set();
63
+ const devDeps = /* @__PURE__ */ new Set();
64
+ const allDeps = {
65
+ ...pkgJson.dependencies,
66
+ ...pkgJson.devDependencies
67
+ };
68
+ for (const dep of Object.keys(allDeps)) {
69
+ if (workspacePackages.has(dep)) {
70
+ deps.add(dep);
71
+ }
72
+ }
73
+ if (pkgJson.devDependencies) {
74
+ for (const dep of Object.keys(pkgJson.devDependencies)) {
75
+ if (workspacePackages.has(dep)) {
76
+ devDeps.add(dep);
77
+ }
78
+ }
79
+ }
80
+ nodes.set(packageName, {
81
+ name: packageName,
82
+ path: packageJsonPath,
83
+ dir: path8.dirname(packageJsonPath),
84
+ deps,
85
+ devDeps,
86
+ dependents: /* @__PURE__ */ new Set()
87
+ });
88
+ }
89
+ }
90
+ }
91
+ for (const [packageName, node] of nodes) {
92
+ for (const dep of node.deps) {
93
+ const depNode = nodes.get(dep);
94
+ if (depNode) {
95
+ depNode.dependents.add(packageName);
96
+ }
97
+ }
98
+ }
99
+ return { nodes, workspacePackages };
100
+ }
101
+ function topologicalSort(graph) {
102
+ const { nodes } = graph;
103
+ const layers = [];
104
+ const sorted = [];
105
+ const circular = [];
106
+ const inDegree = /* @__PURE__ */ new Map();
107
+ for (const [name] of nodes) {
108
+ inDegree.set(name, 0);
109
+ }
110
+ for (const [, node] of nodes) {
111
+ for (const dep of node.deps) {
112
+ if (nodes.has(dep)) {
113
+ inDegree.set(dep, (inDegree.get(dep) ?? 0) + 1);
114
+ }
115
+ }
116
+ }
117
+ const queue = [];
118
+ for (const [name, degree] of inDegree) {
119
+ if (degree === 0) {
120
+ queue.push(name);
121
+ }
122
+ }
123
+ while (queue.length > 0) {
124
+ const layer = [...queue];
125
+ layers.push(layer);
126
+ sorted.push(...layer);
127
+ queue.length = 0;
128
+ for (const name of layer) {
129
+ const node = nodes.get(name);
130
+ if (!node) {
131
+ continue;
132
+ }
133
+ for (const dep of node.deps) {
134
+ if (nodes.has(dep)) {
135
+ const newDegree = (inDegree.get(dep) ?? 0) - 1;
136
+ inDegree.set(dep, newDegree);
137
+ if (newDegree === 0) {
138
+ queue.push(dep);
139
+ }
140
+ }
141
+ }
142
+ }
143
+ }
144
+ if (sorted.length < nodes.size) {
145
+ const remaining = /* @__PURE__ */ new Set();
146
+ for (const [name] of nodes) {
147
+ if (!sorted.includes(name)) {
148
+ remaining.add(name);
149
+ }
150
+ }
151
+ const cycles = findCircularDependencies(graph, remaining);
152
+ circular.push(...cycles);
153
+ }
154
+ return { layers, sorted, circular };
155
+ }
156
+ function findCircularDependencies(graph, subset) {
157
+ const { nodes } = graph;
158
+ const visited = /* @__PURE__ */ new Set();
159
+ const recStack = /* @__PURE__ */ new Set();
160
+ const cycles = [];
161
+ function dfs(node, path9) {
162
+ visited.add(node);
163
+ recStack.add(node);
164
+ path9.push(node);
165
+ const nodeData = nodes.get(node);
166
+ if (!nodeData) {
167
+ return;
168
+ }
169
+ for (const dep of nodeData.deps) {
170
+ if (!nodes.has(dep)) {
171
+ continue;
172
+ }
173
+ if (subset && !subset.has(dep)) {
174
+ continue;
175
+ }
176
+ if (!visited.has(dep)) {
177
+ dfs(dep, [...path9]);
178
+ } else if (recStack.has(dep)) {
179
+ const cycleStart = path9.indexOf(dep);
180
+ if (cycleStart !== -1) {
181
+ const cycle = path9.slice(cycleStart);
182
+ cycles.push([...cycle, dep]);
183
+ }
184
+ }
185
+ }
186
+ recStack.delete(node);
187
+ }
188
+ const nodesToCheck = subset ?? new Set(nodes.keys());
189
+ for (const node of nodesToCheck) {
190
+ if (!visited.has(node)) {
191
+ dfs(node, []);
192
+ }
193
+ }
194
+ return cycles;
195
+ }
196
+ function getBuildOrderForPackage(graph, packageName) {
197
+ const { nodes } = graph;
198
+ const allDeps = /* @__PURE__ */ new Set();
199
+ const queue = [packageName];
200
+ while (queue.length > 0) {
201
+ const current = queue.shift();
202
+ allDeps.add(current);
203
+ const node = nodes.get(current);
204
+ if (!node) {
205
+ continue;
206
+ }
207
+ for (const dep of node.deps) {
208
+ if (!allDeps.has(dep) && nodes.has(dep)) {
209
+ queue.push(dep);
210
+ }
211
+ }
212
+ }
213
+ const subgraph = {
214
+ nodes: /* @__PURE__ */ new Map(),
215
+ workspacePackages: graph.workspacePackages
216
+ };
217
+ for (const pkg of allDeps) {
218
+ const node = nodes.get(pkg);
219
+ if (node) {
220
+ subgraph.nodes.set(pkg, node);
221
+ }
222
+ }
223
+ return topologicalSort(subgraph);
224
+ }
225
+ function getReverseDependencies(graph, packageName) {
226
+ const { nodes } = graph;
227
+ const reverseDeps = /* @__PURE__ */ new Set();
228
+ for (const [name, node] of nodes) {
229
+ if (node.deps.has(packageName)) {
230
+ reverseDeps.add(name);
231
+ }
232
+ }
233
+ return reverseDeps;
234
+ }
235
+ function getImpactAnalysis(graph, packageName) {
236
+ const affected = /* @__PURE__ */ new Set();
237
+ const queue = [packageName];
238
+ while (queue.length > 0) {
239
+ const current = queue.shift();
240
+ const reverseDeps = getReverseDependencies(graph, current);
241
+ for (const dep of reverseDeps) {
242
+ if (!affected.has(dep)) {
243
+ affected.add(dep);
244
+ queue.push(dep);
245
+ }
246
+ }
247
+ }
248
+ return affected;
249
+ }
250
+ async function calculateLinesOfCode(rootDir, sourceFiles) {
251
+ const files = sourceFiles ?? await globby3("**/*.{ts,tsx,js,jsx}", {
252
+ cwd: rootDir,
253
+ ignore: [
254
+ "**/node_modules/**",
255
+ "**/dist/**",
256
+ "**/.git/**",
257
+ "**/.kb/**",
258
+ "**/build/**",
259
+ "**/*.test.{ts,tsx,js,jsx}",
260
+ "**/*.spec.{ts,tsx,js,jsx}"
261
+ ],
262
+ absolute: true,
263
+ deep: 8
264
+ });
265
+ const contents = await Promise.all(files.map((f) => readFile(f, "utf-8").catch(() => null)));
266
+ return contents.reduce((sum, c) => sum + (c ? c.split("\n").length : 0), 0);
267
+ }
268
+ async function calculateSize(rootDir, sourceFiles) {
269
+ const files = sourceFiles ?? await globby3("**/*.{ts,tsx,js,jsx}", {
270
+ cwd: rootDir,
271
+ ignore: ["**/node_modules/**", "**/dist/**", "**/.git/**", "**/.kb/**", "**/build/**"],
272
+ absolute: true,
273
+ deep: 8
274
+ });
275
+ const stats = await Promise.all(files.map((f) => stat(f).catch(() => null)));
276
+ return stats.reduce((sum, s) => sum + (s ? s.size : 0), 0);
277
+ }
278
+ function formatBytes(bytes) {
279
+ if (bytes === 0) {
280
+ return "0 B";
281
+ }
282
+ const units = ["B", "KB", "MB", "GB"];
283
+ const k = 1024;
284
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
285
+ const value = bytes / Math.pow(k, i);
286
+ return `${value.toFixed(2)} ${units[i]}`;
287
+ }
288
+ async function countPackages(rootDir) {
289
+ const packageJsonFiles = await globby3("**/package.json", {
290
+ cwd: rootDir,
291
+ ignore: ["**/node_modules/**", "**/.git/**", "**/.kb/**"],
292
+ absolute: false,
293
+ deep: 6
294
+ });
295
+ return packageJsonFiles.filter((p) => p !== "package.json").length;
296
+ }
297
+ async function calculateStats(rootDir) {
298
+ const sourceFiles = await globby3("**/*.{ts,tsx,js,jsx}", {
299
+ cwd: rootDir,
300
+ ignore: ["**/node_modules/**", "**/dist/**", "**/.git/**", "**/.kb/**", "**/build/**"],
301
+ absolute: true,
302
+ deep: 8
303
+ });
304
+ const [packages, loc, size] = await Promise.all([
305
+ countPackages(rootDir),
306
+ calculateLinesOfCode(rootDir, sourceFiles),
307
+ calculateSize(rootDir, sourceFiles)
308
+ ]);
309
+ return {
310
+ packages,
311
+ loc,
312
+ size,
313
+ sizeFormatted: formatBytes(size)
314
+ };
315
+ }
316
+ async function checkDuplicateDependencies(rootDir, pkgFiles) {
317
+ const packageJsonFiles = pkgFiles ?? await globby3("**/package.json", {
318
+ cwd: rootDir,
319
+ ignore: ["**/node_modules/**", "**/.git/**"],
320
+ absolute: true
321
+ });
322
+ const depVersions = /* @__PURE__ */ new Map();
323
+ const contents = await Promise.all(
324
+ packageJsonFiles.map((f) => readFile(f, "utf-8").catch(() => null))
325
+ );
326
+ for (const content of contents) {
327
+ if (!content) {
328
+ continue;
329
+ }
330
+ try {
331
+ const pkg = JSON.parse(content);
332
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
333
+ for (const [name, version] of Object.entries(allDeps)) {
334
+ if (typeof version !== "string") {
335
+ continue;
336
+ }
337
+ if (!depVersions.has(name)) {
338
+ depVersions.set(name, /* @__PURE__ */ new Set());
339
+ }
340
+ depVersions.get(name).add(version);
341
+ }
342
+ } catch {
343
+ }
344
+ }
345
+ const duplicates = Array.from(depVersions.entries()).filter(
346
+ ([, versions]) => versions.size > 1
347
+ );
348
+ if (duplicates.length === 0) {
349
+ return null;
350
+ }
351
+ const penalty = Math.min(duplicates.length * 2, 30);
352
+ return {
353
+ type: "duplicate",
354
+ severity: duplicates.length > 20 ? "high" : duplicates.length > 10 ? "medium" : "low",
355
+ message: `Found ${duplicates.length} duplicate dependencies with different versions`,
356
+ count: duplicates.length,
357
+ penalty
358
+ };
359
+ }
360
+ async function checkMissingReadmes(rootDir, packageJsonFiles) {
361
+ const pkgFiles = packageJsonFiles ?? await globby3("**/package.json", {
362
+ cwd: rootDir,
363
+ ignore: ["**/node_modules/**", "**/.git/**", "package.json"],
364
+ absolute: true
365
+ });
366
+ const hasReadme = async (pkgPath) => {
367
+ const dir = join(pkgPath, "..");
368
+ for (const name of ["README.md", "readme.md", "Readme.md"]) {
369
+ try {
370
+ await access(join(dir, name));
371
+ return true;
372
+ } catch {
373
+ }
374
+ }
375
+ return false;
376
+ };
377
+ const results = await Promise.all(pkgFiles.map(hasReadme));
378
+ const missingCount = results.filter((has) => !has).length;
379
+ if (missingCount === 0) {
380
+ return null;
381
+ }
382
+ const penalty = Math.min(missingCount, 15);
383
+ return {
384
+ type: "readme",
385
+ severity: missingCount > 20 ? "high" : missingCount > 10 ? "medium" : "low",
386
+ message: `Found ${missingCount} packages without README`,
387
+ count: missingCount,
388
+ penalty
389
+ };
390
+ }
391
+ function calculateHealthScore(issues) {
392
+ const baseScore = 100;
393
+ const totalPenalty = issues.reduce((sum, issue) => sum + issue.penalty, 0);
394
+ return Math.max(0, baseScore - totalPenalty);
395
+ }
396
+ function scoreToGrade(score) {
397
+ if (score >= 90) {
398
+ return "A";
399
+ }
400
+ if (score >= 80) {
401
+ return "B";
402
+ }
403
+ if (score >= 70) {
404
+ return "C";
405
+ }
406
+ if (score >= 60) {
407
+ return "D";
408
+ }
409
+ return "F";
410
+ }
411
+ async function calculateHealth(rootDir) {
412
+ const issues = [];
413
+ const packageJsonFiles = await globby3("**/package.json", {
414
+ cwd: rootDir,
415
+ ignore: ["**/node_modules/**", "**/.git/**", "**/.kb/**", "**/dist/**"],
416
+ absolute: true,
417
+ deep: 6
418
+ });
419
+ const [duplicatesIssue, readmesIssue] = await Promise.all([
420
+ checkDuplicateDependencies(rootDir, packageJsonFiles),
421
+ checkMissingReadmes(rootDir, packageJsonFiles)
422
+ ]);
423
+ if (duplicatesIssue) {
424
+ issues.push(duplicatesIssue);
425
+ }
426
+ if (readmesIssue) {
427
+ issues.push(readmesIssue);
428
+ }
429
+ const score = calculateHealthScore(issues);
430
+ const grade = scoreToGrade(score);
431
+ return {
432
+ score,
433
+ grade,
434
+ issues
435
+ };
436
+ }
437
+ async function analyzeDuplicateDependencies(rootDir) {
438
+ const packageJsonFiles = await globby3("**/package.json", {
439
+ cwd: rootDir,
440
+ ignore: ["**/node_modules/**", "**/.git/**"],
441
+ absolute: true
442
+ });
443
+ const depVersionMap = /* @__PURE__ */ new Map();
444
+ for (const file of packageJsonFiles) {
445
+ try {
446
+ const content = await readFile(file, "utf-8");
447
+ const pkg = JSON.parse(content);
448
+ const pkgName = pkg.name || file;
449
+ const allDeps = {
450
+ ...pkg.dependencies,
451
+ ...pkg.devDependencies
452
+ };
453
+ for (const [name, version] of Object.entries(allDeps)) {
454
+ if (typeof version !== "string") {
455
+ continue;
456
+ }
457
+ if (!depVersionMap.has(name)) {
458
+ depVersionMap.set(name, /* @__PURE__ */ new Map());
459
+ }
460
+ const versionMap = depVersionMap.get(name);
461
+ if (!versionMap.has(version)) {
462
+ versionMap.set(version, []);
463
+ }
464
+ versionMap.get(version).push(pkgName);
465
+ }
466
+ } catch {
467
+ }
468
+ }
469
+ const duplicates = [];
470
+ for (const [name, versionMap] of depVersionMap) {
471
+ if (versionMap.size <= 1) {
472
+ continue;
473
+ }
474
+ const versions = Array.from(versionMap.entries()).map(
475
+ ([version, packages]) => ({
476
+ version,
477
+ packages,
478
+ count: packages.length
479
+ })
480
+ );
481
+ versions.sort((a, b) => b.count - a.count);
482
+ const totalPackages = versions.reduce((sum, v) => sum + v.count, 0);
483
+ duplicates.push({
484
+ name,
485
+ versions,
486
+ totalPackages
487
+ });
488
+ }
489
+ duplicates.sort((a, b) => b.totalPackages - a.totalPackages);
490
+ return duplicates;
491
+ }
492
+ async function analyzeUnusedDependencies(rootDir) {
493
+ const packageJsonFiles = await globby3("**/package.json", {
494
+ cwd: rootDir,
495
+ ignore: ["**/node_modules/**", "**/.git/**"],
496
+ absolute: true
497
+ });
498
+ const unused = [];
499
+ for (const pkgPath of packageJsonFiles) {
500
+ try {
501
+ const content = await readFile(pkgPath, "utf-8");
502
+ const pkg = JSON.parse(content);
503
+ const pkgName = pkg.name || pkgPath;
504
+ const pkgDir = join(pkgPath, "..");
505
+ const allDeps = {
506
+ ...pkg.dependencies,
507
+ ...pkg.devDependencies
508
+ };
509
+ const sourceFiles = await globby3("src/**/*.{ts,tsx,js,jsx}", {
510
+ cwd: pkgDir,
511
+ absolute: true,
512
+ ignore: ["**/*.test.*", "**/*.spec.*"]
513
+ });
514
+ const sourceContents = await Promise.all(
515
+ sourceFiles.map(async (file) => {
516
+ try {
517
+ return readFile(file, "utf-8");
518
+ } catch {
519
+ return "";
520
+ }
521
+ })
522
+ );
523
+ const allSource = sourceContents.join("\n");
524
+ for (const depName of Object.keys(allDeps)) {
525
+ if (depName.startsWith("@types/") || depName === "typescript" || depName === "tsup" || depName === "vitest" || depName === "eslint" || depName === "prettier") {
526
+ continue;
527
+ }
528
+ if (!allSource.includes(depName)) {
529
+ const existing = unused.find((u) => u.name === depName);
530
+ if (existing) {
531
+ existing.packages.push(pkgName);
532
+ } else {
533
+ unused.push({
534
+ name: depName,
535
+ packages: [pkgName]
536
+ });
537
+ }
538
+ }
539
+ }
540
+ } catch {
541
+ }
542
+ }
543
+ return unused;
544
+ }
545
+ async function analyzeMissingDependencies(rootDir) {
546
+ const packageJsonFiles = await globby3("**/package.json", {
547
+ cwd: rootDir,
548
+ ignore: ["**/node_modules/**", "**/.git/**"],
549
+ absolute: true
550
+ });
551
+ const workspacePackages = /* @__PURE__ */ new Set();
552
+ for (const file of packageJsonFiles) {
553
+ try {
554
+ const content = await readFile(file, "utf-8");
555
+ const pkg = JSON.parse(content);
556
+ if (pkg.name) {
557
+ workspacePackages.add(pkg.name);
558
+ }
559
+ } catch {
560
+ }
561
+ }
562
+ const missing = [];
563
+ for (const pkgPath of packageJsonFiles) {
564
+ try {
565
+ const content = await readFile(pkgPath, "utf-8");
566
+ const pkg = JSON.parse(content);
567
+ if (!pkg.name) {
568
+ continue;
569
+ }
570
+ const pkgName = pkg.name;
571
+ const pkgDir = join(pkgPath, "..");
572
+ const declaredDeps = /* @__PURE__ */ new Set([
573
+ ...Object.keys(pkg.dependencies || {}),
574
+ ...Object.keys(pkg.devDependencies || {})
575
+ ]);
576
+ const sourceFiles = await globby3("src/**/*.{ts,tsx,js,jsx}", {
577
+ cwd: pkgDir,
578
+ absolute: true
579
+ });
580
+ for (const file of sourceFiles) {
581
+ try {
582
+ const source = await readFile(file, "utf-8");
583
+ const importRegex = /from\s+['"](@[\w-]+\/[\w-]+|[\w-]+)['"]/g;
584
+ let match;
585
+ while ((match = importRegex.exec(source)) !== null) {
586
+ const importedPkg = match[1];
587
+ if (!importedPkg) {
588
+ continue;
589
+ }
590
+ if (workspacePackages.has(importedPkg) && !declaredDeps.has(importedPkg)) {
591
+ const existing = missing.find((m) => m.name === importedPkg);
592
+ if (existing) {
593
+ if (!existing.packages.includes(pkgName)) {
594
+ existing.packages.push(pkgName);
595
+ }
596
+ if (!existing.importedIn.includes(file)) {
597
+ existing.importedIn.push(file);
598
+ }
599
+ } else {
600
+ missing.push({
601
+ name: importedPkg,
602
+ packages: [pkgName],
603
+ importedIn: [file]
604
+ });
605
+ }
606
+ }
607
+ }
608
+ } catch {
609
+ }
610
+ }
611
+ } catch {
612
+ }
613
+ }
614
+ return missing;
615
+ }
616
+ async function analyzeDependencies(rootDir) {
617
+ const [duplicates, unused, missing] = await Promise.all([
618
+ analyzeDuplicateDependencies(rootDir),
619
+ analyzeUnusedDependencies(rootDir),
620
+ analyzeMissingDependencies(rootDir)
621
+ ]);
622
+ return {
623
+ duplicates,
624
+ unused,
625
+ missing
626
+ };
627
+ }
628
+ var execAsync = promisify(exec);
629
+ function findPackagesWithBuildScript(rootDir, filter) {
630
+ const packages = [];
631
+ if (!fs8.existsSync(rootDir)) {
632
+ return packages;
633
+ }
634
+ const entries = fs8.readdirSync(rootDir, { withFileTypes: true });
635
+ for (const entry of entries) {
636
+ if (!entry.isDirectory() || !entry.name.startsWith("kb-labs-")) {
637
+ continue;
638
+ }
639
+ const repoPath = path8.join(rootDir, entry.name);
640
+ const packagesDir = path8.join(repoPath, "packages");
641
+ if (!fs8.existsSync(packagesDir)) {
642
+ continue;
643
+ }
644
+ const packageDirs = fs8.readdirSync(packagesDir, { withFileTypes: true });
645
+ for (const pkgDir of packageDirs) {
646
+ if (!pkgDir.isDirectory()) {
647
+ continue;
648
+ }
649
+ const packageJsonPath = path8.join(packagesDir, pkgDir.name, "package.json");
650
+ if (fs8.existsSync(packageJsonPath)) {
651
+ const pkgJson = JSON.parse(fs8.readFileSync(packageJsonPath, "utf-8"));
652
+ if (!pkgJson.scripts?.build) {
653
+ continue;
654
+ }
655
+ if (filter && !pkgJson.name.includes(filter) && !pkgDir.name.includes(filter)) {
656
+ continue;
657
+ }
658
+ packages.push(packageJsonPath);
659
+ }
660
+ }
661
+ }
662
+ return packages;
663
+ }
664
+ function isDistStale(packageDir) {
665
+ const distFile = path8.join(packageDir, "dist/index.js");
666
+ const srcDir = path8.join(packageDir, "src");
667
+ if (!fs8.existsSync(distFile)) {
668
+ return { stale: false };
669
+ }
670
+ if (!fs8.existsSync(srcDir)) {
671
+ return { stale: false };
672
+ }
673
+ const distMtime = fs8.statSync(distFile).mtime.getTime();
674
+ let newestSrcMtime = 0;
675
+ function walkDir(dir) {
676
+ const entries = fs8.readdirSync(dir, { withFileTypes: true });
677
+ for (const entry of entries) {
678
+ const fullPath = path8.join(dir, entry.name);
679
+ if (entry.isDirectory()) {
680
+ walkDir(fullPath);
681
+ } else if (/\.(ts|tsx|js|jsx)$/.test(entry.name)) {
682
+ const mtime = fs8.statSync(fullPath).mtime.getTime();
683
+ if (mtime > newestSrcMtime) {
684
+ newestSrcMtime = mtime;
685
+ }
686
+ }
687
+ }
688
+ }
689
+ walkDir(srcDir);
690
+ return {
691
+ stale: newestSrcMtime > distMtime,
692
+ distMtime,
693
+ srcMtime: newestSrcMtime
694
+ };
695
+ }
696
+ async function tryBuildPackage(packageDir, packageName, timeout) {
697
+ try {
698
+ await execAsync("pnpm run build", {
699
+ cwd: packageDir,
700
+ timeout,
701
+ encoding: "utf-8"
702
+ });
703
+ return { success: true };
704
+ } catch (err) {
705
+ return {
706
+ success: false,
707
+ error: err.stderr?.trim() || err.message,
708
+ exitCode: err.code || 1
709
+ };
710
+ }
711
+ }
712
+ async function checkBuilds(rootDir, options = {}) {
713
+ const startTime = Date.now();
714
+ const timeout = options.timeout || 3e4;
715
+ const packagePaths = findPackagesWithBuildScript(rootDir, options.packageFilter);
716
+ const result = {
717
+ totalPackages: packagePaths.length,
718
+ passing: 0,
719
+ failing: 0,
720
+ failures: [],
721
+ staleBuilds: [],
722
+ duration: 0
723
+ };
724
+ for (const pkgPath of packagePaths) {
725
+ const pkgJson = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
726
+ const packageName = pkgJson.name;
727
+ const packageDir = path8.dirname(pkgPath);
728
+ const staleCheck = isDistStale(packageDir);
729
+ if (staleCheck.stale) {
730
+ result.staleBuilds.push({
731
+ package: packageName,
732
+ distMtime: staleCheck.distMtime,
733
+ srcMtime: staleCheck.srcMtime
734
+ });
735
+ }
736
+ const buildResult = await tryBuildPackage(packageDir, packageName, timeout);
737
+ if (buildResult.success) {
738
+ result.passing++;
739
+ } else {
740
+ result.failing++;
741
+ result.failures.push({
742
+ package: packageName,
743
+ error: buildResult.error,
744
+ exitCode: buildResult.exitCode
745
+ });
746
+ }
747
+ }
748
+ result.duration = Date.now() - startTime;
749
+ return result;
750
+ }
751
+ function findPackagesWithTsConfig(rootDir, filter) {
752
+ const packages = [];
753
+ if (!fs8.existsSync(rootDir)) {
754
+ return packages;
755
+ }
756
+ const entries = fs8.readdirSync(rootDir, { withFileTypes: true });
757
+ for (const entry of entries) {
758
+ if (!entry.isDirectory() || !entry.name.startsWith("kb-labs-")) {
759
+ continue;
760
+ }
761
+ const repoPath = path8.join(rootDir, entry.name);
762
+ const packagesDir = path8.join(repoPath, "packages");
763
+ if (!fs8.existsSync(packagesDir)) {
764
+ continue;
765
+ }
766
+ const packageDirs = fs8.readdirSync(packagesDir, { withFileTypes: true });
767
+ for (const pkgDir of packageDirs) {
768
+ if (!pkgDir.isDirectory()) {
769
+ continue;
770
+ }
771
+ const packageJsonPath = path8.join(packagesDir, pkgDir.name, "package.json");
772
+ const tsconfigPath = path8.join(packagesDir, pkgDir.name, "tsconfig.json");
773
+ if (fs8.existsSync(packageJsonPath) && fs8.existsSync(tsconfigPath)) {
774
+ const pkgJson = JSON.parse(fs8.readFileSync(packageJsonPath, "utf-8"));
775
+ if (filter && !pkgJson.name.includes(filter) && !pkgDir.name.includes(filter)) {
776
+ continue;
777
+ }
778
+ packages.push({
779
+ name: pkgJson.name,
780
+ dir: path8.dirname(packageJsonPath),
781
+ tsconfigPath
782
+ });
783
+ }
784
+ }
785
+ }
786
+ return packages;
787
+ }
788
+ function createProgram(packageDir, tsconfigPath) {
789
+ try {
790
+ const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
791
+ if (configFile.error) {
792
+ return null;
793
+ }
794
+ const parsedConfig = ts.parseJsonConfigFileContent(
795
+ configFile.config,
796
+ ts.sys,
797
+ packageDir
798
+ );
799
+ if (parsedConfig.errors.length > 0) {
800
+ return null;
801
+ }
802
+ return ts.createProgram({
803
+ rootNames: parsedConfig.fileNames,
804
+ options: parsedConfig.options
805
+ });
806
+ } catch (err) {
807
+ return null;
808
+ }
809
+ }
810
+ function analyzePackageTypes(program) {
811
+ const diagnostics = ts.getPreEmitDiagnostics(program);
812
+ let errors = 0;
813
+ let warnings = 0;
814
+ for (const diagnostic of diagnostics) {
815
+ if (!diagnostic.file) {
816
+ continue;
817
+ }
818
+ if (diagnostic.category === ts.DiagnosticCategory.Error) {
819
+ errors++;
820
+ } else {
821
+ warnings++;
822
+ }
823
+ }
824
+ return { errors, warnings };
825
+ }
826
+ function calculateTypeCoverage(program) {
827
+ const checker = program.getTypeChecker();
828
+ const sourceFiles = program.getSourceFiles().filter(
829
+ (sf) => !sf.isDeclarationFile && !sf.fileName.includes("node_modules")
830
+ );
831
+ let totalSymbols = 0;
832
+ let typedSymbols = 0;
833
+ let anyCount = 0;
834
+ let tsIgnoreCount = 0;
835
+ for (const sourceFile of sourceFiles) {
836
+ const text = sourceFile.getFullText();
837
+ const tsIgnoreMatches = text.match(/@ts-ignore/g);
838
+ tsIgnoreCount += tsIgnoreMatches ? tsIgnoreMatches.length : 0;
839
+ ts.forEachChild(sourceFile, function visit(node) {
840
+ if (ts.isTypeNode(node)) {
841
+ totalSymbols++;
842
+ const type = checker.getTypeAtLocation(node);
843
+ if (type.flags & ts.TypeFlags.Any) {
844
+ anyCount++;
845
+ } else {
846
+ typedSymbols++;
847
+ }
848
+ }
849
+ ts.forEachChild(node, visit);
850
+ });
851
+ }
852
+ const coverage = totalSymbols > 0 ? typedSymbols / totalSymbols * 100 : 100;
853
+ return {
854
+ coverage: Math.round(coverage * 10) / 10,
855
+ totalSymbols,
856
+ typedSymbols,
857
+ anyCount,
858
+ tsIgnoreCount
859
+ };
860
+ }
861
+ async function analyzeTypes(rootDir, options = {}) {
862
+ const startTime = Date.now();
863
+ const packages = findPackagesWithTsConfig(rootDir, options.packageFilter);
864
+ const result = {
865
+ totalPackages: packages.length,
866
+ packagesWithErrors: 0,
867
+ totalErrors: 0,
868
+ totalWarnings: 0,
869
+ avgCoverage: 0,
870
+ packages: [],
871
+ duration: 0
872
+ };
873
+ for (const { name, dir, tsconfigPath } of packages) {
874
+ const program = createProgram(dir, tsconfigPath);
875
+ if (!program) {
876
+ continue;
877
+ }
878
+ const { errors, warnings } = analyzePackageTypes(program);
879
+ const coverage = calculateTypeCoverage(program);
880
+ result.totalErrors += errors;
881
+ result.totalWarnings += warnings;
882
+ if (errors > 0) {
883
+ result.packagesWithErrors++;
884
+ }
885
+ result.packages.push({
886
+ name,
887
+ errors,
888
+ warnings,
889
+ coverage: coverage.coverage,
890
+ anyCount: coverage.anyCount,
891
+ tsIgnoreCount: coverage.tsIgnoreCount
892
+ });
893
+ }
894
+ if (result.packages.length > 0) {
895
+ result.avgCoverage = result.packages.reduce((sum, p) => sum + p.coverage, 0) / result.packages.length;
896
+ result.avgCoverage = Math.round(result.avgCoverage * 10) / 10;
897
+ }
898
+ result.duration = Date.now() - startTime;
899
+ return result;
900
+ }
901
+ var execAsync2 = promisify(exec);
902
+ function findPackagesWithTests(rootDir, filter) {
903
+ const packages = [];
904
+ if (!fs8.existsSync(rootDir)) {
905
+ return packages;
906
+ }
907
+ const entries = fs8.readdirSync(rootDir, { withFileTypes: true });
908
+ for (const entry of entries) {
909
+ if (!entry.isDirectory() || !entry.name.startsWith("kb-labs-")) {
910
+ continue;
911
+ }
912
+ const repoPath = path8.join(rootDir, entry.name);
913
+ const packagesDir = path8.join(repoPath, "packages");
914
+ if (!fs8.existsSync(packagesDir)) {
915
+ continue;
916
+ }
917
+ const packageDirs = fs8.readdirSync(packagesDir, { withFileTypes: true });
918
+ for (const pkgDir of packageDirs) {
919
+ if (!pkgDir.isDirectory()) {
920
+ continue;
921
+ }
922
+ const packageJsonPath = path8.join(packagesDir, pkgDir.name, "package.json");
923
+ if (fs8.existsSync(packageJsonPath)) {
924
+ const pkgJson = JSON.parse(fs8.readFileSync(packageJsonPath, "utf-8"));
925
+ if (filter && !pkgJson.name.includes(filter) && !pkgDir.name.includes(filter)) {
926
+ continue;
927
+ }
928
+ packages.push({
929
+ name: pkgJson.name,
930
+ dir: path8.dirname(packageJsonPath),
931
+ hasTestScript: !!pkgJson.scripts?.test
932
+ });
933
+ }
934
+ }
935
+ }
936
+ return packages;
937
+ }
938
+ async function runPackageTests(packageDir, packageName, timeout) {
939
+ try {
940
+ const { stdout, stderr } = await execAsync2("pnpm test", {
941
+ cwd: packageDir,
942
+ timeout,
943
+ env: { ...process.env, CI: "true" }
944
+ // CI mode for non-interactive tests
945
+ });
946
+ return {
947
+ success: true,
948
+ exitCode: 0,
949
+ output: (stdout || "") + (stderr || "")
950
+ };
951
+ } catch (err) {
952
+ return {
953
+ success: false,
954
+ exitCode: err.code || 1,
955
+ error: err.message || "Test execution failed",
956
+ output: (err.stdout || "") + (err.stderr || "")
957
+ };
958
+ }
959
+ }
960
+ function parseTestOutput(output) {
961
+ const vitestMatch = output.match(/Test Files\s+(\d+)\s+passed/);
962
+ const jestMatch = output.match(/Tests:\s+(\d+)\s+passed,\s+(\d+)\s+total/);
963
+ const jestFailMatch = output.match(/(\d+)\s+failed,\s+(\d+)\s+passed,\s+(\d+)\s+total/);
964
+ if (jestFailMatch && jestFailMatch[1] && jestFailMatch[2] && jestFailMatch[3]) {
965
+ return {
966
+ failed: parseInt(jestFailMatch[1], 10),
967
+ passed: parseInt(jestFailMatch[2], 10),
968
+ total: parseInt(jestFailMatch[3], 10)
969
+ };
970
+ }
971
+ if (jestMatch && jestMatch[1] && jestMatch[2]) {
972
+ return {
973
+ passed: parseInt(jestMatch[1], 10),
974
+ total: parseInt(jestMatch[2], 10)
975
+ };
976
+ }
977
+ if (vitestMatch && vitestMatch[1]) {
978
+ return {
979
+ passed: parseInt(vitestMatch[1], 10),
980
+ total: parseInt(vitestMatch[1], 10)
981
+ };
982
+ }
983
+ return {};
984
+ }
985
+ function readCoverage(packageDir) {
986
+ const coveragePath = path8.join(packageDir, "coverage", "coverage-summary.json");
987
+ if (!fs8.existsSync(coveragePath)) {
988
+ return null;
989
+ }
990
+ try {
991
+ const coverageData = JSON.parse(fs8.readFileSync(coveragePath, "utf-8"));
992
+ const total = coverageData.total;
993
+ return {
994
+ lines: total?.lines?.pct || 0,
995
+ statements: total?.statements?.pct || 0,
996
+ functions: total?.functions?.pct || 0,
997
+ branches: total?.branches?.pct || 0
998
+ };
999
+ } catch {
1000
+ return null;
1001
+ }
1002
+ }
1003
+ async function runTests(rootDir, options = {}) {
1004
+ const startTime = Date.now();
1005
+ const timeout = options.timeout || 6e4;
1006
+ const packages = findPackagesWithTests(rootDir, options.packageFilter);
1007
+ const result = {
1008
+ totalPackages: packages.length,
1009
+ passing: 0,
1010
+ failing: 0,
1011
+ skipped: 0,
1012
+ failures: [],
1013
+ summary: {
1014
+ totalTests: 0,
1015
+ passedTests: 0,
1016
+ failedTests: 0
1017
+ },
1018
+ coverage: {
1019
+ avgCoverage: 0,
1020
+ packages: []
1021
+ },
1022
+ duration: 0
1023
+ };
1024
+ for (const { name, dir, hasTestScript } of packages) {
1025
+ if (!hasTestScript) {
1026
+ result.skipped++;
1027
+ continue;
1028
+ }
1029
+ if (options.coverageOnly) {
1030
+ const coverage = readCoverage(dir);
1031
+ if (coverage) {
1032
+ result.coverage.packages.push({
1033
+ name,
1034
+ ...coverage
1035
+ });
1036
+ }
1037
+ continue;
1038
+ }
1039
+ const testResult = await runPackageTests(dir, name, timeout);
1040
+ if (testResult.success) {
1041
+ result.passing++;
1042
+ if (testResult.output) {
1043
+ const counts = parseTestOutput(testResult.output);
1044
+ if (counts.total) {
1045
+ result.summary.totalTests += counts.total;
1046
+ }
1047
+ if (counts.passed) {
1048
+ result.summary.passedTests += counts.passed;
1049
+ }
1050
+ if (counts.failed) {
1051
+ result.summary.failedTests += counts.failed || 0;
1052
+ }
1053
+ }
1054
+ } else {
1055
+ result.failing++;
1056
+ const counts = testResult.output ? parseTestOutput(testResult.output) : {};
1057
+ result.failures.push({
1058
+ package: name,
1059
+ error: testResult.error || "Unknown error",
1060
+ exitCode: testResult.exitCode,
1061
+ failedTests: counts.failed,
1062
+ totalTests: counts.total
1063
+ });
1064
+ if (counts.total) {
1065
+ result.summary.totalTests += counts.total;
1066
+ }
1067
+ if (counts.passed) {
1068
+ result.summary.passedTests += counts.passed || 0;
1069
+ }
1070
+ if (counts.failed) {
1071
+ result.summary.failedTests += counts.failed;
1072
+ }
1073
+ }
1074
+ if (options.withCoverage) {
1075
+ const coverage = readCoverage(dir);
1076
+ if (coverage) {
1077
+ result.coverage.packages.push({
1078
+ name,
1079
+ ...coverage
1080
+ });
1081
+ }
1082
+ }
1083
+ }
1084
+ if (result.coverage.packages.length > 0) {
1085
+ const totalLines = result.coverage.packages.reduce((sum, pkg) => sum + pkg.lines, 0);
1086
+ result.coverage.avgCoverage = totalLines / result.coverage.packages.length;
1087
+ }
1088
+ result.duration = Date.now() - startTime;
1089
+ return result;
1090
+ }
1091
+ var CONFIG_FILE_PATTERNS = [
1092
+ "tsup.config.ts",
1093
+ "tsup.config.mts",
1094
+ "tsup.config.js",
1095
+ "tsup.config.mjs",
1096
+ "tsup.bin.config.ts",
1097
+ "tsup.lib.config.ts",
1098
+ "vitest.config.ts",
1099
+ "vitest.config.mts",
1100
+ "vitest.config.js",
1101
+ "jest.config.ts",
1102
+ "jest.config.js",
1103
+ "eslint.config.js",
1104
+ "eslint.config.mjs",
1105
+ "vitest.setup.ts",
1106
+ "vitest.setup.js"
1107
+ ];
1108
+ async function collectEntryPoints(packageDir, packageJson) {
1109
+ const entryFiles = /* @__PURE__ */ new Set();
1110
+ const aliveByConvention = /* @__PURE__ */ new Set();
1111
+ const warnings = [];
1112
+ let failOpen = false;
1113
+ collectPackageJsonEntries(packageDir, packageJson, entryFiles);
1114
+ const tsupOk = await collectTsupEntries(packageDir, entryFiles, warnings);
1115
+ if (!tsupOk) {
1116
+ failOpen = true;
1117
+ }
1118
+ collectManifestHandlers(packageDir, entryFiles, warnings);
1119
+ await collectDynamicImportTargets(packageDir, entryFiles);
1120
+ await collectTestFiles(packageDir, aliveByConvention);
1121
+ collectConfigFiles(packageDir, aliveByConvention);
1122
+ if (entryFiles.size === 0) {
1123
+ const defaultEntry = path8.join(packageDir, "src", "index.ts");
1124
+ if (fs8.existsSync(defaultEntry)) {
1125
+ entryFiles.add(defaultEntry);
1126
+ warnings.push("No entry points found, using src/index.ts as default");
1127
+ }
1128
+ }
1129
+ return { entryFiles, aliveByConvention, warnings, failOpen };
1130
+ }
1131
+ function distPathToSrcPath(distPath, packageDir) {
1132
+ let normalized = distPath.replace(/^\.?\/?/, "");
1133
+ if (normalized.startsWith("dist/") || normalized.startsWith("dist\\")) {
1134
+ normalized = "src/" + normalized.slice(5);
1135
+ }
1136
+ normalized = normalized.replace(/\.d\.ts$/, ".ts").replace(/\.js$/, ".ts").replace(/\.mjs$/, ".ts").replace(/\.cjs$/, ".ts");
1137
+ const absolute = path8.resolve(packageDir, normalized);
1138
+ if (fs8.existsSync(absolute)) {
1139
+ return absolute;
1140
+ }
1141
+ const indexPath = path8.join(absolute.replace(/\.ts$/, ""), "index.ts");
1142
+ if (fs8.existsSync(indexPath)) {
1143
+ return indexPath;
1144
+ }
1145
+ return null;
1146
+ }
1147
+ function collectPackageJsonEntries(packageDir, pkgJson, entryFiles, warnings) {
1148
+ for (const field of ["main", "module", "types", "typings"]) {
1149
+ const value = pkgJson[field];
1150
+ if (typeof value === "string") {
1151
+ const srcPath = distPathToSrcPath(value, packageDir);
1152
+ if (srcPath) {
1153
+ entryFiles.add(srcPath);
1154
+ }
1155
+ }
1156
+ }
1157
+ const bin = pkgJson["bin"];
1158
+ if (typeof bin === "string") {
1159
+ const srcPath = distPathToSrcPath(bin, packageDir);
1160
+ if (srcPath) {
1161
+ entryFiles.add(srcPath);
1162
+ }
1163
+ } else if (bin && typeof bin === "object") {
1164
+ for (const value of Object.values(bin)) {
1165
+ if (typeof value === "string") {
1166
+ const srcPath = distPathToSrcPath(value, packageDir);
1167
+ if (srcPath) {
1168
+ entryFiles.add(srcPath);
1169
+ }
1170
+ }
1171
+ }
1172
+ }
1173
+ const exports$1 = pkgJson["exports"];
1174
+ if (exports$1 && typeof exports$1 === "object") {
1175
+ collectExportsEntries(
1176
+ packageDir,
1177
+ exports$1,
1178
+ entryFiles);
1179
+ }
1180
+ }
1181
+ function collectExportsEntries(packageDir, exports$1, entryFiles, _warnings) {
1182
+ for (const [key, value] of Object.entries(exports$1)) {
1183
+ if (key.includes("*")) {
1184
+ continue;
1185
+ }
1186
+ const importPath = extractImportPath(value);
1187
+ if (importPath) {
1188
+ const srcPath = distPathToSrcPath(importPath, packageDir);
1189
+ if (srcPath) {
1190
+ entryFiles.add(srcPath);
1191
+ }
1192
+ }
1193
+ }
1194
+ }
1195
+ function extractImportPath(value) {
1196
+ if (typeof value === "string") {
1197
+ return value;
1198
+ }
1199
+ if (value && typeof value === "object") {
1200
+ const obj = value;
1201
+ for (const key of ["import", "default", "require"]) {
1202
+ if (typeof obj[key] === "string") {
1203
+ return obj[key];
1204
+ }
1205
+ }
1206
+ }
1207
+ return null;
1208
+ }
1209
+ async function collectTsupEntries(packageDir, entryFiles, warnings) {
1210
+ const tsupConfigs = [
1211
+ "tsup.config.ts",
1212
+ "tsup.config.mts",
1213
+ "tsup.config.js",
1214
+ "tsup.config.mjs",
1215
+ "tsup.bin.config.ts",
1216
+ "tsup.lib.config.ts"
1217
+ ];
1218
+ let foundAny = false;
1219
+ for (const configName of tsupConfigs) {
1220
+ const configPath = path8.join(packageDir, configName);
1221
+ if (!fs8.existsSync(configPath)) {
1222
+ continue;
1223
+ }
1224
+ foundAny = true;
1225
+ try {
1226
+ const content = fs8.readFileSync(configPath, "utf-8");
1227
+ const entries = parseTsupEntries(content);
1228
+ for (const entry of entries) {
1229
+ if (entry.includes("*") || entry.includes("{")) {
1230
+ const expanded = await globby3(entry, {
1231
+ cwd: packageDir,
1232
+ absolute: true
1233
+ });
1234
+ for (const file of expanded) {
1235
+ entryFiles.add(file);
1236
+ }
1237
+ } else {
1238
+ const absolute = path8.resolve(packageDir, entry);
1239
+ if (fs8.existsSync(absolute)) {
1240
+ entryFiles.add(absolute);
1241
+ }
1242
+ }
1243
+ }
1244
+ } catch {
1245
+ warnings.push(`Failed to parse ${configName}`);
1246
+ return false;
1247
+ }
1248
+ }
1249
+ if (!foundAny) {
1250
+ return true;
1251
+ }
1252
+ return true;
1253
+ }
1254
+ function parseTsupEntries(content) {
1255
+ const entries = [];
1256
+ const singleMatch = content.match(/entry\s*:\s*['"]([^'"]+)['"]/);
1257
+ if (singleMatch && singleMatch[1]) {
1258
+ entries.push(singleMatch[1]);
1259
+ return entries;
1260
+ }
1261
+ const entryBlockMatch = content.match(
1262
+ /entry\s*:\s*[\[{]([\s\S]*?)[\]}]/
1263
+ );
1264
+ if (entryBlockMatch && entryBlockMatch[1]) {
1265
+ const block = entryBlockMatch[1];
1266
+ const pathPattern = /['"]([^'"]*?src\/[^'"]+)['"]/g;
1267
+ let match;
1268
+ while ((match = pathPattern.exec(block)) !== null) {
1269
+ if (match[1]) {
1270
+ entries.push(match[1]);
1271
+ }
1272
+ }
1273
+ return entries;
1274
+ }
1275
+ return entries;
1276
+ }
1277
+ function collectManifestHandlers(packageDir, entryFiles, warnings) {
1278
+ const manifestPath = path8.join(packageDir, "src", "manifest.ts");
1279
+ if (!fs8.existsSync(manifestPath)) {
1280
+ return;
1281
+ }
1282
+ try {
1283
+ const content = fs8.readFileSync(manifestPath, "utf-8");
1284
+ const handlers = parseManifestHandlers(content);
1285
+ for (const handlerPath of handlers) {
1286
+ const pathOnly = handlerPath.split("#")[0] ?? handlerPath;
1287
+ const srcRelative = "src/" + pathOnly.replace(/^\.\//, "").replace(/\.js$/, ".ts");
1288
+ const absolute = path8.resolve(packageDir, srcRelative);
1289
+ if (fs8.existsSync(absolute)) {
1290
+ entryFiles.add(absolute);
1291
+ }
1292
+ }
1293
+ entryFiles.add(manifestPath);
1294
+ } catch {
1295
+ warnings.push("Failed to parse manifest.ts");
1296
+ }
1297
+ }
1298
+ function parseManifestHandlers(content) {
1299
+ const handlers = [];
1300
+ const seen = /* @__PURE__ */ new Set();
1301
+ const pattern = /(?:handler|handlerPath)\s*:\s*['"]([^'"]+)['"]/g;
1302
+ let match;
1303
+ while ((match = pattern.exec(content)) !== null) {
1304
+ const matchValue = match[1];
1305
+ if (!matchValue) {
1306
+ continue;
1307
+ }
1308
+ const raw = matchValue.split("#")[0] ?? matchValue;
1309
+ if (!seen.has(raw)) {
1310
+ seen.add(raw);
1311
+ handlers.push(raw);
1312
+ }
1313
+ }
1314
+ return handlers;
1315
+ }
1316
+ async function collectDynamicImportTargets(packageDir, entryFiles, _warnings) {
1317
+ const srcDir = path8.join(packageDir, "src");
1318
+ if (!fs8.existsSync(srcDir)) {
1319
+ return;
1320
+ }
1321
+ const sourceFiles = await globby3("**/*.{ts,tsx}", {
1322
+ cwd: srcDir,
1323
+ absolute: true,
1324
+ ignore: ["**/*.test.ts", "**/*.spec.ts", "**/__tests__/**"]
1325
+ });
1326
+ const dynamicImportPattern = /import\s*\(\s*['"](\.[^'"]+)['"]\s*\)/g;
1327
+ for (const file of sourceFiles) {
1328
+ try {
1329
+ const content = fs8.readFileSync(file, "utf-8");
1330
+ let match;
1331
+ while ((match = dynamicImportPattern.exec(content)) !== null) {
1332
+ const importPath = match[1];
1333
+ if (!importPath) {
1334
+ continue;
1335
+ }
1336
+ const resolved = resolveFilePath(importPath, file);
1337
+ if (resolved) {
1338
+ entryFiles.add(resolved);
1339
+ }
1340
+ }
1341
+ } catch {
1342
+ }
1343
+ }
1344
+ }
1345
+ async function collectTestFiles(packageDir, aliveByConvention) {
1346
+ const srcDir = path8.join(packageDir, "src");
1347
+ if (!fs8.existsSync(srcDir)) {
1348
+ return;
1349
+ }
1350
+ const testFiles = await globby3(
1351
+ ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx", "**/__tests__/**/*.ts"],
1352
+ { cwd: srcDir, absolute: true }
1353
+ );
1354
+ for (const file of testFiles) {
1355
+ aliveByConvention.add(file);
1356
+ }
1357
+ }
1358
+ function collectConfigFiles(packageDir, aliveByConvention) {
1359
+ for (const pattern of CONFIG_FILE_PATTERNS) {
1360
+ const configPath = path8.join(packageDir, pattern);
1361
+ if (fs8.existsSync(configPath)) {
1362
+ aliveByConvention.add(configPath);
1363
+ }
1364
+ }
1365
+ }
1366
+ function resolveFilePath(importPath, fromFile) {
1367
+ const dir = path8.dirname(fromFile);
1368
+ const base = path8.resolve(dir, importPath);
1369
+ const tsPath = base.replace(/\.js$/, ".ts");
1370
+ if (fs8.existsSync(tsPath)) {
1371
+ return tsPath;
1372
+ }
1373
+ if (fs8.existsSync(base)) {
1374
+ return base;
1375
+ }
1376
+ if (fs8.existsSync(base + ".ts")) {
1377
+ return base + ".ts";
1378
+ }
1379
+ if (fs8.existsSync(base + ".tsx")) {
1380
+ return base + ".tsx";
1381
+ }
1382
+ if (fs8.existsSync(path8.join(base, "index.ts"))) {
1383
+ return path8.join(base, "index.ts");
1384
+ }
1385
+ return null;
1386
+ }
1387
+ var IMPORT_PATTERNS = [
1388
+ // Static imports: import X from 'module', import { X } from 'module', import 'module'
1389
+ /import\s+(?:[\w*{}\n\r\t, ]+\s+from\s+)?['"]([^'"]+)['"]/g,
1390
+ // Dynamic imports (string literal): import('./module'), import("./module")
1391
+ /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
1392
+ // CommonJS require: require('./module'), require("./module")
1393
+ /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
1394
+ // Re-exports: export { X } from 'module', export * from 'module'
1395
+ /export\s+(?:[\w*{}\n\r\t, ]+\s+)?from\s+['"]([^'"]+)['"]/g
1396
+ ];
1397
+ var RESOLVE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
1398
+ function extractFileImports(content) {
1399
+ const imports = [];
1400
+ for (const pattern of IMPORT_PATTERNS) {
1401
+ pattern.lastIndex = 0;
1402
+ let match;
1403
+ while ((match = pattern.exec(content)) !== null) {
1404
+ const specifier = match[1];
1405
+ if (specifier) {
1406
+ imports.push(specifier);
1407
+ }
1408
+ }
1409
+ }
1410
+ return imports;
1411
+ }
1412
+ function isRelativeImport(specifier) {
1413
+ return specifier.startsWith("./") || specifier.startsWith("../");
1414
+ }
1415
+ function resolveRelativeImport(specifier, sourceFile) {
1416
+ const dir = path8.dirname(sourceFile);
1417
+ const basePath = path8.resolve(dir, specifier);
1418
+ if (specifier.endsWith(".js")) {
1419
+ const tsPath = basePath.slice(0, -3) + ".ts";
1420
+ if (fs8.existsSync(tsPath)) {
1421
+ return tsPath;
1422
+ }
1423
+ const tsxPath = basePath.slice(0, -3) + ".tsx";
1424
+ if (fs8.existsSync(tsxPath)) {
1425
+ return tsxPath;
1426
+ }
1427
+ }
1428
+ if (fs8.existsSync(basePath) && fs8.statSync(basePath).isFile()) {
1429
+ return basePath;
1430
+ }
1431
+ for (const ext of RESOLVE_EXTENSIONS) {
1432
+ const withExt = basePath + ext;
1433
+ if (fs8.existsSync(withExt)) {
1434
+ return withExt;
1435
+ }
1436
+ }
1437
+ if (fs8.existsSync(basePath) && fs8.statSync(basePath).isDirectory()) {
1438
+ for (const ext of RESOLVE_EXTENSIONS) {
1439
+ const indexPath = path8.join(basePath, "index" + ext);
1440
+ if (fs8.existsSync(indexPath)) {
1441
+ return indexPath;
1442
+ }
1443
+ }
1444
+ }
1445
+ for (const ext of RESOLVE_EXTENSIONS) {
1446
+ const indexPath = path8.join(basePath, "index" + ext);
1447
+ if (fs8.existsSync(indexPath)) {
1448
+ return indexPath;
1449
+ }
1450
+ }
1451
+ return null;
1452
+ }
1453
+ function buildFileImportGraph(sourceFiles) {
1454
+ const graph = /* @__PURE__ */ new Map();
1455
+ for (const file of sourceFiles) {
1456
+ const deps = /* @__PURE__ */ new Set();
1457
+ graph.set(file, deps);
1458
+ try {
1459
+ const content = fs8.readFileSync(file, "utf-8");
1460
+ const imports = extractFileImports(content);
1461
+ for (const specifier of imports) {
1462
+ if (!isRelativeImport(specifier)) {
1463
+ continue;
1464
+ }
1465
+ const resolved = resolveRelativeImport(specifier, file);
1466
+ if (resolved) {
1467
+ deps.add(resolved);
1468
+ }
1469
+ }
1470
+ } catch {
1471
+ }
1472
+ }
1473
+ return graph;
1474
+ }
1475
+ function findReachableFiles(entryPoints, importGraph) {
1476
+ const visited = /* @__PURE__ */ new Set();
1477
+ const queue = [...entryPoints];
1478
+ while (queue.length > 0) {
1479
+ const current = queue.shift();
1480
+ if (visited.has(current)) {
1481
+ continue;
1482
+ }
1483
+ visited.add(current);
1484
+ const deps = importGraph.get(current);
1485
+ if (deps) {
1486
+ for (const dep of deps) {
1487
+ if (!visited.has(dep)) {
1488
+ queue.push(dep);
1489
+ }
1490
+ }
1491
+ }
1492
+ }
1493
+ return visited;
1494
+ }
1495
+ function countGraphEdges(graph) {
1496
+ let count = 0;
1497
+ for (const deps of graph.values()) {
1498
+ count += deps.size;
1499
+ }
1500
+ return count;
1501
+ }
1502
+
1503
+ // src/dead-code/scan-dead-files.ts
1504
+ async function scanDeadFiles(rootDir, options) {
1505
+ const startTime = Date.now();
1506
+ const packages = findPackagesInMonorepo(rootDir, options?.packageFilter);
1507
+ const results = [];
1508
+ for (const pkg of packages) {
1509
+ const result = await analyzePackage(pkg);
1510
+ if (result) {
1511
+ results.push(result);
1512
+ }
1513
+ }
1514
+ const summary = calculateSummary(results);
1515
+ return {
1516
+ packages: results,
1517
+ summary,
1518
+ duration: Date.now() - startTime
1519
+ };
1520
+ }
1521
+ function findPackagesInMonorepo(rootDir, filter) {
1522
+ const packages = [];
1523
+ if (!fs8.existsSync(rootDir)) {
1524
+ return packages;
1525
+ }
1526
+ const entries = fs8.readdirSync(rootDir, { withFileTypes: true });
1527
+ for (const entry of entries) {
1528
+ if (!entry.isDirectory() || !entry.name.startsWith("kb-labs-")) {
1529
+ continue;
1530
+ }
1531
+ const repoPath = path8.join(rootDir, entry.name);
1532
+ const packagesDir = path8.join(repoPath, "packages");
1533
+ if (!fs8.existsSync(packagesDir)) {
1534
+ continue;
1535
+ }
1536
+ const packageDirs = fs8.readdirSync(packagesDir, { withFileTypes: true });
1537
+ for (const pkgDir of packageDirs) {
1538
+ if (!pkgDir.isDirectory()) {
1539
+ continue;
1540
+ }
1541
+ const packageJsonPath = path8.join(packagesDir, pkgDir.name, "package.json");
1542
+ if (!fs8.existsSync(packageJsonPath)) {
1543
+ continue;
1544
+ }
1545
+ try {
1546
+ const pkgJson = JSON.parse(fs8.readFileSync(packageJsonPath, "utf-8"));
1547
+ const pkgName = pkgJson.name || pkgDir.name;
1548
+ const packageDir = path8.join(packagesDir, pkgDir.name);
1549
+ const srcDir = path8.join(packageDir, "src");
1550
+ if (!fs8.existsSync(srcDir)) {
1551
+ continue;
1552
+ }
1553
+ if (filter && !pkgName.includes(filter) && !pkgDir.name.includes(filter)) {
1554
+ continue;
1555
+ }
1556
+ packages.push({ packageDir, packageJson: pkgJson, packageName: pkgName });
1557
+ } catch {
1558
+ }
1559
+ }
1560
+ }
1561
+ return packages;
1562
+ }
1563
+ async function analyzePackage(pkg) {
1564
+ const { packageDir, packageJson, packageName } = pkg;
1565
+ const srcDir = path8.join(packageDir, "src");
1566
+ const allSourceFiles = await globby3("**/*.{ts,tsx}", {
1567
+ cwd: srcDir,
1568
+ absolute: true,
1569
+ ignore: ["**/*.d.ts"]
1570
+ });
1571
+ if (allSourceFiles.length === 0) {
1572
+ return null;
1573
+ }
1574
+ const {
1575
+ entryFiles,
1576
+ aliveByConvention,
1577
+ warnings,
1578
+ failOpen
1579
+ } = await collectEntryPoints(packageDir, packageJson);
1580
+ if (failOpen) {
1581
+ return {
1582
+ packageName,
1583
+ packageDir,
1584
+ totalFiles: allSourceFiles.length,
1585
+ aliveFiles: allSourceFiles.length,
1586
+ deadFiles: [],
1587
+ entryPoints: [...entryFiles].map((f) => path8.relative(packageDir, f)),
1588
+ graphEdgeCount: 0,
1589
+ warnings: [...warnings, "FAIL-OPEN: All files treated as alive due to config parse errors"]
1590
+ };
1591
+ }
1592
+ const importGraph = buildFileImportGraph(allSourceFiles);
1593
+ const graphEdgeCount = countGraphEdges(importGraph);
1594
+ const seeds = /* @__PURE__ */ new Set([...entryFiles, ...aliveByConvention]);
1595
+ const reachable = findReachableFiles(seeds, importGraph);
1596
+ const allAlive = /* @__PURE__ */ new Set([...reachable, ...aliveByConvention]);
1597
+ const deadFiles = [];
1598
+ for (const file of allSourceFiles) {
1599
+ if (!allAlive.has(file)) {
1600
+ const stats = safeFileStat(file);
1601
+ deadFiles.push({
1602
+ absolutePath: file,
1603
+ relativePath: path8.relative(packageDir, file),
1604
+ packageName,
1605
+ packageDir,
1606
+ sizeBytes: stats?.size ?? 0
1607
+ });
1608
+ }
1609
+ }
1610
+ deadFiles.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
1611
+ return {
1612
+ packageName,
1613
+ packageDir,
1614
+ totalFiles: allSourceFiles.length,
1615
+ aliveFiles: allSourceFiles.length - deadFiles.length,
1616
+ deadFiles,
1617
+ entryPoints: [...entryFiles].map((f) => path8.relative(packageDir, f)),
1618
+ graphEdgeCount,
1619
+ warnings
1620
+ };
1621
+ }
1622
+ function calculateSummary(results, rootDir) {
1623
+ let totalFiles = 0;
1624
+ let totalDead = 0;
1625
+ let totalDeadBytes = 0;
1626
+ for (const pkg of results) {
1627
+ totalFiles += pkg.totalFiles;
1628
+ totalDead += pkg.deadFiles.length;
1629
+ for (const deadFile of pkg.deadFiles) {
1630
+ totalDeadBytes += deadFile.sizeBytes;
1631
+ }
1632
+ }
1633
+ const emptyDirectories = findPotentialEmptyDirs(results);
1634
+ return {
1635
+ totalPackages: results.length,
1636
+ totalFiles,
1637
+ totalAlive: totalFiles - totalDead,
1638
+ totalDead,
1639
+ totalDeadBytes,
1640
+ emptyDirectories
1641
+ };
1642
+ }
1643
+ function findPotentialEmptyDirs(results, _rootDir) {
1644
+ const emptyDirs = [];
1645
+ for (const pkg of results) {
1646
+ if (pkg.deadFiles.length === 0) {
1647
+ continue;
1648
+ }
1649
+ const deadByDir = /* @__PURE__ */ new Map();
1650
+ for (const deadFile of pkg.deadFiles) {
1651
+ const dir = path8.dirname(deadFile.absolutePath);
1652
+ deadByDir.set(dir, (deadByDir.get(dir) ?? 0) + 1);
1653
+ }
1654
+ for (const [dir, deadCount] of deadByDir) {
1655
+ try {
1656
+ const allFiles = fs8.readdirSync(dir);
1657
+ if (allFiles.length === deadCount) {
1658
+ emptyDirs.push(path8.relative(pkg.packageDir, dir));
1659
+ }
1660
+ } catch {
1661
+ }
1662
+ }
1663
+ }
1664
+ return emptyDirs;
1665
+ }
1666
+ function safeFileStat(filePath) {
1667
+ try {
1668
+ return fs8.statSync(filePath);
1669
+ } catch {
1670
+ return null;
1671
+ }
1672
+ }
1673
+ var BACKUP_DIR = ".dead-code-backup";
1674
+ async function removeDeadFiles(rootDir, scanResult, options) {
1675
+ const dryRun = options?.dryRun ?? false;
1676
+ const backupId = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1677
+ const backupPath = path8.join(rootDir, BACKUP_DIR, backupId);
1678
+ const gitSha = safeExec("git rev-parse HEAD", rootDir) ?? "unknown";
1679
+ const gitBranch = safeExec("git rev-parse --abbrev-ref HEAD", rootDir) ?? "unknown";
1680
+ const allDeadFiles = [];
1681
+ for (const pkg of scanResult.packages) {
1682
+ allDeadFiles.push(...pkg.deadFiles);
1683
+ }
1684
+ if (allDeadFiles.length === 0) {
1685
+ return {
1686
+ backupId,
1687
+ backupPath,
1688
+ filesRemoved: 0,
1689
+ bytesRemoved: 0,
1690
+ emptyDirsRemoved: 0,
1691
+ exportsCleanedUp: 0,
1692
+ manifest: {
1693
+ id: backupId,
1694
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1695
+ gitSha,
1696
+ gitBranch,
1697
+ removedFiles: [],
1698
+ removedEmptyDirs: [],
1699
+ cleanedExports: [],
1700
+ totalFilesRemoved: 0,
1701
+ totalBytesRemoved: 0
1702
+ }
1703
+ };
1704
+ }
1705
+ const manifest = {
1706
+ id: backupId,
1707
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1708
+ gitSha,
1709
+ gitBranch,
1710
+ removedFiles: allDeadFiles.map((f) => ({
1711
+ originalPath: f.absolutePath,
1712
+ backupPath: path8.relative(rootDir, f.absolutePath),
1713
+ packageName: f.packageName,
1714
+ sizeBytes: f.sizeBytes
1715
+ })),
1716
+ removedEmptyDirs: [],
1717
+ cleanedExports: [],
1718
+ totalFilesRemoved: allDeadFiles.length,
1719
+ totalBytesRemoved: allDeadFiles.reduce((sum, f) => sum + f.sizeBytes, 0)
1720
+ };
1721
+ if (dryRun) {
1722
+ return {
1723
+ backupId,
1724
+ backupPath,
1725
+ filesRemoved: allDeadFiles.length,
1726
+ bytesRemoved: manifest.totalBytesRemoved,
1727
+ emptyDirsRemoved: scanResult.summary.emptyDirectories.length,
1728
+ exportsCleanedUp: 0,
1729
+ manifest
1730
+ };
1731
+ }
1732
+ const filesDir = path8.join(backupPath, "files");
1733
+ for (const deadFile of allDeadFiles) {
1734
+ const relPath = path8.relative(rootDir, deadFile.absolutePath);
1735
+ const destPath = path8.join(filesDir, relPath);
1736
+ const destDir = path8.dirname(destPath);
1737
+ fs8.mkdirSync(destDir, { recursive: true });
1738
+ fs8.copyFileSync(deadFile.absolutePath, destPath);
1739
+ }
1740
+ fs8.writeFileSync(
1741
+ path8.join(backupPath, "manifest.json"),
1742
+ JSON.stringify(manifest, null, 2) + "\n"
1743
+ );
1744
+ for (const deadFile of allDeadFiles) {
1745
+ fs8.unlinkSync(deadFile.absolutePath);
1746
+ }
1747
+ const removedDirs = [];
1748
+ const deadFileDirs = new Set(allDeadFiles.map((f) => path8.dirname(f.absolutePath)));
1749
+ for (const dir of deadFileDirs) {
1750
+ removeEmptyDirsUpward(dir, rootDir, removedDirs);
1751
+ }
1752
+ manifest.removedEmptyDirs = removedDirs.map((d) => path8.relative(rootDir, d));
1753
+ const deletedPaths = new Set(allDeadFiles.map((f) => f.absolutePath));
1754
+ let exportsCleanedUp = 0;
1755
+ for (const pkg of scanResult.packages) {
1756
+ if (pkg.deadFiles.length === 0) {
1757
+ continue;
1758
+ }
1759
+ const pkgJsonPath = path8.join(pkg.packageDir, "package.json");
1760
+ const cleaned = cleanPackageJsonExports(pkgJsonPath, deletedPaths, pkg.packageDir);
1761
+ if (cleaned.length > 0) {
1762
+ manifest.cleanedExports.push({
1763
+ packageJsonPath: path8.relative(rootDir, pkgJsonPath),
1764
+ removedExportKeys: cleaned
1765
+ });
1766
+ exportsCleanedUp += cleaned.length;
1767
+ }
1768
+ }
1769
+ fs8.writeFileSync(
1770
+ path8.join(backupPath, "manifest.json"),
1771
+ JSON.stringify(manifest, null, 2) + "\n"
1772
+ );
1773
+ return {
1774
+ backupId,
1775
+ backupPath,
1776
+ filesRemoved: allDeadFiles.length,
1777
+ bytesRemoved: manifest.totalBytesRemoved,
1778
+ emptyDirsRemoved: removedDirs.length,
1779
+ exportsCleanedUp,
1780
+ manifest
1781
+ };
1782
+ }
1783
+ async function restoreFromBackup(rootDir, backupId) {
1784
+ const backupPath = path8.join(rootDir, BACKUP_DIR, backupId);
1785
+ const manifestPath = path8.join(backupPath, "manifest.json");
1786
+ if (!fs8.existsSync(manifestPath)) {
1787
+ throw new Error(`Backup not found: ${backupId}`);
1788
+ }
1789
+ const manifest = JSON.parse(
1790
+ fs8.readFileSync(manifestPath, "utf-8")
1791
+ );
1792
+ let restoredFiles = 0;
1793
+ for (const entry of manifest.removedFiles) {
1794
+ const backupFilePath = path8.join(backupPath, "files", entry.backupPath);
1795
+ if (!fs8.existsSync(backupFilePath)) {
1796
+ continue;
1797
+ }
1798
+ const parentDir = path8.dirname(entry.originalPath);
1799
+ fs8.mkdirSync(parentDir, { recursive: true });
1800
+ fs8.copyFileSync(backupFilePath, entry.originalPath);
1801
+ restoredFiles++;
1802
+ }
1803
+ let restoredExports = 0;
1804
+ for (const exportEntry of manifest.cleanedExports) {
1805
+ const pkgJsonPath = path8.resolve(rootDir, exportEntry.packageJsonPath);
1806
+ if (!fs8.existsSync(pkgJsonPath)) {
1807
+ continue;
1808
+ }
1809
+ restoredExports += exportEntry.removedExportKeys.length;
1810
+ }
1811
+ return { restoredFiles, restoredExports };
1812
+ }
1813
+ function listBackups(rootDir) {
1814
+ const backupDir = path8.join(rootDir, BACKUP_DIR);
1815
+ if (!fs8.existsSync(backupDir)) {
1816
+ return [];
1817
+ }
1818
+ const backups = [];
1819
+ const entries = fs8.readdirSync(backupDir, { withFileTypes: true });
1820
+ for (const entry of entries) {
1821
+ if (!entry.isDirectory()) {
1822
+ continue;
1823
+ }
1824
+ const manifestPath = path8.join(backupDir, entry.name, "manifest.json");
1825
+ if (!fs8.existsSync(manifestPath)) {
1826
+ continue;
1827
+ }
1828
+ try {
1829
+ const manifest = JSON.parse(
1830
+ fs8.readFileSync(manifestPath, "utf-8")
1831
+ );
1832
+ backups.push(manifest);
1833
+ } catch {
1834
+ }
1835
+ }
1836
+ backups.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
1837
+ return backups;
1838
+ }
1839
+ function safeExec(cmd, cwd) {
1840
+ try {
1841
+ return execSync(cmd, { cwd, encoding: "utf-8", timeout: 5e3 }).trim();
1842
+ } catch {
1843
+ return null;
1844
+ }
1845
+ }
1846
+ function removeEmptyDirsUpward(dir, rootBoundary, removed) {
1847
+ let current = dir;
1848
+ while (current !== rootBoundary && current.startsWith(rootBoundary)) {
1849
+ try {
1850
+ const entries = fs8.readdirSync(current);
1851
+ if (entries.length > 0) {
1852
+ break;
1853
+ }
1854
+ fs8.rmdirSync(current);
1855
+ removed.push(current);
1856
+ current = path8.dirname(current);
1857
+ } catch {
1858
+ break;
1859
+ }
1860
+ }
1861
+ }
1862
+ function cleanPackageJsonExports(packageJsonPath, deletedPaths, packageDir) {
1863
+ if (!fs8.existsSync(packageJsonPath)) {
1864
+ return [];
1865
+ }
1866
+ try {
1867
+ const content = fs8.readFileSync(packageJsonPath, "utf-8");
1868
+ const pkgJson = JSON.parse(content);
1869
+ const removedKeys = [];
1870
+ if (!pkgJson.exports || typeof pkgJson.exports !== "object") {
1871
+ return [];
1872
+ }
1873
+ for (const [key, value] of Object.entries(pkgJson.exports)) {
1874
+ if (key.includes("*")) {
1875
+ continue;
1876
+ }
1877
+ const exportPath = extractExportImportPath(value);
1878
+ if (!exportPath) {
1879
+ continue;
1880
+ }
1881
+ const srcPath = distToSrcForExportCheck(exportPath, packageDir);
1882
+ if (srcPath && deletedPaths.has(srcPath)) {
1883
+ delete pkgJson.exports[key];
1884
+ removedKeys.push(key);
1885
+ }
1886
+ }
1887
+ if (removedKeys.length > 0) {
1888
+ fs8.writeFileSync(packageJsonPath, JSON.stringify(pkgJson, null, 2) + "\n");
1889
+ }
1890
+ return removedKeys;
1891
+ } catch {
1892
+ return [];
1893
+ }
1894
+ }
1895
+ function extractExportImportPath(value) {
1896
+ if (typeof value === "string") {
1897
+ return value;
1898
+ }
1899
+ if (value && typeof value === "object") {
1900
+ const obj = value;
1901
+ for (const key of ["import", "default", "require"]) {
1902
+ if (typeof obj[key] === "string") {
1903
+ return obj[key];
1904
+ }
1905
+ }
1906
+ }
1907
+ return null;
1908
+ }
1909
+ function distToSrcForExportCheck(distPath, packageDir) {
1910
+ let normalized = distPath.replace(/^\.?\/?/, "");
1911
+ if (!normalized.startsWith("dist/") && !normalized.startsWith("dist\\")) {
1912
+ return null;
1913
+ }
1914
+ normalized = "src/" + normalized.slice(5);
1915
+ normalized = normalized.replace(/\.d\.ts$/, ".ts").replace(/\.js$/, ".ts").replace(/\.mjs$/, ".ts").replace(/\.cjs$/, ".ts");
1916
+ return path8.resolve(packageDir, normalized);
1917
+ }
1918
+
1919
+ export { analyzeDependencies, analyzeDuplicateDependencies, analyzeMissingDependencies, analyzeTypes, analyzeUnusedDependencies, buildDependencyGraph, buildFileImportGraph, calculateHealth, calculateHealthScore, calculateLinesOfCode, calculateSize, calculateStats, checkBuilds, checkDuplicateDependencies, checkMissingReadmes, collectEntryPoints, countPackages, distPathToSrcPath, extractFileImports, findCircularDependencies, findReachableFiles, formatBytes, getBuildOrderForPackage, getImpactAnalysis, getReverseDependencies, listBackups, parseManifestHandlers, parseTsupEntries, removeDeadFiles, resolveRelativeImport, restoreFromBackup, runTests, scanDeadFiles, scoreToGrade, topologicalSort };
1920
+ //# sourceMappingURL=index.js.map
1921
+ //# sourceMappingURL=index.js.map