@kb-labs/quality-entry 2.14.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/README.md +120 -0
- package/dist/cli/commands/build-order.d.ts +16 -0
- package/dist/cli/commands/build-order.js +113 -0
- package/dist/cli/commands/build-order.js.map +1 -0
- package/dist/cli/commands/check-builds.d.ts +10 -0
- package/dist/cli/commands/check-builds.js +93 -0
- package/dist/cli/commands/check-builds.js.map +1 -0
- package/dist/cli/commands/check-tests.d.ts +10 -0
- package/dist/cli/commands/check-tests.js +114 -0
- package/dist/cli/commands/check-tests.js.map +1 -0
- package/dist/cli/commands/check-types.d.ts +10 -0
- package/dist/cli/commands/check-types.js +108 -0
- package/dist/cli/commands/check-types.js.map +1 -0
- package/dist/cli/commands/cycles.d.ts +17 -0
- package/dist/cli/commands/cycles.js +85 -0
- package/dist/cli/commands/cycles.js.map +1 -0
- package/dist/cli/commands/dead-code.d.ts +10 -0
- package/dist/cli/commands/dead-code.js +217 -0
- package/dist/cli/commands/dead-code.js.map +1 -0
- package/dist/cli/commands/fix-deps.d.ts +28 -0
- package/dist/cli/commands/fix-deps.js +389 -0
- package/dist/cli/commands/fix-deps.js.map +1 -0
- package/dist/cli/commands/flags.d.ts +344 -0
- package/dist/cli/commands/flags.js +298 -0
- package/dist/cli/commands/flags.js.map +1 -0
- package/dist/cli/commands/health.d.ts +10 -0
- package/dist/cli/commands/health.js +210 -0
- package/dist/cli/commands/health.js.map +1 -0
- package/dist/cli/commands/index.d.ts +14 -0
- package/dist/cli/commands/index.js +1747 -0
- package/dist/cli/commands/index.js.map +1 -0
- package/dist/cli/commands/stats.d.ts +10 -0
- package/dist/cli/commands/stats.js +282 -0
- package/dist/cli/commands/stats.js.map +1 -0
- package/dist/cli/commands/visualize.d.ts +26 -0
- package/dist/cli/commands/visualize.js +210 -0
- package/dist/cli/commands/visualize.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +666 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.d.ts +168 -0
- package/dist/manifest.js +666 -0
- package/dist/manifest.js.map +1 -0
- package/dist/rest/handlers/build-order-handler.d.ts +17 -0
- package/dist/rest/handlers/build-order-handler.js +28 -0
- package/dist/rest/handlers/build-order-handler.js.map +1 -0
- package/dist/rest/handlers/builds-handler.d.ts +12 -0
- package/dist/rest/handlers/builds-handler.js +17 -0
- package/dist/rest/handlers/builds-handler.js.map +1 -0
- package/dist/rest/handlers/cycles-handler.d.ts +13 -0
- package/dist/rest/handlers/cycles-handler.js +23 -0
- package/dist/rest/handlers/cycles-handler.js.map +1 -0
- package/dist/rest/handlers/dependencies-handler.d.ts +14 -0
- package/dist/rest/handlers/dependencies-handler.js +19 -0
- package/dist/rest/handlers/dependencies-handler.js.map +1 -0
- package/dist/rest/handlers/graph-handler.d.ts +30 -0
- package/dist/rest/handlers/graph-handler.js +155 -0
- package/dist/rest/handlers/graph-handler.js.map +1 -0
- package/dist/rest/handlers/health-handler.d.ts +15 -0
- package/dist/rest/handlers/health-handler.js +19 -0
- package/dist/rest/handlers/health-handler.js.map +1 -0
- package/dist/rest/handlers/stale-handler.d.ts +30 -0
- package/dist/rest/handlers/stale-handler.js +20 -0
- package/dist/rest/handlers/stale-handler.js.map +1 -0
- package/dist/rest/handlers/stats-handler.d.ts +16 -0
- package/dist/rest/handlers/stats-handler.js +29 -0
- package/dist/rest/handlers/stats-handler.js.map +1 -0
- package/dist/rest/handlers/tests-handler.d.ts +14 -0
- package/dist/rest/handlers/tests-handler.js +19 -0
- package/dist/rest/handlers/tests-handler.js.map +1 -0
- package/dist/rest/handlers/types-handler.d.ts +12 -0
- package/dist/rest/handlers/types-handler.js +17 -0
- package/dist/rest/handlers/types-handler.js.map +1 -0
- package/package.json +98 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { defineCommand } from '@kb-labs/sdk';
|
|
2
|
+
import { CACHE_KEYS } from '@kb-labs/quality-contracts';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
// src/cli/commands/stats.ts
|
|
7
|
+
var stats_default = defineCommand({
|
|
8
|
+
id: "quality:stats",
|
|
9
|
+
description: "Show monorepo statistics and health score",
|
|
10
|
+
handler: {
|
|
11
|
+
async execute(ctx, input) {
|
|
12
|
+
const { ui, platform } = ctx;
|
|
13
|
+
const flags = input.flags ?? input;
|
|
14
|
+
if (!flags.refresh) {
|
|
15
|
+
const cached = await platform.cache.get(CACHE_KEYS.STATS);
|
|
16
|
+
if (cached) {
|
|
17
|
+
outputStats(cached, flags, ui);
|
|
18
|
+
return { exitCode: 0 };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const stats = await collectStats(ctx.cwd);
|
|
22
|
+
if (flags.health) {
|
|
23
|
+
stats.health = calculateHealthScore(stats);
|
|
24
|
+
}
|
|
25
|
+
await platform.cache.set(CACHE_KEYS.STATS, stats, 5 * 60 * 1e3);
|
|
26
|
+
await platform.analytics.track("quality:stats", {
|
|
27
|
+
packages: stats.overview.totalPackages,
|
|
28
|
+
repositories: stats.overview.totalRepositories,
|
|
29
|
+
withHealth: flags.health ?? false
|
|
30
|
+
});
|
|
31
|
+
outputStats(stats, flags, ui);
|
|
32
|
+
return { exitCode: 0 };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
function findPackages(rootDir) {
|
|
37
|
+
const packages = [];
|
|
38
|
+
if (!fs.existsSync(rootDir)) {
|
|
39
|
+
return packages;
|
|
40
|
+
}
|
|
41
|
+
const entries = fs.readdirSync(rootDir, { withFileTypes: true });
|
|
42
|
+
for (const entry of entries) {
|
|
43
|
+
if (!entry.isDirectory() || !entry.name.startsWith("kb-labs-")) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const repoPath = path.join(rootDir, entry.name);
|
|
47
|
+
const packagesDir = path.join(repoPath, "packages");
|
|
48
|
+
if (!fs.existsSync(packagesDir)) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const packageDirs = fs.readdirSync(packagesDir, { withFileTypes: true });
|
|
52
|
+
for (const pkgDir of packageDirs) {
|
|
53
|
+
if (!pkgDir.isDirectory()) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const packageJsonPath = path.join(packagesDir, pkgDir.name, "package.json");
|
|
57
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
58
|
+
packages.push(packageJsonPath);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return packages;
|
|
63
|
+
}
|
|
64
|
+
function calculateSize(packageDir) {
|
|
65
|
+
const srcDir = path.join(packageDir, "src");
|
|
66
|
+
if (!fs.existsSync(srcDir)) {
|
|
67
|
+
return { files: 0, lines: 0, bytes: 0 };
|
|
68
|
+
}
|
|
69
|
+
let files = 0;
|
|
70
|
+
let lines = 0;
|
|
71
|
+
let bytes = 0;
|
|
72
|
+
function walk(dir) {
|
|
73
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
const fullPath = path.join(dir, entry.name);
|
|
76
|
+
if (entry.isDirectory()) {
|
|
77
|
+
walk(fullPath);
|
|
78
|
+
} else if (entry.isFile() && /\.(ts|tsx|js|jsx)$/.test(entry.name)) {
|
|
79
|
+
files++;
|
|
80
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
81
|
+
lines += content.split("\n").length;
|
|
82
|
+
bytes += Buffer.byteLength(content, "utf-8");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
walk(srcDir);
|
|
87
|
+
return { files, lines, bytes };
|
|
88
|
+
}
|
|
89
|
+
async function collectStats(rootDir) {
|
|
90
|
+
const packages = findPackages(rootDir);
|
|
91
|
+
const stats = {
|
|
92
|
+
overview: {
|
|
93
|
+
totalPackages: 0,
|
|
94
|
+
totalRepositories: 0,
|
|
95
|
+
totalFiles: 0,
|
|
96
|
+
totalLines: 0,
|
|
97
|
+
totalBytes: 0
|
|
98
|
+
},
|
|
99
|
+
byRepository: {},
|
|
100
|
+
dependencies: {
|
|
101
|
+
total: 0,
|
|
102
|
+
workspace: 0,
|
|
103
|
+
external: 0,
|
|
104
|
+
duplicates: 0,
|
|
105
|
+
topUsed: []
|
|
106
|
+
},
|
|
107
|
+
health: {
|
|
108
|
+
score: 0,
|
|
109
|
+
grade: "F",
|
|
110
|
+
issues: []
|
|
111
|
+
},
|
|
112
|
+
largestPackages: []
|
|
113
|
+
};
|
|
114
|
+
const repos = /* @__PURE__ */ new Set();
|
|
115
|
+
const allDeps = /* @__PURE__ */ new Map();
|
|
116
|
+
for (const packagePath of packages) {
|
|
117
|
+
const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
|
|
118
|
+
const packageName = packageJson.name;
|
|
119
|
+
if (!packageName || !packageName.startsWith("@kb-labs/")) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
stats.overview.totalPackages++;
|
|
123
|
+
const packageDir = path.dirname(packagePath);
|
|
124
|
+
const repoName = path.basename(path.dirname(path.dirname(packageDir)));
|
|
125
|
+
repos.add(repoName);
|
|
126
|
+
const size = calculateSize(packageDir);
|
|
127
|
+
stats.overview.totalFiles += size.files;
|
|
128
|
+
stats.overview.totalLines += size.lines;
|
|
129
|
+
stats.overview.totalBytes += size.bytes;
|
|
130
|
+
if (!stats.byRepository[repoName]) {
|
|
131
|
+
stats.byRepository[repoName] = {
|
|
132
|
+
name: repoName,
|
|
133
|
+
packages: 0,
|
|
134
|
+
files: 0,
|
|
135
|
+
lines: 0,
|
|
136
|
+
bytes: 0
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
stats.byRepository[repoName].packages++;
|
|
140
|
+
stats.byRepository[repoName].files += size.files;
|
|
141
|
+
stats.byRepository[repoName].lines += size.lines;
|
|
142
|
+
stats.byRepository[repoName].bytes += size.bytes;
|
|
143
|
+
const deps = {
|
|
144
|
+
...packageJson.dependencies,
|
|
145
|
+
...packageJson.devDependencies
|
|
146
|
+
};
|
|
147
|
+
for (const dep of Object.keys(deps)) {
|
|
148
|
+
stats.dependencies.total++;
|
|
149
|
+
if (dep.startsWith("@kb-labs/")) {
|
|
150
|
+
stats.dependencies.workspace++;
|
|
151
|
+
} else {
|
|
152
|
+
stats.dependencies.external++;
|
|
153
|
+
}
|
|
154
|
+
allDeps.set(dep, (allDeps.get(dep) || 0) + 1);
|
|
155
|
+
}
|
|
156
|
+
stats.largestPackages.push({
|
|
157
|
+
name: packageName,
|
|
158
|
+
repository: repoName,
|
|
159
|
+
files: size.files,
|
|
160
|
+
lines: size.lines,
|
|
161
|
+
bytes: size.bytes
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
stats.overview.totalRepositories = repos.size;
|
|
165
|
+
stats.dependencies.duplicates = Array.from(allDeps.values()).filter((count) => count > 1).length;
|
|
166
|
+
stats.dependencies.topUsed = Array.from(allDeps.entries()).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([name, count]) => ({ name, count }));
|
|
167
|
+
stats.largestPackages.sort((a, b) => b.lines - a.lines);
|
|
168
|
+
stats.largestPackages = stats.largestPackages.slice(0, 10);
|
|
169
|
+
return stats;
|
|
170
|
+
}
|
|
171
|
+
function calculateHealthScore(stats) {
|
|
172
|
+
let score = 100;
|
|
173
|
+
const issues = [];
|
|
174
|
+
if (stats.dependencies.duplicates > 0) {
|
|
175
|
+
const penalty = Math.min(20, stats.dependencies.duplicates * 2);
|
|
176
|
+
score -= penalty;
|
|
177
|
+
issues.push({
|
|
178
|
+
type: "duplicates",
|
|
179
|
+
severity: "warning",
|
|
180
|
+
message: `${stats.dependencies.duplicates} duplicate dependencies (-${penalty})`
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
let grade;
|
|
184
|
+
if (score >= 90) {
|
|
185
|
+
grade = "A";
|
|
186
|
+
} else if (score >= 80) {
|
|
187
|
+
grade = "B";
|
|
188
|
+
} else if (score >= 70) {
|
|
189
|
+
grade = "C";
|
|
190
|
+
} else if (score >= 60) {
|
|
191
|
+
grade = "D";
|
|
192
|
+
} else {
|
|
193
|
+
grade = "F";
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
score,
|
|
197
|
+
grade,
|
|
198
|
+
issues
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function outputStats(stats, flags, ui) {
|
|
202
|
+
if (flags.json) {
|
|
203
|
+
ui?.json?.(stats);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (flags.md) {
|
|
207
|
+
outputMarkdown(stats);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const overviewItems = [
|
|
211
|
+
`Packages: ${stats.overview.totalPackages}`,
|
|
212
|
+
`Repositories: ${stats.overview.totalRepositories}`,
|
|
213
|
+
`Lines of Code: ${stats.overview.totalLines.toLocaleString()}`,
|
|
214
|
+
`Total Size: ${formatBytes(stats.overview.totalBytes)}`
|
|
215
|
+
];
|
|
216
|
+
const sections = [
|
|
217
|
+
{ header: "Overview", items: overviewItems }
|
|
218
|
+
];
|
|
219
|
+
if (flags.health && stats.health) {
|
|
220
|
+
const healthItems = [
|
|
221
|
+
`Score: ${stats.health.score}/100 (Grade ${stats.health.grade})`
|
|
222
|
+
];
|
|
223
|
+
if (stats.health.issues.length > 0) {
|
|
224
|
+
healthItems.push("");
|
|
225
|
+
healthItems.push("Issues:");
|
|
226
|
+
stats.health.issues.forEach((issue) => {
|
|
227
|
+
healthItems.push(` ${issue.message}`);
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
sections.push({ header: "Health Score", items: healthItems });
|
|
231
|
+
}
|
|
232
|
+
const topRepos = Object.values(stats.byRepository).sort((a, b) => b.lines - a.lines).slice(0, 5).map((repo) => `${repo.name}: ${repo.lines.toLocaleString()} lines, ${repo.packages} pkg(s)`);
|
|
233
|
+
if (topRepos.length > 0) {
|
|
234
|
+
sections.push({ header: "Top Repositories", items: topRepos });
|
|
235
|
+
}
|
|
236
|
+
ui?.success?.("Statistics collected successfully", {
|
|
237
|
+
title: "\u{1F4CA} KB Labs Monorepo Statistics",
|
|
238
|
+
sections
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
function outputMarkdown(stats) {
|
|
242
|
+
console.log("# KB Labs Monorepo Statistics\n");
|
|
243
|
+
console.log("## Overview\n");
|
|
244
|
+
console.log("| Metric | Value |");
|
|
245
|
+
console.log("|--------|-------|");
|
|
246
|
+
console.log(`| Packages | ${stats.overview.totalPackages} |`);
|
|
247
|
+
console.log(`| Repositories | ${stats.overview.totalRepositories} |`);
|
|
248
|
+
console.log(`| Lines of Code | ${stats.overview.totalLines.toLocaleString()} |`);
|
|
249
|
+
console.log(`| Total Size | ${formatBytes(stats.overview.totalBytes)} |
|
|
250
|
+
`);
|
|
251
|
+
if (stats.health) {
|
|
252
|
+
console.log("## Health Score\n");
|
|
253
|
+
console.log(`**${stats.health.score}/100** (Grade ${stats.health.grade})
|
|
254
|
+
`);
|
|
255
|
+
if (stats.health.issues.length > 0) {
|
|
256
|
+
console.log("### Issues\n");
|
|
257
|
+
for (const issue of stats.health.issues) {
|
|
258
|
+
console.log(`- ${issue.message}`);
|
|
259
|
+
}
|
|
260
|
+
console.log("");
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
console.log("## By Repository\n");
|
|
264
|
+
console.log("| Repository | Packages | Lines | Size |");
|
|
265
|
+
console.log("|------------|----------|-------|------|");
|
|
266
|
+
for (const [name, repo] of Object.entries(stats.byRepository)) {
|
|
267
|
+
console.log(`| ${name} | ${repo.packages} | ${repo.lines.toLocaleString()} | ${formatBytes(repo.bytes)} |`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function formatBytes(bytes) {
|
|
271
|
+
if (bytes === 0) {
|
|
272
|
+
return "0 B";
|
|
273
|
+
}
|
|
274
|
+
const k = 1024;
|
|
275
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
276
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
277
|
+
return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export { stats_default as default };
|
|
281
|
+
//# sourceMappingURL=stats.js.map
|
|
282
|
+
//# sourceMappingURL=stats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/cli/commands/stats.ts"],"names":[],"mappings":"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","file":"stats.js","sourcesContent":["/**\n * quality:stats - Monorepo statistics and health score\n *\n * Collects comprehensive statistics about the monorepo including:\n * - Package count by repository\n * - Total lines of code\n * - File counts\n * - Dependency statistics\n * - Health score (if --health flag provided)\n */\n\nimport { defineCommand, type PluginContextV3 } from '@kb-labs/sdk';\nimport type { StatsResult, Issue } from '@kb-labs/quality-contracts';\nimport { CACHE_KEYS } from '@kb-labs/quality-contracts';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport type { StatsFlags } from './flags.js';\n\n// Input type with backward compatibility\ntype StatsInput = StatsFlags & { argv?: string[] };\n\ntype StatsCommandResult = {\n exitCode: number;\n result?: StatsResult;\n meta?: Record<string, unknown>;\n};\n\nexport default defineCommand({\n id: 'quality:stats',\n description: 'Show monorepo statistics and health score',\n\n handler: {\n async execute(ctx: PluginContextV3, input: StatsInput): Promise<StatsCommandResult> {\n const { ui, platform } = ctx;\n\n // V3: Flags come in input.flags object (not auto-merged)\n const flags = (input as any).flags ?? input;\n\n // Check cache unless refresh requested\n if (!flags.refresh) {\n const cached = await platform.cache.get<StatsResult>(CACHE_KEYS.STATS);\n if (cached) {\n outputStats(cached, flags, ui);\n return { exitCode: 0 };\n }\n }\n\n // Collect statistics\n const stats = await collectStats(ctx.cwd);\n\n // Calculate health score if requested\n if (flags.health) {\n stats.health = calculateHealthScore(stats);\n }\n\n // Cache results for 5 minutes\n await platform.cache.set(CACHE_KEYS.STATS, stats, 5 * 60 * 1000);\n\n // Track analytics\n await platform.analytics.track('quality:stats', {\n packages: stats.overview.totalPackages,\n repositories: stats.overview.totalRepositories,\n withHealth: flags.health ?? false,\n });\n\n // Output results\n outputStats(stats, flags, ui);\n\n return { exitCode: 0 };\n },\n },\n});\n\n/**\n * Find all packages in the monorepo\n */\nfunction findPackages(rootDir: string): string[] {\n const packages: string[] = [];\n\n if (!fs.existsSync(rootDir)) {\n return packages;\n }\n\n const entries = fs.readdirSync(rootDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (!entry.isDirectory() || !entry.name.startsWith('kb-labs-')) {continue;}\n\n const repoPath = path.join(rootDir, entry.name);\n const packagesDir = path.join(repoPath, 'packages');\n\n if (!fs.existsSync(packagesDir)) {continue;}\n\n const packageDirs = fs.readdirSync(packagesDir, { withFileTypes: true });\n\n for (const pkgDir of packageDirs) {\n if (!pkgDir.isDirectory()) {continue;}\n\n const packageJsonPath = path.join(packagesDir, pkgDir.name, 'package.json');\n\n if (fs.existsSync(packageJsonPath)) {\n packages.push(packageJsonPath);\n }\n }\n }\n\n return packages;\n}\n\n/**\n * Calculate package size (files, lines, bytes)\n */\nfunction calculateSize(packageDir: string): { files: number; lines: number; bytes: number } {\n const srcDir = path.join(packageDir, 'src');\n if (!fs.existsSync(srcDir)) {\n return { files: 0, lines: 0, bytes: 0 };\n }\n\n let files = 0;\n let lines = 0;\n let bytes = 0;\n\n function walk(dir: string) {\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n\n if (entry.isDirectory()) {\n walk(fullPath);\n } else if (entry.isFile() && /\\.(ts|tsx|js|jsx)$/.test(entry.name)) {\n files++;\n const content = fs.readFileSync(fullPath, 'utf-8');\n lines += content.split('\\n').length;\n bytes += Buffer.byteLength(content, 'utf-8');\n }\n }\n }\n\n walk(srcDir);\n return { files, lines, bytes };\n}\n\n/**\n * Collect comprehensive statistics\n */\nasync function collectStats(rootDir: string): Promise<StatsResult> {\n const packages = findPackages(rootDir);\n\n const stats: StatsResult = {\n overview: {\n totalPackages: 0,\n totalRepositories: 0,\n totalFiles: 0,\n totalLines: 0,\n totalBytes: 0,\n },\n byRepository: {},\n dependencies: {\n total: 0,\n workspace: 0,\n external: 0,\n duplicates: 0,\n topUsed: [],\n },\n health: {\n score: 0,\n grade: 'F',\n issues: [],\n },\n largestPackages: [],\n };\n\n const repos = new Set<string>();\n const allDeps = new Map<string, number>();\n\n for (const packagePath of packages) {\n const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));\n const packageName = packageJson.name;\n\n if (!packageName || !packageName.startsWith('@kb-labs/')) {continue;}\n\n stats.overview.totalPackages++;\n\n const packageDir = path.dirname(packagePath);\n const repoName = path.basename(path.dirname(path.dirname(packageDir)));\n\n repos.add(repoName);\n\n const size = calculateSize(packageDir);\n stats.overview.totalFiles += size.files;\n stats.overview.totalLines += size.lines;\n stats.overview.totalBytes += size.bytes;\n\n // Track by repository\n if (!stats.byRepository[repoName]) {\n stats.byRepository[repoName] = {\n name: repoName,\n packages: 0,\n files: 0,\n lines: 0,\n bytes: 0,\n };\n }\n\n stats.byRepository[repoName].packages++;\n stats.byRepository[repoName].files += size.files;\n stats.byRepository[repoName].lines += size.lines;\n stats.byRepository[repoName].bytes += size.bytes;\n\n // Track dependencies\n const deps = {\n ...packageJson.dependencies,\n ...packageJson.devDependencies,\n };\n\n for (const dep of Object.keys(deps)) {\n stats.dependencies.total++;\n\n if (dep.startsWith('@kb-labs/')) {\n stats.dependencies.workspace++;\n } else {\n stats.dependencies.external++;\n }\n\n allDeps.set(dep, (allDeps.get(dep) || 0) + 1);\n }\n\n // Track package for largest packages list\n stats.largestPackages.push({\n name: packageName,\n repository: repoName,\n files: size.files,\n lines: size.lines,\n bytes: size.bytes,\n });\n }\n\n stats.overview.totalRepositories = repos.size;\n\n // Find duplicates\n stats.dependencies.duplicates = Array.from(allDeps.values()).filter(count => count > 1).length;\n\n // Top 10 most used dependencies\n stats.dependencies.topUsed = Array.from(allDeps.entries())\n .sort((a, b) => b[1] - a[1])\n .slice(0, 10)\n .map(([name, count]) => ({ name, count }));\n\n // Sort largest packages by lines\n stats.largestPackages.sort((a, b) => b.lines - a.lines);\n stats.largestPackages = stats.largestPackages.slice(0, 10);\n\n return stats;\n}\n\n/**\n * Calculate health score\n */\nfunction calculateHealthScore(stats: StatsResult) {\n let score = 100;\n const issues: Issue[] = [];\n\n // Penalize for duplicate dependencies\n if (stats.dependencies.duplicates > 0) {\n const penalty = Math.min(20, stats.dependencies.duplicates * 2);\n score -= penalty;\n issues.push({\n type: 'duplicates',\n severity: 'warning',\n message: `${stats.dependencies.duplicates} duplicate dependencies (-${penalty})`,\n });\n }\n\n // Penalize for missing README files (would need to check, simplified here)\n // This is a placeholder - real implementation would scan for README files\n\n // Determine grade\n let grade: 'A' | 'B' | 'C' | 'D' | 'F';\n if (score >= 90) {grade = 'A';}\n else if (score >= 80) {grade = 'B';}\n else if (score >= 70) {grade = 'C';}\n else if (score >= 60) {grade = 'D';}\n else {grade = 'F';}\n\n return {\n score,\n grade,\n issues,\n };\n}\n\n/**\n * Output statistics in requested format\n */\nfunction outputStats(stats: StatsResult, flags: any, ui: any) {\n if (flags.json) {\n ui?.json?.(stats);\n return;\n }\n\n if (flags.md) {\n outputMarkdown(stats);\n return;\n }\n\n // Default: formatted output using V3 ctx.ui API\n const overviewItems = [\n `Packages: ${stats.overview.totalPackages}`,\n `Repositories: ${stats.overview.totalRepositories}`,\n `Lines of Code: ${stats.overview.totalLines.toLocaleString()}`,\n `Total Size: ${formatBytes(stats.overview.totalBytes)}`,\n ];\n\n const sections: Array<{ header: string; items: string[] }> = [\n { header: 'Overview', items: overviewItems },\n ];\n\n // Add health section if requested\n if (flags.health && stats.health) {\n const healthItems = [\n `Score: ${stats.health.score}/100 (Grade ${stats.health.grade})`,\n ];\n\n if (stats.health.issues.length > 0) {\n healthItems.push('');\n healthItems.push('Issues:');\n stats.health.issues.forEach(issue => {\n healthItems.push(` ${issue.message}`);\n });\n }\n\n sections.push({ header: 'Health Score', items: healthItems });\n }\n\n // Add top repositories\n const topRepos = Object.values(stats.byRepository)\n .sort((a, b) => b.lines - a.lines)\n .slice(0, 5)\n .map(repo => `${repo.name}: ${repo.lines.toLocaleString()} lines, ${repo.packages} pkg(s)`);\n\n if (topRepos.length > 0) {\n sections.push({ header: 'Top Repositories', items: topRepos });\n }\n\n ui?.success?.('Statistics collected successfully', {\n title: '๐ KB Labs Monorepo Statistics',\n sections,\n });\n}\n\n/**\n * Output Markdown table\n */\nfunction outputMarkdown(stats: StatsResult) {\n console.log('# KB Labs Monorepo Statistics\\n');\n console.log('## Overview\\n');\n console.log('| Metric | Value |');\n console.log('|--------|-------|');\n console.log(`| Packages | ${stats.overview.totalPackages} |`);\n console.log(`| Repositories | ${stats.overview.totalRepositories} |`);\n console.log(`| Lines of Code | ${stats.overview.totalLines.toLocaleString()} |`);\n console.log(`| Total Size | ${formatBytes(stats.overview.totalBytes)} |\\n`);\n\n if (stats.health) {\n console.log('## Health Score\\n');\n console.log(`**${stats.health.score}/100** (Grade ${stats.health.grade})\\n`);\n\n if (stats.health.issues.length > 0) {\n console.log('### Issues\\n');\n for (const issue of stats.health.issues) {\n console.log(`- ${issue.message}`);\n }\n console.log('');\n }\n }\n\n console.log('## By Repository\\n');\n console.log('| Repository | Packages | Lines | Size |');\n console.log('|------------|----------|-------|------|');\n for (const [name, repo] of Object.entries(stats.byRepository)) {\n console.log(`| ${name} | ${repo.packages} | ${repo.lines.toLocaleString()} | ${formatBytes(repo.bytes)} |`);\n }\n}\n\n/**\n * Format bytes to human-readable string\n */\nfunction formatBytes(bytes: number): string {\n if (bytes === 0) {return '0 B';}\n const k = 1024;\n const sizes = ['B', 'KB', 'MB', 'GB'];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* quality:visualize - Visualize dependency graph
|
|
5
|
+
*
|
|
6
|
+
* Shows dependency graph in various formats:
|
|
7
|
+
* - Tree view for specific package
|
|
8
|
+
* - DOT format for graphviz
|
|
9
|
+
* - Statistics about the graph
|
|
10
|
+
*/
|
|
11
|
+
type VisualizeFlags = {
|
|
12
|
+
package?: string;
|
|
13
|
+
tree?: boolean;
|
|
14
|
+
dot?: boolean;
|
|
15
|
+
stats?: boolean;
|
|
16
|
+
reverse?: boolean;
|
|
17
|
+
impact?: boolean;
|
|
18
|
+
json?: boolean;
|
|
19
|
+
argv?: string[];
|
|
20
|
+
};
|
|
21
|
+
type VisualizeInput = VisualizeFlags & {
|
|
22
|
+
argv?: string[];
|
|
23
|
+
};
|
|
24
|
+
declare const _default: _kb_labs_shared_command_kit.CommandHandlerV3<unknown, VisualizeInput, unknown>;
|
|
25
|
+
|
|
26
|
+
export { _default as default };
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { defineCommand } from '@kb-labs/sdk';
|
|
2
|
+
import { buildDependencyGraph, getReverseDependencies, getImpactAnalysis } from '@kb-labs/quality-core/graph';
|
|
3
|
+
|
|
4
|
+
// src/cli/commands/visualize.ts
|
|
5
|
+
var visualize_default = defineCommand({
|
|
6
|
+
id: "quality:visualize",
|
|
7
|
+
description: "Visualize dependency graph",
|
|
8
|
+
handler: {
|
|
9
|
+
async execute(ctx, input) {
|
|
10
|
+
const { ui } = ctx;
|
|
11
|
+
const flags = input.flags ?? input;
|
|
12
|
+
const graph = buildDependencyGraph(ctx.cwd);
|
|
13
|
+
if (flags.dot) {
|
|
14
|
+
outputDot(graph, flags, ui);
|
|
15
|
+
} else if (flags.tree && flags.package) {
|
|
16
|
+
outputTree(graph, flags.package, flags, ui);
|
|
17
|
+
} else if (flags.reverse && flags.package) {
|
|
18
|
+
outputReverse(graph, flags.package, flags, ui);
|
|
19
|
+
} else if (flags.impact && flags.package) {
|
|
20
|
+
outputImpact(graph, flags.package, flags, ui);
|
|
21
|
+
} else if (flags.stats) {
|
|
22
|
+
outputStats(graph, flags, ui);
|
|
23
|
+
} else {
|
|
24
|
+
outputStats(graph, flags, ui);
|
|
25
|
+
}
|
|
26
|
+
return { exitCode: 0 };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
function outputTree(graph, packageName, flags, ui) {
|
|
31
|
+
const { nodes } = graph;
|
|
32
|
+
const node = nodes.get(packageName);
|
|
33
|
+
if (!node) {
|
|
34
|
+
ui?.fatal?.(`Package not found: ${packageName}`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const sections = [];
|
|
38
|
+
const visited = /* @__PURE__ */ new Set();
|
|
39
|
+
const treeItems = [];
|
|
40
|
+
function buildTree(pkg, depth, prefix) {
|
|
41
|
+
if (visited.has(pkg)) {
|
|
42
|
+
treeItems.push(`${prefix}${pkg} (circular)`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
visited.add(pkg);
|
|
46
|
+
const pkgNode = nodes.get(pkg);
|
|
47
|
+
if (!pkgNode) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
treeItems.push(`${prefix}${pkg}`);
|
|
51
|
+
const deps = Array.from(pkgNode.deps);
|
|
52
|
+
for (let i = 0; i < deps.length; i++) {
|
|
53
|
+
const dep = deps[i];
|
|
54
|
+
if (!dep) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const isLast = i === deps.length - 1;
|
|
58
|
+
const newPrefix = prefix + (isLast ? " \u2514\u2500 " : " \u251C\u2500 ");
|
|
59
|
+
buildTree(dep, depth + 1, newPrefix);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
buildTree(packageName, 0, "");
|
|
63
|
+
sections.push({
|
|
64
|
+
header: "Dependency Tree",
|
|
65
|
+
items: treeItems
|
|
66
|
+
});
|
|
67
|
+
ui?.success?.("Dependency tree generated", {
|
|
68
|
+
title: `\u{1F4E6} Dependency Tree for ${packageName}`,
|
|
69
|
+
sections
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function outputReverse(graph, packageName, flags, ui) {
|
|
73
|
+
const reverseDeps = getReverseDependencies(graph, packageName);
|
|
74
|
+
if (flags.json) {
|
|
75
|
+
ui?.json?.({ package: packageName, reverseDependencies: Array.from(reverseDeps) });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const sections = [];
|
|
79
|
+
if (reverseDeps.size === 0) {
|
|
80
|
+
sections.push({
|
|
81
|
+
header: "Result",
|
|
82
|
+
items: ["No packages depend on this package"]
|
|
83
|
+
});
|
|
84
|
+
} else {
|
|
85
|
+
sections.push({
|
|
86
|
+
header: `Packages that depend on ${packageName}`,
|
|
87
|
+
items: Array.from(reverseDeps).map((dep) => `\u2022 ${dep}`)
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
ui?.success?.("Reverse dependencies found", {
|
|
91
|
+
title: `\u2B05\uFE0F Reverse Dependencies for ${packageName}`,
|
|
92
|
+
sections
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function outputImpact(graph, packageName, flags, ui) {
|
|
96
|
+
const affected = getImpactAnalysis(graph, packageName);
|
|
97
|
+
if (flags.json) {
|
|
98
|
+
ui?.json?.({ package: packageName, affectedPackages: Array.from(affected) });
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const sections = [];
|
|
102
|
+
if (affected.size === 0) {
|
|
103
|
+
sections.push({
|
|
104
|
+
header: "Result",
|
|
105
|
+
items: ["No other packages affected by changes to this package"]
|
|
106
|
+
});
|
|
107
|
+
} else {
|
|
108
|
+
sections.push({
|
|
109
|
+
header: `Impact Analysis for ${packageName}`,
|
|
110
|
+
items: [
|
|
111
|
+
`Changes to this package will affect ${affected.size} other packages:`,
|
|
112
|
+
"",
|
|
113
|
+
...Array.from(affected).map((dep) => `\u2022 ${dep}`)
|
|
114
|
+
]
|
|
115
|
+
});
|
|
116
|
+
sections.push({
|
|
117
|
+
header: "Recommendations",
|
|
118
|
+
items: [
|
|
119
|
+
"\u{1F9EA} Run tests in affected packages",
|
|
120
|
+
"\u{1F528} Rebuild affected packages",
|
|
121
|
+
"\u{1F4DD} Update documentation if API changed"
|
|
122
|
+
]
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
ui?.success?.("Impact analysis completed", {
|
|
126
|
+
title: `\u{1F4A5} Impact Analysis for ${packageName}`,
|
|
127
|
+
sections
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function outputStats(graph, flags, ui) {
|
|
131
|
+
const { nodes } = graph;
|
|
132
|
+
let totalDeps = 0;
|
|
133
|
+
let maxDeps = 0;
|
|
134
|
+
let maxDepsPackage = "";
|
|
135
|
+
const depCounts = /* @__PURE__ */ new Map();
|
|
136
|
+
for (const [name, node] of nodes) {
|
|
137
|
+
const depCount = node.deps.size;
|
|
138
|
+
totalDeps += depCount;
|
|
139
|
+
if (depCount > maxDeps) {
|
|
140
|
+
maxDeps = depCount;
|
|
141
|
+
maxDepsPackage = name;
|
|
142
|
+
}
|
|
143
|
+
depCounts.set(depCount, (depCounts.get(depCount) ?? 0) + 1);
|
|
144
|
+
}
|
|
145
|
+
const avgDeps = nodes.size > 0 ? (totalDeps / nodes.size).toFixed(2) : "0";
|
|
146
|
+
if (flags.json) {
|
|
147
|
+
ui?.json?.({
|
|
148
|
+
totalPackages: nodes.size,
|
|
149
|
+
totalDependencies: totalDeps,
|
|
150
|
+
averageDependencies: Number.parseFloat(avgDeps),
|
|
151
|
+
maxDependencies: maxDeps,
|
|
152
|
+
maxDependenciesPackage: maxDepsPackage
|
|
153
|
+
});
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const sections = [];
|
|
157
|
+
sections.push({
|
|
158
|
+
header: "Graph Statistics",
|
|
159
|
+
items: [
|
|
160
|
+
`Total packages: ${nodes.size}`,
|
|
161
|
+
`Total dependencies: ${totalDeps}`,
|
|
162
|
+
`Average dependencies per package: ${avgDeps}`,
|
|
163
|
+
`Max dependencies: ${maxDeps} (${maxDepsPackage})`
|
|
164
|
+
]
|
|
165
|
+
});
|
|
166
|
+
const distributionItems = [];
|
|
167
|
+
const sortedCounts = Array.from(depCounts.entries()).sort((a, b) => a[0] - b[0]);
|
|
168
|
+
for (const [count, packages] of sortedCounts) {
|
|
169
|
+
const bar = "\u2588".repeat(Math.min(packages, 20));
|
|
170
|
+
distributionItems.push(`${count} deps: ${bar} ${packages} packages`);
|
|
171
|
+
}
|
|
172
|
+
sections.push({
|
|
173
|
+
header: "Dependency Distribution",
|
|
174
|
+
items: distributionItems
|
|
175
|
+
});
|
|
176
|
+
ui?.success?.("Graph statistics calculated", {
|
|
177
|
+
title: "\u{1F4CA} Dependency Graph Statistics",
|
|
178
|
+
sections
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
function outputDot(graph, flags, ui) {
|
|
182
|
+
const { nodes } = graph;
|
|
183
|
+
const lines = [];
|
|
184
|
+
lines.push("digraph dependencies {");
|
|
185
|
+
lines.push(" rankdir=LR;");
|
|
186
|
+
lines.push(" node [shape=box];");
|
|
187
|
+
lines.push("");
|
|
188
|
+
for (const [name, node] of nodes) {
|
|
189
|
+
for (const dep of node.deps) {
|
|
190
|
+
const safeName = name.replace(/[@\/]/g, "_");
|
|
191
|
+
const safeDep = dep.replace(/[@\/]/g, "_");
|
|
192
|
+
lines.push(` "${safeName}" -> "${safeDep}";`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
lines.push("}");
|
|
196
|
+
if (flags.json) {
|
|
197
|
+
ui?.json?.({ dot: lines.join("\n") });
|
|
198
|
+
} else {
|
|
199
|
+
for (const line of lines) {
|
|
200
|
+
ui?.write?.(line);
|
|
201
|
+
}
|
|
202
|
+
ui?.write?.("");
|
|
203
|
+
ui?.write?.("# Save to file and visualize with:");
|
|
204
|
+
ui?.write?.("# dot -Tpng dependencies.dot -o dependencies.png");
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export { visualize_default as default };
|
|
209
|
+
//# sourceMappingURL=visualize.js.map
|
|
210
|
+
//# sourceMappingURL=visualize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/cli/commands/visualize.ts"],"names":[],"mappings":"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","file":"visualize.js","sourcesContent":["/**\n * quality:visualize - Visualize dependency graph\n *\n * Shows dependency graph in various formats:\n * - Tree view for specific package\n * - DOT format for graphviz\n * - Statistics about the graph\n */\n\nimport { defineCommand, type PluginContextV3 } from '@kb-labs/sdk';\nimport {\n buildDependencyGraph,\n getReverseDependencies,\n getImpactAnalysis,\n type DependencyGraph,\n} from '@kb-labs/quality-core/graph';\n\ntype VisualizeFlags = {\n package?: string;\n tree?: boolean;\n dot?: boolean;\n stats?: boolean;\n reverse?: boolean;\n impact?: boolean;\n json?: boolean;\n argv?: string[];\n};\n\ntype VisualizeInput = VisualizeFlags & { argv?: string[] };\n\ntype VisualizeCommandResult = {\n exitCode: number;\n graph?: any;\n};\n\nexport default defineCommand({\n id: 'quality:visualize',\n description: 'Visualize dependency graph',\n\n handler: {\n async execute(ctx: PluginContextV3, input: VisualizeInput): Promise<VisualizeCommandResult> {\n const { ui } = ctx;\n\n // V3: Flags come in input.flags object (not auto-merged)\n const flags = (input as any).flags ?? input;\n\n // Build dependency graph\n const graph = buildDependencyGraph(ctx.cwd);\n\n // Handle different output modes\n if (flags.dot) {\n outputDot(graph, flags, ui);\n } else if (flags.tree && flags.package) {\n outputTree(graph, flags.package, flags, ui);\n } else if (flags.reverse && flags.package) {\n outputReverse(graph, flags.package, flags, ui);\n } else if (flags.impact && flags.package) {\n outputImpact(graph, flags.package, flags, ui);\n } else if (flags.stats) {\n outputStats(graph, flags, ui);\n } else {\n // Default: show stats\n outputStats(graph, flags, ui);\n }\n\n return { exitCode: 0 };\n },\n },\n});\n\n/**\n * Output dependency tree for a package\n */\nfunction outputTree(graph: DependencyGraph, packageName: string, flags: any, ui: any) {\n const { nodes } = graph;\n const node = nodes.get(packageName);\n\n if (!node) {\n ui?.fatal?.(`Package not found: ${packageName}`);\n return;\n }\n\n const sections: Array<{ header: string; items: string[] }> = [];\n\n // Build tree recursively\n const visited = new Set<string>();\n const treeItems: string[] = [];\n\n function buildTree(pkg: string, depth: number, prefix: string) {\n if (visited.has(pkg)) {\n treeItems.push(`${prefix}${pkg} (circular)`);\n return;\n }\n visited.add(pkg);\n\n const pkgNode = nodes.get(pkg);\n if (!pkgNode) {return;}\n\n treeItems.push(`${prefix}${pkg}`);\n\n const deps = Array.from(pkgNode.deps);\n for (let i = 0; i < deps.length; i++) {\n const dep = deps[i];\n if (!dep) {continue;}\n const isLast = i === deps.length - 1;\n const newPrefix = prefix + (isLast ? ' โโ ' : ' โโ ');\n buildTree(dep, depth + 1, newPrefix);\n }\n }\n\n buildTree(packageName, 0, '');\n\n sections.push({\n header: 'Dependency Tree',\n items: treeItems,\n });\n\n ui?.success?.('Dependency tree generated', {\n title: `๐ฆ Dependency Tree for ${packageName}`,\n sections,\n });\n}\n\n/**\n * Output reverse dependencies (who depends on this package)\n */\nfunction outputReverse(graph: DependencyGraph, packageName: string, flags: any, ui: any) {\n const reverseDeps = getReverseDependencies(graph, packageName);\n\n if (flags.json) {\n ui?.json?.({ package: packageName, reverseDependencies: Array.from(reverseDeps) });\n return;\n }\n\n const sections: Array<{ header: string; items: string[] }> = [];\n\n if (reverseDeps.size === 0) {\n sections.push({\n header: 'Result',\n items: ['No packages depend on this package'],\n });\n } else {\n sections.push({\n header: `Packages that depend on ${packageName}`,\n items: Array.from(reverseDeps).map(dep => `โข ${dep}`),\n });\n }\n\n ui?.success?.('Reverse dependencies found', {\n title: `โฌ
๏ธ Reverse Dependencies for ${packageName}`,\n sections,\n });\n}\n\n/**\n * Output impact analysis (all packages affected by changes)\n */\nfunction outputImpact(graph: DependencyGraph, packageName: string, flags: any, ui: any) {\n const affected = getImpactAnalysis(graph, packageName);\n\n if (flags.json) {\n ui?.json?.({ package: packageName, affectedPackages: Array.from(affected) });\n return;\n }\n\n const sections: Array<{ header: string; items: string[] }> = [];\n\n if (affected.size === 0) {\n sections.push({\n header: 'Result',\n items: ['No other packages affected by changes to this package'],\n });\n } else {\n sections.push({\n header: `Impact Analysis for ${packageName}`,\n items: [\n `Changes to this package will affect ${affected.size} other packages:`,\n '',\n ...Array.from(affected).map(dep => `โข ${dep}`),\n ],\n });\n\n sections.push({\n header: 'Recommendations',\n items: [\n '๐งช Run tests in affected packages',\n '๐จ Rebuild affected packages',\n '๐ Update documentation if API changed',\n ],\n });\n }\n\n ui?.success?.('Impact analysis completed', {\n title: `๐ฅ Impact Analysis for ${packageName}`,\n sections,\n });\n}\n\n/**\n * Output dependency graph statistics\n */\nfunction outputStats(graph: DependencyGraph, flags: any, ui: any) {\n const { nodes } = graph;\n\n // Calculate statistics\n let totalDeps = 0;\n let maxDeps = 0;\n let maxDepsPackage = '';\n const depCounts = new Map<number, number>();\n\n for (const [name, node] of nodes) {\n const depCount = node.deps.size;\n totalDeps += depCount;\n\n if (depCount > maxDeps) {\n maxDeps = depCount;\n maxDepsPackage = name;\n }\n\n depCounts.set(depCount, (depCounts.get(depCount) ?? 0) + 1);\n }\n\n const avgDeps = nodes.size > 0 ? (totalDeps / nodes.size).toFixed(2) : '0';\n\n if (flags.json) {\n ui?.json?.({\n totalPackages: nodes.size,\n totalDependencies: totalDeps,\n averageDependencies: Number.parseFloat(avgDeps),\n maxDependencies: maxDeps,\n maxDependenciesPackage: maxDepsPackage,\n });\n return;\n }\n\n const sections: Array<{ header: string; items: string[] }> = [];\n\n sections.push({\n header: 'Graph Statistics',\n items: [\n `Total packages: ${nodes.size}`,\n `Total dependencies: ${totalDeps}`,\n `Average dependencies per package: ${avgDeps}`,\n `Max dependencies: ${maxDeps} (${maxDepsPackage})`,\n ],\n });\n\n // Dependency distribution\n const distributionItems: string[] = [];\n const sortedCounts = Array.from(depCounts.entries()).sort((a, b) => a[0] - b[0]);\n for (const [count, packages] of sortedCounts) {\n const bar = 'โ'.repeat(Math.min(packages, 20));\n distributionItems.push(`${count} deps: ${bar} ${packages} packages`);\n }\n\n sections.push({\n header: 'Dependency Distribution',\n items: distributionItems,\n });\n\n ui?.success?.('Graph statistics calculated', {\n title: '๐ Dependency Graph Statistics',\n sections,\n });\n}\n\n/**\n * Output DOT format for graphviz\n */\nfunction outputDot(graph: DependencyGraph, flags: any, ui: any) {\n const { nodes } = graph;\n\n const lines: string[] = [];\n lines.push('digraph dependencies {');\n lines.push(' rankdir=LR;');\n lines.push(' node [shape=box];');\n lines.push('');\n\n for (const [name, node] of nodes) {\n for (const dep of node.deps) {\n const safeName = name.replace(/[@\\/]/g, '_');\n const safeDep = dep.replace(/[@\\/]/g, '_');\n lines.push(` \"${safeName}\" -> \"${safeDep}\";`);\n }\n }\n\n lines.push('}');\n\n if (flags.json) {\n ui?.json?.({ dot: lines.join('\\n') });\n } else {\n for (const line of lines) {\n ui?.write?.(line);\n }\n ui?.write?.('');\n ui?.write?.('# Save to file and visualize with:');\n ui?.write?.('# dot -Tpng dependencies.dot -o dependencies.png');\n }\n}\n"]}
|
package/dist/index.d.ts
ADDED