@kb-labs/quality-entry 2.93.0 → 2.96.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/cli/commands/build-order.d.ts +5 -12
- package/dist/cli/commands/build-order.js +45 -97
- package/dist/cli/commands/build-order.js.map +1 -1
- package/dist/cli/commands/check-layers.d.ts +9 -0
- package/dist/cli/commands/check-layers.js +48 -0
- package/dist/cli/commands/check-layers.js.map +1 -0
- package/dist/cli/commands/context.d.ts +9 -0
- package/dist/cli/commands/context.js +94 -0
- package/dist/cli/commands/context.js.map +1 -0
- package/dist/cli/commands/coupling.d.ts +9 -0
- package/dist/cli/commands/coupling.js +35 -0
- package/dist/cli/commands/coupling.js.map +1 -0
- package/dist/cli/commands/dead-code.d.ts +4 -5
- package/dist/cli/commands/dead-code.js +41 -197
- package/dist/cli/commands/dead-code.js.map +1 -1
- package/dist/cli/commands/fix-deps.d.ts +2 -5
- package/dist/cli/commands/fix-deps.js +74 -83
- package/dist/cli/commands/fix-deps.js.map +1 -1
- package/dist/cli/commands/flags.d.ts +115 -1
- package/dist/cli/commands/flags.js +46 -1
- package/dist/cli/commands/flags.js.map +1 -1
- package/dist/cli/commands/gate.d.ts +9 -0
- package/dist/cli/commands/gate.js +59 -0
- package/dist/cli/commands/gate.js.map +1 -0
- package/dist/cli/commands/health.d.ts +4 -5
- package/dist/cli/commands/health.js +42 -191
- package/dist/cli/commands/health.js.map +1 -1
- package/dist/cli/commands/history.d.ts +9 -0
- package/dist/cli/commands/history.js +55 -0
- package/dist/cli/commands/history.js.map +1 -0
- package/dist/cli/commands/index.d.ts +6 -2
- package/dist/cli/commands/index.js +411 -828
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/snapshot.d.ts +9 -0
- package/dist/cli/commands/snapshot.js +91 -0
- package/dist/cli/commands/snapshot.js.map +1 -0
- package/dist/cli/commands/stats.d.ts +4 -5
- package/dist/cli/commands/stats.js +17 -265
- package/dist/cli/commands/stats.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +198 -306
- package/dist/index.js.map +1 -1
- package/dist/manifest.d.ts +95 -82
- package/dist/manifest.js +198 -306
- package/dist/manifest.js.map +1 -1
- package/dist/rest/handlers/build-order-handler.d.ts +5 -13
- package/dist/rest/handlers/build-order-handler.js +14 -17
- package/dist/rest/handlers/build-order-handler.js.map +1 -1
- package/dist/rest/handlers/builds-handler.js +10 -6
- package/dist/rest/handlers/builds-handler.js.map +1 -1
- package/dist/rest/handlers/coupling-handler.d.ts +8 -0
- package/dist/rest/handlers/coupling-handler.js +28 -0
- package/dist/rest/handlers/coupling-handler.js.map +1 -0
- package/dist/rest/handlers/cycles-handler.js +16 -12
- package/dist/rest/handlers/cycles-handler.js.map +1 -1
- package/dist/rest/handlers/dependencies-handler.js +12 -8
- package/dist/rest/handlers/dependencies-handler.js.map +1 -1
- package/dist/rest/handlers/graph-handler.js +77 -73
- package/dist/rest/handlers/graph-handler.js.map +1 -1
- package/dist/rest/handlers/health-handler.d.ts +4 -11
- package/dist/rest/handlers/health-handler.js +35 -9
- package/dist/rest/handlers/health-handler.js.map +1 -1
- package/dist/rest/handlers/history-handler.d.ts +7 -0
- package/dist/rest/handlers/history-handler.js +21 -0
- package/dist/rest/handlers/history-handler.js.map +1 -0
- package/dist/rest/handlers/knip-handler.d.ts +8 -0
- package/dist/rest/handlers/knip-handler.js +31 -0
- package/dist/rest/handlers/knip-handler.js.map +1 -0
- package/dist/rest/handlers/layers-handler.d.ts +8 -0
- package/dist/rest/handlers/layers-handler.js +28 -0
- package/dist/rest/handlers/layers-handler.js.map +1 -0
- package/dist/rest/handlers/snapshot-handler.d.ts +7 -0
- package/dist/rest/handlers/snapshot-handler.js +67 -0
- package/dist/rest/handlers/snapshot-handler.js.map +1 -0
- package/dist/rest/handlers/stale-handler.js +13 -9
- package/dist/rest/handlers/stale-handler.js.map +1 -1
- package/dist/rest/handlers/stats-handler.d.ts +4 -9
- package/dist/rest/handlers/stats-handler.js +22 -18
- package/dist/rest/handlers/stats-handler.js.map +1 -1
- package/dist/rest/handlers/tests-handler.js +12 -8
- package/dist/rest/handlers/tests-handler.js.map +1 -1
- package/dist/rest/handlers/types-handler.js +10 -6
- package/dist/rest/handlers/types-handler.js.map +1 -1
- package/dist/widgets/117.js +24 -1
- package/dist/widgets/117.js.map +1 -1
- package/dist/widgets/370.js +1 -1
- package/dist/widgets/370.js.map +1 -1
- package/dist/widgets/62.js +97 -0
- package/dist/widgets/62.js.map +1 -0
- package/dist/widgets/__federation_expose_QualityOverview.js +1 -1
- package/dist/widgets/__federation_expose_QualityOverview.js.map +1 -1
- package/dist/widgets/mf-manifest.json +27 -27
- package/dist/widgets/mf-stats.json +27 -27
- package/dist/widgets/remoteEntry.js +2 -2
- package/dist/widgets/remoteEntry.js.map +1 -1
- package/package.json +9 -8
- package/dist/widgets/418.js +0 -39
- package/dist/widgets/418.js.map +0 -1
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
|
|
2
|
+
import { CLIInput } from '@kb-labs/sdk';
|
|
3
|
+
import { SnapshotFlags } from './flags.js';
|
|
4
|
+
|
|
5
|
+
declare const _default: _kb_labs_shared_command_kit.CommandHandlerV3<unknown, CLIInput<SnapshotFlags>, {
|
|
6
|
+
exitCode: number;
|
|
7
|
+
}>;
|
|
8
|
+
|
|
9
|
+
export { _default as default };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { defineCommand, useConfig } from '@kb-labs/sdk';
|
|
2
|
+
import { analyzeLayering, analyzeCoupling, analyzeTypes, runKnip, calculateHealth, QualitySnapshotStore } from '@kb-labs/quality-core';
|
|
3
|
+
import { defaultQualityConfig } from '@kb-labs/quality-contracts';
|
|
4
|
+
|
|
5
|
+
// src/cli/commands/snapshot.ts
|
|
6
|
+
async function captureGit(shell, cwd) {
|
|
7
|
+
try {
|
|
8
|
+
const [c, b, m] = await Promise.all([
|
|
9
|
+
shell.exec("git", ["rev-parse", "--short", "HEAD"], { cwd, throwOnError: false }),
|
|
10
|
+
shell.exec("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd, throwOnError: false }),
|
|
11
|
+
shell.exec("git", ["log", "-1", "--format=%s"], { cwd, throwOnError: false })
|
|
12
|
+
]);
|
|
13
|
+
if (!c.ok) return void 0;
|
|
14
|
+
return { commit: c.stdout.trim(), branch: b.stdout.trim() || "HEAD", message: m.stdout.trim() };
|
|
15
|
+
} catch {
|
|
16
|
+
return void 0;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
var snapshot_default = defineCommand({
|
|
20
|
+
id: "quality:snapshot",
|
|
21
|
+
description: "Collect all quality metrics and save a snapshot for trend tracking",
|
|
22
|
+
handler: {
|
|
23
|
+
async intent(_ctx, _input) {
|
|
24
|
+
return {
|
|
25
|
+
summary: "Collect all quality metrics and save snapshot for trend tracking",
|
|
26
|
+
operations: [
|
|
27
|
+
{ type: "create", resource: "file", details: { path: ".kb/quality/snapshots/<timestamp>.json" } }
|
|
28
|
+
]
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
async execute(ctx, input) {
|
|
32
|
+
const { flags } = input;
|
|
33
|
+
const config = await useConfig();
|
|
34
|
+
const cwd = ctx.cwd ?? process.cwd();
|
|
35
|
+
const cfg = config ?? defaultQualityConfig;
|
|
36
|
+
ctx.ui?.success?.("Collecting quality metrics\u2026");
|
|
37
|
+
const [layering, coupling, types, git] = await Promise.all([
|
|
38
|
+
analyzeLayering({ rootDir: cwd, layerMap: cfg.layers }),
|
|
39
|
+
Promise.resolve(analyzeCoupling({ rootDir: cwd, layerMap: cfg.layers })),
|
|
40
|
+
analyzeTypes(cwd),
|
|
41
|
+
captureGit(ctx.api.shell, cwd)
|
|
42
|
+
]);
|
|
43
|
+
let knip = { unusedFiles: [], unusedExports: [], unusedDependencies: [], unlistedDependencies: [], totalIssues: 0 };
|
|
44
|
+
if (cfg.knip.enabled) {
|
|
45
|
+
knip = await runKnip({ shell: ctx.api.shell, rootDir: cwd });
|
|
46
|
+
}
|
|
47
|
+
const totalPackages = coupling.packages.length;
|
|
48
|
+
const health = calculateHealth({
|
|
49
|
+
layering,
|
|
50
|
+
coupling,
|
|
51
|
+
types,
|
|
52
|
+
knip,
|
|
53
|
+
totalPackages,
|
|
54
|
+
avgTestCoverage: null,
|
|
55
|
+
thresholds: cfg.thresholds
|
|
56
|
+
});
|
|
57
|
+
const store = new QualitySnapshotStore(cwd, cfg.maxSnapshots);
|
|
58
|
+
const snap = store.save({
|
|
59
|
+
score: health.score,
|
|
60
|
+
grade: health.grade,
|
|
61
|
+
dimensions: health.dimensions,
|
|
62
|
+
counters: {
|
|
63
|
+
layeringViolations: layering.totalViolations,
|
|
64
|
+
avgInstability: coupling.avgInstability,
|
|
65
|
+
anyCount: types.packages.reduce((s, p) => s + p.anyCount, 0),
|
|
66
|
+
tsIgnoreCount: types.packages.reduce((s, p) => s + p.tsIgnoreCount, 0),
|
|
67
|
+
unusedFiles: knip.unusedFiles.length,
|
|
68
|
+
unusedDeps: knip.unusedDependencies.length
|
|
69
|
+
},
|
|
70
|
+
git: git ?? { commit: "", branch: "", message: "" }
|
|
71
|
+
});
|
|
72
|
+
if (flags.json) {
|
|
73
|
+
ctx.ui?.json?.(snap);
|
|
74
|
+
} else {
|
|
75
|
+
ctx.ui?.success?.(`Snapshot saved: score ${snap.score}/100 (${snap.grade})`, {
|
|
76
|
+
sections: [{
|
|
77
|
+
header: "Dimensions",
|
|
78
|
+
items: Object.entries(snap.dimensions).map(
|
|
79
|
+
([k, d]) => `${k} ${d.score}/100 (${d.grade})`
|
|
80
|
+
)
|
|
81
|
+
}]
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return { exitCode: 0 };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
export { snapshot_default as default };
|
|
90
|
+
//# sourceMappingURL=snapshot.js.map
|
|
91
|
+
//# sourceMappingURL=snapshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/cli/commands/snapshot.ts"],"names":[],"mappings":"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","file":"snapshot.js","sourcesContent":["import {\n defineCommand,\n useConfig,\n type CLIInput,\n type PluginContextV3,\n} from '@kb-labs/sdk';\nimport {\n analyzeLayering,\n analyzeCoupling,\n runKnip,\n analyzeTypes,\n QualitySnapshotStore,\n} from '@kb-labs/quality-core';\nimport { calculateHealth } from '@kb-labs/quality-core';\nimport { defaultQualityConfig, type QualityPluginConfig, type KnipReport } from '@kb-labs/quality-contracts';\nimport type { SnapshotFlags } from './flags.js';\nimport type { ShellAPI } from '@kb-labs/sdk';\n\nasync function captureGit(shell: ShellAPI, cwd: string) {\n try {\n const [c, b, m] = await Promise.all([\n shell.exec('git', ['rev-parse', '--short', 'HEAD'], { cwd, throwOnError: false }),\n shell.exec('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd, throwOnError: false }),\n shell.exec('git', ['log', '-1', '--format=%s'], { cwd, throwOnError: false }),\n ]);\n if (!c.ok) return undefined;\n return { commit: c.stdout.trim(), branch: b.stdout.trim() || 'HEAD', message: m.stdout.trim() };\n } catch {\n return undefined;\n }\n}\n\nexport default defineCommand<unknown, CLIInput<SnapshotFlags>, { exitCode: number }>({\n id: 'quality:snapshot',\n description: 'Collect all quality metrics and save a snapshot for trend tracking',\n\n handler: {\n async intent(_ctx: PluginContextV3, _input: CLIInput<SnapshotFlags>) {\n return {\n summary: 'Collect all quality metrics and save snapshot for trend tracking',\n operations: [\n { type: 'create' as const, resource: 'file', details: { path: '.kb/quality/snapshots/<timestamp>.json' } },\n ],\n };\n },\n\n async execute(ctx: PluginContextV3, input: CLIInput<SnapshotFlags>): Promise<{ exitCode: number }> {\n const { flags } = input;\n const config = await useConfig<QualityPluginConfig>();\n const cwd = ctx.cwd ?? process.cwd();\n const cfg = config ?? defaultQualityConfig;\n\n ctx.ui?.success?.('Collecting quality metrics…');\n\n const [layering, coupling, types, git] = await Promise.all([\n analyzeLayering({ rootDir: cwd, layerMap: cfg.layers }),\n Promise.resolve(analyzeCoupling({ rootDir: cwd, layerMap: cfg.layers })),\n analyzeTypes(cwd),\n captureGit(ctx.api.shell, cwd),\n ]);\n\n let knip: KnipReport = { unusedFiles: [], unusedExports: [], unusedDependencies: [], unlistedDependencies: [], totalIssues: 0 };\n if (cfg.knip.enabled) {\n knip = await runKnip({ shell: ctx.api.shell, rootDir: cwd });\n }\n\n const totalPackages = coupling.packages.length;\n const health = calculateHealth({\n layering,\n coupling,\n types,\n knip,\n totalPackages,\n avgTestCoverage: null,\n thresholds: cfg.thresholds,\n });\n\n const store = new QualitySnapshotStore(cwd, cfg.maxSnapshots);\n const snap = store.save({\n score: health.score,\n grade: health.grade,\n dimensions: health.dimensions,\n counters: {\n layeringViolations: layering.totalViolations,\n avgInstability: coupling.avgInstability,\n anyCount: types.packages.reduce((s: number, p: { anyCount: number }) => s + p.anyCount, 0),\n tsIgnoreCount: types.packages.reduce((s: number, p: { tsIgnoreCount: number }) => s + p.tsIgnoreCount, 0),\n unusedFiles: knip.unusedFiles.length,\n unusedDeps: knip.unusedDependencies.length,\n },\n git: git ?? { commit: '', branch: '', message: '' },\n });\n\n if (flags.json) {\n ctx.ui?.json?.(snap);\n } else {\n ctx.ui?.success?.(`Snapshot saved: score ${snap.score}/100 (${snap.grade})`, {\n sections: [{\n header: 'Dimensions',\n items: Object.entries(snap.dimensions).map(\n ([k, d]) => `${k} ${d.score}/100 (${d.grade})`\n ),\n }],\n });\n }\n\n return { exitCode: 0 };\n },\n },\n});\n"]}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
|
|
2
|
-
import {
|
|
2
|
+
import { CLIInput } from '@kb-labs/sdk';
|
|
3
3
|
import { StatsFlags } from './flags.js';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
declare const _default: _kb_labs_shared_command_kit.CommandHandlerV3<unknown, StatsInput, StatsResult>;
|
|
5
|
+
declare const _default: _kb_labs_shared_command_kit.CommandHandlerV3<unknown, CLIInput<StatsFlags>, {
|
|
6
|
+
exitCode: number;
|
|
7
|
+
}>;
|
|
9
8
|
|
|
10
9
|
export { _default as default };
|
|
@@ -1,281 +1,33 @@
|
|
|
1
1
|
import { defineCommand } from '@kb-labs/sdk';
|
|
2
|
-
import {
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import path from 'path';
|
|
2
|
+
import { calculateStats } from '@kb-labs/quality-core';
|
|
5
3
|
|
|
6
4
|
// src/cli/commands/stats.ts
|
|
7
5
|
var stats_default = defineCommand({
|
|
8
6
|
id: "quality:stats",
|
|
9
|
-
description: "
|
|
7
|
+
description: "Monorepo statistics: packages, lines of code, size",
|
|
10
8
|
handler: {
|
|
11
9
|
async execute(ctx, input) {
|
|
12
|
-
const {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return { exitCode: 0 };
|
|
19
|
-
}
|
|
10
|
+
const { flags } = input;
|
|
11
|
+
const cwd = ctx.cwd ?? process.cwd();
|
|
12
|
+
const stats = await calculateStats(cwd);
|
|
13
|
+
if (flags.json) {
|
|
14
|
+
ctx.ui?.json?.(stats);
|
|
15
|
+
return { exitCode: 0 };
|
|
20
16
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
17
|
+
ctx.ui?.success?.("Monorepo statistics", {
|
|
18
|
+
sections: [{
|
|
19
|
+
header: "Overview",
|
|
20
|
+
items: [
|
|
21
|
+
`Packages: ${stats.packages}`,
|
|
22
|
+
`Lines of code: ${stats.loc.toLocaleString()}`,
|
|
23
|
+
`Size: ${stats.sizeFormatted}`
|
|
24
|
+
]
|
|
25
|
+
}]
|
|
30
26
|
});
|
|
31
|
-
outputStats(stats, flags, ui);
|
|
32
27
|
return { exitCode: 0 };
|
|
33
28
|
}
|
|
34
29
|
}
|
|
35
30
|
});
|
|
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
31
|
|
|
280
32
|
export { stats_default as default };
|
|
281
33
|
//# sourceMappingURL=stats.js.map
|
|
@@ -1 +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 may come wrapped in input.flags or passed directly\n const flags = ('flags' in input && typeof (input as { flags?: unknown }).flags === 'object' && (input as { flags?: unknown }).flags !== null)\n ? (input as { flags: StatsInput }).flags\n : 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: Record<string, unknown>, ui: PluginContextV3['ui']) {\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"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/cli/commands/stats.ts"],"names":[],"mappings":";;;;AAQA,IAAO,gBAAQ,aAAA,CAAmE;AAAA,EAChF,EAAA,EAAI,eAAA;AAAA,EACJ,WAAA,EAAa,oDAAA;AAAA,EAEb,OAAA,EAAS;AAAA,IACP,MAAM,OAAA,CAAQ,GAAA,EAAsB,KAAA,EAA4D;AAC9F,MAAA,MAAM,EAAE,OAAM,GAAI,KAAA;AAClB,MAAA,MAAM,GAAA,GAAM,GAAA,CAAI,GAAA,IAAO,OAAA,CAAQ,GAAA,EAAI;AAEnC,MAAA,MAAM,KAAA,GAAQ,MAAM,cAAA,CAAe,GAAG,CAAA;AAEtC,MAAA,IAAI,MAAM,IAAA,EAAM;AACd,QAAA,GAAA,CAAI,EAAA,EAAI,OAAO,KAAK,CAAA;AACpB,QAAA,OAAO,EAAE,UAAU,CAAA,EAAE;AAAA,MACvB;AAEA,MAAA,GAAA,CAAI,EAAA,EAAI,UAAU,qBAAA,EAAuB;AAAA,QACvC,UAAU,CAAC;AAAA,UACT,MAAA,EAAQ,UAAA;AAAA,UACR,KAAA,EAAO;AAAA,YACL,CAAA,UAAA,EAAa,MAAM,QAAQ,CAAA,CAAA;AAAA,YAC3B,CAAA,eAAA,EAAkB,KAAA,CAAM,GAAA,CAAI,cAAA,EAAgB,CAAA,CAAA;AAAA,YAC5C,CAAA,MAAA,EAAS,MAAM,aAAa,CAAA;AAAA;AAC9B,SACD;AAAA,OACF,CAAA;AAED,MAAA,OAAO,EAAE,UAAU,CAAA,EAAE;AAAA,IACvB;AAAA;AAEJ,CAAC","file":"stats.js","sourcesContent":["import {\n defineCommand,\n type CLIInput,\n type PluginContextV3,\n} from '@kb-labs/sdk';\nimport { calculateStats } from '@kb-labs/quality-core';\nimport type { StatsFlags } from './flags.js';\n\nexport default defineCommand<unknown, CLIInput<StatsFlags>, { exitCode: number }>({\n id: 'quality:stats',\n description: 'Monorepo statistics: packages, lines of code, size',\n\n handler: {\n async execute(ctx: PluginContextV3, input: CLIInput<StatsFlags>): Promise<{ exitCode: number }> {\n const { flags } = input;\n const cwd = ctx.cwd ?? process.cwd();\n\n const stats = await calculateStats(cwd);\n\n if (flags.json) {\n ctx.ui?.json?.(stats);\n return { exitCode: 0 };\n }\n\n ctx.ui?.success?.('Monorepo statistics', {\n sections: [{\n header: 'Overview',\n items: [\n `Packages: ${stats.packages}`,\n `Lines of code: ${stats.loc.toLocaleString()}`,\n `Size: ${stats.sizeFormatted}`,\n ],\n }],\n });\n\n return { exitCode: 0 };\n },\n },\n});\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { default as manifest, default as manifestDefault } from './manifest.js';
|
|
2
|
-
import '@kb-labs/
|
|
2
|
+
import '@kb-labs/plugin-contracts';
|