@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/README.md +63 -0
- package/dist/builds/index.d.ts +34 -0
- package/dist/builds/index.js +133 -0
- package/dist/builds/index.js.map +1 -0
- package/dist/dead-code/index.d.ts +121 -0
- package/dist/dead-code/index.js +837 -0
- package/dist/dead-code/index.js.map +1 -0
- package/dist/dependencies/index.d.ts +50 -0
- package/dist/dependencies/index.js +200 -0
- package/dist/dependencies/index.js.map +1 -0
- package/dist/graph/index.d.ts +50 -0
- package/dist/graph/index.js +248 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/health/index.d.ts +39 -0
- package/dist/health/index.js +130 -0
- package/dist/health/index.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +1921 -0
- package/dist/index.js.map +1 -0
- package/dist/stale/index.d.ts +31 -0
- package/dist/stale/index.js +273 -0
- package/dist/stale/index.js.map +1 -0
- package/dist/stats/index.d.ts +33 -0
- package/dist/stats/index.js +74 -0
- package/dist/stats/index.js.map +1 -0
- package/dist/tests/index.d.ts +47 -0
- package/dist/tests/index.js +200 -0
- package/dist/tests/index.js.map +1 -0
- package/dist/types/index.d.ts +32 -0
- package/dist/types/index.js +159 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +90 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
// src/tests/run-tests.ts
|
|
7
|
+
var execAsync = promisify(exec);
|
|
8
|
+
function findPackagesWithTests(rootDir, filter) {
|
|
9
|
+
const packages = [];
|
|
10
|
+
if (!fs.existsSync(rootDir)) {
|
|
11
|
+
return packages;
|
|
12
|
+
}
|
|
13
|
+
const entries = fs.readdirSync(rootDir, { withFileTypes: true });
|
|
14
|
+
for (const entry of entries) {
|
|
15
|
+
if (!entry.isDirectory() || !entry.name.startsWith("kb-labs-")) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const repoPath = path.join(rootDir, entry.name);
|
|
19
|
+
const packagesDir = path.join(repoPath, "packages");
|
|
20
|
+
if (!fs.existsSync(packagesDir)) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const packageDirs = fs.readdirSync(packagesDir, { withFileTypes: true });
|
|
24
|
+
for (const pkgDir of packageDirs) {
|
|
25
|
+
if (!pkgDir.isDirectory()) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const packageJsonPath = path.join(packagesDir, pkgDir.name, "package.json");
|
|
29
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
30
|
+
const pkgJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
31
|
+
if (filter && !pkgJson.name.includes(filter) && !pkgDir.name.includes(filter)) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
packages.push({
|
|
35
|
+
name: pkgJson.name,
|
|
36
|
+
dir: path.dirname(packageJsonPath),
|
|
37
|
+
hasTestScript: !!pkgJson.scripts?.test
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return packages;
|
|
43
|
+
}
|
|
44
|
+
async function runPackageTests(packageDir, packageName, timeout) {
|
|
45
|
+
try {
|
|
46
|
+
const { stdout, stderr } = await execAsync("pnpm test", {
|
|
47
|
+
cwd: packageDir,
|
|
48
|
+
timeout,
|
|
49
|
+
env: { ...process.env, CI: "true" }
|
|
50
|
+
// CI mode for non-interactive tests
|
|
51
|
+
});
|
|
52
|
+
return {
|
|
53
|
+
success: true,
|
|
54
|
+
exitCode: 0,
|
|
55
|
+
output: (stdout || "") + (stderr || "")
|
|
56
|
+
};
|
|
57
|
+
} catch (err) {
|
|
58
|
+
return {
|
|
59
|
+
success: false,
|
|
60
|
+
exitCode: err.code || 1,
|
|
61
|
+
error: err.message || "Test execution failed",
|
|
62
|
+
output: (err.stdout || "") + (err.stderr || "")
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function parseTestOutput(output) {
|
|
67
|
+
const vitestMatch = output.match(/Test Files\s+(\d+)\s+passed/);
|
|
68
|
+
const jestMatch = output.match(/Tests:\s+(\d+)\s+passed,\s+(\d+)\s+total/);
|
|
69
|
+
const jestFailMatch = output.match(/(\d+)\s+failed,\s+(\d+)\s+passed,\s+(\d+)\s+total/);
|
|
70
|
+
if (jestFailMatch && jestFailMatch[1] && jestFailMatch[2] && jestFailMatch[3]) {
|
|
71
|
+
return {
|
|
72
|
+
failed: parseInt(jestFailMatch[1], 10),
|
|
73
|
+
passed: parseInt(jestFailMatch[2], 10),
|
|
74
|
+
total: parseInt(jestFailMatch[3], 10)
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (jestMatch && jestMatch[1] && jestMatch[2]) {
|
|
78
|
+
return {
|
|
79
|
+
passed: parseInt(jestMatch[1], 10),
|
|
80
|
+
total: parseInt(jestMatch[2], 10)
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (vitestMatch && vitestMatch[1]) {
|
|
84
|
+
return {
|
|
85
|
+
passed: parseInt(vitestMatch[1], 10),
|
|
86
|
+
total: parseInt(vitestMatch[1], 10)
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return {};
|
|
90
|
+
}
|
|
91
|
+
function readCoverage(packageDir) {
|
|
92
|
+
const coveragePath = path.join(packageDir, "coverage", "coverage-summary.json");
|
|
93
|
+
if (!fs.existsSync(coveragePath)) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const coverageData = JSON.parse(fs.readFileSync(coveragePath, "utf-8"));
|
|
98
|
+
const total = coverageData.total;
|
|
99
|
+
return {
|
|
100
|
+
lines: total?.lines?.pct || 0,
|
|
101
|
+
statements: total?.statements?.pct || 0,
|
|
102
|
+
functions: total?.functions?.pct || 0,
|
|
103
|
+
branches: total?.branches?.pct || 0
|
|
104
|
+
};
|
|
105
|
+
} catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async function runTests(rootDir, options = {}) {
|
|
110
|
+
const startTime = Date.now();
|
|
111
|
+
const timeout = options.timeout || 6e4;
|
|
112
|
+
const packages = findPackagesWithTests(rootDir, options.packageFilter);
|
|
113
|
+
const result = {
|
|
114
|
+
totalPackages: packages.length,
|
|
115
|
+
passing: 0,
|
|
116
|
+
failing: 0,
|
|
117
|
+
skipped: 0,
|
|
118
|
+
failures: [],
|
|
119
|
+
summary: {
|
|
120
|
+
totalTests: 0,
|
|
121
|
+
passedTests: 0,
|
|
122
|
+
failedTests: 0
|
|
123
|
+
},
|
|
124
|
+
coverage: {
|
|
125
|
+
avgCoverage: 0,
|
|
126
|
+
packages: []
|
|
127
|
+
},
|
|
128
|
+
duration: 0
|
|
129
|
+
};
|
|
130
|
+
for (const { name, dir, hasTestScript } of packages) {
|
|
131
|
+
if (!hasTestScript) {
|
|
132
|
+
result.skipped++;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (options.coverageOnly) {
|
|
136
|
+
const coverage = readCoverage(dir);
|
|
137
|
+
if (coverage) {
|
|
138
|
+
result.coverage.packages.push({
|
|
139
|
+
name,
|
|
140
|
+
...coverage
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const testResult = await runPackageTests(dir, name, timeout);
|
|
146
|
+
if (testResult.success) {
|
|
147
|
+
result.passing++;
|
|
148
|
+
if (testResult.output) {
|
|
149
|
+
const counts = parseTestOutput(testResult.output);
|
|
150
|
+
if (counts.total) {
|
|
151
|
+
result.summary.totalTests += counts.total;
|
|
152
|
+
}
|
|
153
|
+
if (counts.passed) {
|
|
154
|
+
result.summary.passedTests += counts.passed;
|
|
155
|
+
}
|
|
156
|
+
if (counts.failed) {
|
|
157
|
+
result.summary.failedTests += counts.failed || 0;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
result.failing++;
|
|
162
|
+
const counts = testResult.output ? parseTestOutput(testResult.output) : {};
|
|
163
|
+
result.failures.push({
|
|
164
|
+
package: name,
|
|
165
|
+
error: testResult.error || "Unknown error",
|
|
166
|
+
exitCode: testResult.exitCode,
|
|
167
|
+
failedTests: counts.failed,
|
|
168
|
+
totalTests: counts.total
|
|
169
|
+
});
|
|
170
|
+
if (counts.total) {
|
|
171
|
+
result.summary.totalTests += counts.total;
|
|
172
|
+
}
|
|
173
|
+
if (counts.passed) {
|
|
174
|
+
result.summary.passedTests += counts.passed || 0;
|
|
175
|
+
}
|
|
176
|
+
if (counts.failed) {
|
|
177
|
+
result.summary.failedTests += counts.failed;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (options.withCoverage) {
|
|
181
|
+
const coverage = readCoverage(dir);
|
|
182
|
+
if (coverage) {
|
|
183
|
+
result.coverage.packages.push({
|
|
184
|
+
name,
|
|
185
|
+
...coverage
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (result.coverage.packages.length > 0) {
|
|
191
|
+
const totalLines = result.coverage.packages.reduce((sum, pkg) => sum + pkg.lines, 0);
|
|
192
|
+
result.coverage.avgCoverage = totalLines / result.coverage.packages.length;
|
|
193
|
+
}
|
|
194
|
+
result.duration = Date.now() - startTime;
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export { runTests };
|
|
199
|
+
//# sourceMappingURL=index.js.map
|
|
200
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/tests/run-tests.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Test execution and coverage tracking\n *\n * Runs tests across monorepo packages and collects coverage statistics\n */\n\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nconst execAsync = promisify(exec);\n\nexport interface TestRunOptions {\n packageFilter?: string;\n timeout?: number;\n withCoverage?: boolean;\n coverageOnly?: boolean;\n concurrency?: number; // Max parallel test runs (default: 3)\n}\n\nexport interface TestRunResult {\n totalPackages: number;\n passing: number;\n failing: number;\n skipped: number;\n failures: Array<{\n package: string;\n error: string;\n exitCode: number;\n failedTests?: number;\n totalTests?: number;\n }>;\n summary: {\n totalTests: number;\n passedTests: number;\n failedTests: number;\n };\n coverage: {\n avgCoverage: number;\n packages: Array<{\n name: string;\n lines: number;\n statements: number;\n functions: number;\n branches: number;\n }>;\n };\n duration: number;\n}\n\ninterface PackageWithTests {\n name: string;\n dir: string;\n hasTestScript: boolean;\n}\n\n/**\n * Find all packages with test scripts\n */\nfunction findPackagesWithTests(rootDir: string, filter?: string): PackageWithTests[] {\n const packages: PackageWithTests[] = [];\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 const pkgJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));\n\n // Skip if filter doesn't match\n if (filter && !pkgJson.name.includes(filter) && !pkgDir.name.includes(filter)) {\n continue;\n }\n\n packages.push({\n name: pkgJson.name,\n dir: path.dirname(packageJsonPath),\n hasTestScript: !!pkgJson.scripts?.test,\n });\n }\n }\n }\n\n return packages;\n}\n\n/**\n * Run tests for a package\n */\nasync function runPackageTests(\n packageDir: string,\n packageName: string,\n timeout: number\n): Promise<{ success: boolean; exitCode: number; error?: string; output?: string }> {\n try {\n const { stdout, stderr } = await execAsync('pnpm test', {\n cwd: packageDir,\n timeout,\n env: { ...process.env, CI: 'true' }, // CI mode for non-interactive tests\n });\n\n return {\n success: true,\n exitCode: 0,\n output: (stdout || '') + (stderr || ''),\n };\n } catch (err: any) {\n return {\n success: false,\n exitCode: err.code || 1,\n error: err.message || 'Test execution failed',\n output: (err.stdout || '') + (err.stderr || ''),\n };\n }\n}\n\n/**\n * Parse test output to extract test counts (vitest/jest)\n */\nfunction parseTestOutput(output: string): { total?: number; passed?: number; failed?: number } {\n // Vitest format: \"Test Files 1 passed (1)\"\n // Jest format: \"Tests: 5 passed, 5 total\"\n\n const vitestMatch = output.match(/Test Files\\s+(\\d+)\\s+passed/);\n const jestMatch = output.match(/Tests:\\s+(\\d+)\\s+passed,\\s+(\\d+)\\s+total/);\n const jestFailMatch = output.match(/(\\d+)\\s+failed,\\s+(\\d+)\\s+passed,\\s+(\\d+)\\s+total/);\n\n if (jestFailMatch && jestFailMatch[1] && jestFailMatch[2] && jestFailMatch[3]) {\n return {\n failed: parseInt(jestFailMatch[1], 10),\n passed: parseInt(jestFailMatch[2], 10),\n total: parseInt(jestFailMatch[3], 10),\n };\n }\n\n if (jestMatch && jestMatch[1] && jestMatch[2]) {\n return {\n passed: parseInt(jestMatch[1], 10),\n total: parseInt(jestMatch[2], 10),\n };\n }\n\n if (vitestMatch && vitestMatch[1]) {\n return {\n passed: parseInt(vitestMatch[1], 10),\n total: parseInt(vitestMatch[1], 10),\n };\n }\n\n return {};\n}\n\n/**\n * Run async tasks with concurrency limit\n */\nasync function runWithConcurrency<T, R>(\n items: T[],\n handler: (item: T) => Promise<R>,\n concurrency: number\n): Promise<R[]> {\n const results: R[] = [];\n const executing: Promise<void>[] = [];\n\n for (const item of items) {\n const promise = handler(item).then(result => {\n results.push(result);\n });\n\n executing.push(promise);\n\n if (executing.length >= concurrency) {\n await Promise.race(executing);\n executing.splice(\n executing.findIndex(p => p === promise),\n 1\n );\n }\n }\n\n await Promise.all(executing);\n return results;\n}\n\n/**\n * Read coverage data from coverage-summary.json\n */\nfunction readCoverage(packageDir: string): {\n lines: number;\n statements: number;\n functions: number;\n branches: number;\n} | null {\n const coveragePath = path.join(packageDir, 'coverage', 'coverage-summary.json');\n\n if (!fs.existsSync(coveragePath)) {\n return null;\n }\n\n try {\n const coverageData = JSON.parse(fs.readFileSync(coveragePath, 'utf-8'));\n const total = coverageData.total;\n\n return {\n lines: total?.lines?.pct || 0,\n statements: total?.statements?.pct || 0,\n functions: total?.functions?.pct || 0,\n branches: total?.branches?.pct || 0,\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Run tests across monorepo\n */\nexport async function runTests(\n rootDir: string,\n options: TestRunOptions = {}\n): Promise<TestRunResult> {\n const startTime = Date.now();\n const timeout = options.timeout || 60000; // 60s per package\n const packages = findPackagesWithTests(rootDir, options.packageFilter);\n\n const result: TestRunResult = {\n totalPackages: packages.length,\n passing: 0,\n failing: 0,\n skipped: 0,\n failures: [],\n summary: {\n totalTests: 0,\n passedTests: 0,\n failedTests: 0,\n },\n coverage: {\n avgCoverage: 0,\n packages: [],\n },\n duration: 0,\n };\n\n for (const { name, dir, hasTestScript } of packages) {\n if (!hasTestScript) {\n result.skipped++;\n continue;\n }\n\n // Coverage-only mode: just read existing coverage\n if (options.coverageOnly) {\n const coverage = readCoverage(dir);\n if (coverage) {\n result.coverage.packages.push({\n name,\n ...coverage,\n });\n }\n continue;\n }\n\n // Run tests\n const testResult = await runPackageTests(dir, name, timeout);\n\n if (testResult.success) {\n result.passing++;\n\n // Parse test counts\n if (testResult.output) {\n const counts = parseTestOutput(testResult.output);\n if (counts.total) {result.summary.totalTests += counts.total;}\n if (counts.passed) {result.summary.passedTests += counts.passed;}\n if (counts.failed) {result.summary.failedTests += counts.failed || 0;}\n }\n } else {\n result.failing++;\n\n // Parse failure info\n const counts = testResult.output ? parseTestOutput(testResult.output) : {};\n\n result.failures.push({\n package: name,\n error: testResult.error || 'Unknown error',\n exitCode: testResult.exitCode,\n failedTests: counts.failed,\n totalTests: counts.total,\n });\n\n if (counts.total) {result.summary.totalTests += counts.total;}\n if (counts.passed) {result.summary.passedTests += counts.passed || 0;}\n if (counts.failed) {result.summary.failedTests += counts.failed;}\n }\n\n // Collect coverage if requested\n if (options.withCoverage) {\n const coverage = readCoverage(dir);\n if (coverage) {\n result.coverage.packages.push({\n name,\n ...coverage,\n });\n }\n }\n }\n\n // Calculate average coverage\n if (result.coverage.packages.length > 0) {\n const totalLines = result.coverage.packages.reduce((sum, pkg) => sum + pkg.lines, 0);\n result.coverage.avgCoverage = totalLines / result.coverage.packages.length;\n }\n\n result.duration = Date.now() - startTime;\n\n return result;\n}\n"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript type safety analysis
|
|
3
|
+
*
|
|
4
|
+
* Uses TypeScript Compiler API for semantic analysis
|
|
5
|
+
* Simplified version compared to devkit-types-audit (focuses on essential metrics)
|
|
6
|
+
*/
|
|
7
|
+
interface TypeAnalysisResult {
|
|
8
|
+
totalPackages: number;
|
|
9
|
+
packagesWithErrors: number;
|
|
10
|
+
totalErrors: number;
|
|
11
|
+
totalWarnings: number;
|
|
12
|
+
avgCoverage: number;
|
|
13
|
+
packages: Array<{
|
|
14
|
+
name: string;
|
|
15
|
+
errors: number;
|
|
16
|
+
warnings: number;
|
|
17
|
+
coverage: number;
|
|
18
|
+
anyCount: number;
|
|
19
|
+
tsIgnoreCount: number;
|
|
20
|
+
}>;
|
|
21
|
+
duration: number;
|
|
22
|
+
}
|
|
23
|
+
interface TypeAnalysisOptions {
|
|
24
|
+
packageFilter?: string;
|
|
25
|
+
errorsOnly?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Analyze types across monorepo
|
|
29
|
+
*/
|
|
30
|
+
declare function analyzeTypes(rootDir: string, options?: TypeAnalysisOptions): Promise<TypeAnalysisResult>;
|
|
31
|
+
|
|
32
|
+
export { type TypeAnalysisOptions, type TypeAnalysisResult, analyzeTypes };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
// src/types/analyze-types.ts
|
|
6
|
+
function findPackagesWithTsConfig(rootDir, filter) {
|
|
7
|
+
const packages = [];
|
|
8
|
+
if (!fs.existsSync(rootDir)) {
|
|
9
|
+
return packages;
|
|
10
|
+
}
|
|
11
|
+
const entries = fs.readdirSync(rootDir, { withFileTypes: true });
|
|
12
|
+
for (const entry of entries) {
|
|
13
|
+
if (!entry.isDirectory() || !entry.name.startsWith("kb-labs-")) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
const repoPath = path.join(rootDir, entry.name);
|
|
17
|
+
const packagesDir = path.join(repoPath, "packages");
|
|
18
|
+
if (!fs.existsSync(packagesDir)) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const packageDirs = fs.readdirSync(packagesDir, { withFileTypes: true });
|
|
22
|
+
for (const pkgDir of packageDirs) {
|
|
23
|
+
if (!pkgDir.isDirectory()) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const packageJsonPath = path.join(packagesDir, pkgDir.name, "package.json");
|
|
27
|
+
const tsconfigPath = path.join(packagesDir, pkgDir.name, "tsconfig.json");
|
|
28
|
+
if (fs.existsSync(packageJsonPath) && fs.existsSync(tsconfigPath)) {
|
|
29
|
+
const pkgJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
30
|
+
if (filter && !pkgJson.name.includes(filter) && !pkgDir.name.includes(filter)) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
packages.push({
|
|
34
|
+
name: pkgJson.name,
|
|
35
|
+
dir: path.dirname(packageJsonPath),
|
|
36
|
+
tsconfigPath
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return packages;
|
|
42
|
+
}
|
|
43
|
+
function createProgram(packageDir, tsconfigPath) {
|
|
44
|
+
try {
|
|
45
|
+
const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
|
|
46
|
+
if (configFile.error) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const parsedConfig = ts.parseJsonConfigFileContent(
|
|
50
|
+
configFile.config,
|
|
51
|
+
ts.sys,
|
|
52
|
+
packageDir
|
|
53
|
+
);
|
|
54
|
+
if (parsedConfig.errors.length > 0) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
return ts.createProgram({
|
|
58
|
+
rootNames: parsedConfig.fileNames,
|
|
59
|
+
options: parsedConfig.options
|
|
60
|
+
});
|
|
61
|
+
} catch (err) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function analyzePackageTypes(program) {
|
|
66
|
+
const diagnostics = ts.getPreEmitDiagnostics(program);
|
|
67
|
+
let errors = 0;
|
|
68
|
+
let warnings = 0;
|
|
69
|
+
for (const diagnostic of diagnostics) {
|
|
70
|
+
if (!diagnostic.file) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (diagnostic.category === ts.DiagnosticCategory.Error) {
|
|
74
|
+
errors++;
|
|
75
|
+
} else {
|
|
76
|
+
warnings++;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { errors, warnings };
|
|
80
|
+
}
|
|
81
|
+
function calculateTypeCoverage(program) {
|
|
82
|
+
const checker = program.getTypeChecker();
|
|
83
|
+
const sourceFiles = program.getSourceFiles().filter(
|
|
84
|
+
(sf) => !sf.isDeclarationFile && !sf.fileName.includes("node_modules")
|
|
85
|
+
);
|
|
86
|
+
let totalSymbols = 0;
|
|
87
|
+
let typedSymbols = 0;
|
|
88
|
+
let anyCount = 0;
|
|
89
|
+
let tsIgnoreCount = 0;
|
|
90
|
+
for (const sourceFile of sourceFiles) {
|
|
91
|
+
const text = sourceFile.getFullText();
|
|
92
|
+
const tsIgnoreMatches = text.match(/@ts-ignore/g);
|
|
93
|
+
tsIgnoreCount += tsIgnoreMatches ? tsIgnoreMatches.length : 0;
|
|
94
|
+
ts.forEachChild(sourceFile, function visit(node) {
|
|
95
|
+
if (ts.isTypeNode(node)) {
|
|
96
|
+
totalSymbols++;
|
|
97
|
+
const type = checker.getTypeAtLocation(node);
|
|
98
|
+
if (type.flags & ts.TypeFlags.Any) {
|
|
99
|
+
anyCount++;
|
|
100
|
+
} else {
|
|
101
|
+
typedSymbols++;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
ts.forEachChild(node, visit);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
const coverage = totalSymbols > 0 ? typedSymbols / totalSymbols * 100 : 100;
|
|
108
|
+
return {
|
|
109
|
+
coverage: Math.round(coverage * 10) / 10,
|
|
110
|
+
totalSymbols,
|
|
111
|
+
typedSymbols,
|
|
112
|
+
anyCount,
|
|
113
|
+
tsIgnoreCount
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
async function analyzeTypes(rootDir, options = {}) {
|
|
117
|
+
const startTime = Date.now();
|
|
118
|
+
const packages = findPackagesWithTsConfig(rootDir, options.packageFilter);
|
|
119
|
+
const result = {
|
|
120
|
+
totalPackages: packages.length,
|
|
121
|
+
packagesWithErrors: 0,
|
|
122
|
+
totalErrors: 0,
|
|
123
|
+
totalWarnings: 0,
|
|
124
|
+
avgCoverage: 0,
|
|
125
|
+
packages: [],
|
|
126
|
+
duration: 0
|
|
127
|
+
};
|
|
128
|
+
for (const { name, dir, tsconfigPath } of packages) {
|
|
129
|
+
const program = createProgram(dir, tsconfigPath);
|
|
130
|
+
if (!program) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const { errors, warnings } = analyzePackageTypes(program);
|
|
134
|
+
const coverage = calculateTypeCoverage(program);
|
|
135
|
+
result.totalErrors += errors;
|
|
136
|
+
result.totalWarnings += warnings;
|
|
137
|
+
if (errors > 0) {
|
|
138
|
+
result.packagesWithErrors++;
|
|
139
|
+
}
|
|
140
|
+
result.packages.push({
|
|
141
|
+
name,
|
|
142
|
+
errors,
|
|
143
|
+
warnings,
|
|
144
|
+
coverage: coverage.coverage,
|
|
145
|
+
anyCount: coverage.anyCount,
|
|
146
|
+
tsIgnoreCount: coverage.tsIgnoreCount
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
if (result.packages.length > 0) {
|
|
150
|
+
result.avgCoverage = result.packages.reduce((sum, p) => sum + p.coverage, 0) / result.packages.length;
|
|
151
|
+
result.avgCoverage = Math.round(result.avgCoverage * 10) / 10;
|
|
152
|
+
}
|
|
153
|
+
result.duration = Date.now() - startTime;
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export { analyzeTypes };
|
|
158
|
+
//# sourceMappingURL=index.js.map
|
|
159
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/types/analyze-types.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * TypeScript type safety analysis\n *\n * Uses TypeScript Compiler API for semantic analysis\n * Simplified version compared to devkit-types-audit (focuses on essential metrics)\n */\n\nimport ts from 'typescript';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nexport interface TypeAnalysisResult {\n totalPackages: number;\n packagesWithErrors: number;\n totalErrors: number;\n totalWarnings: number;\n avgCoverage: number;\n packages: Array<{\n name: string;\n errors: number;\n warnings: number;\n coverage: number;\n anyCount: number;\n tsIgnoreCount: number;\n }>;\n duration: number;\n}\n\nexport interface TypeAnalysisOptions {\n packageFilter?: string;\n errorsOnly?: boolean;\n}\n\ninterface PackageWithTsConfig {\n name: string;\n dir: string;\n tsconfigPath: string;\n}\n\n/**\n * Find all packages with tsconfig.json\n */\nfunction findPackagesWithTsConfig(rootDir: string, filter?: string): PackageWithTsConfig[] {\n const packages: PackageWithTsConfig[] = [];\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 const tsconfigPath = path.join(packagesDir, pkgDir.name, 'tsconfig.json');\n\n if (fs.existsSync(packageJsonPath) && fs.existsSync(tsconfigPath)) {\n const pkgJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));\n\n // Skip if filter doesn't match\n if (filter && !pkgJson.name.includes(filter) && !pkgDir.name.includes(filter)) {\n continue;\n }\n\n packages.push({\n name: pkgJson.name,\n dir: path.dirname(packageJsonPath),\n tsconfigPath,\n });\n }\n }\n }\n\n return packages;\n}\n\n/**\n * Create TypeScript program for a package\n */\nfunction createProgram(packageDir: string, tsconfigPath: string): ts.Program | null {\n try {\n const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);\n if (configFile.error) {return null;}\n\n const parsedConfig = ts.parseJsonConfigFileContent(\n configFile.config,\n ts.sys,\n packageDir\n );\n\n if (parsedConfig.errors.length > 0) {return null;}\n\n return ts.createProgram({\n rootNames: parsedConfig.fileNames,\n options: parsedConfig.options,\n });\n } catch (err) {\n return null;\n }\n}\n\n/**\n * Analyze type errors in a package\n */\nfunction analyzePackageTypes(program: ts.Program): { errors: number; warnings: number } {\n const diagnostics = ts.getPreEmitDiagnostics(program);\n\n let errors = 0;\n let warnings = 0;\n\n for (const diagnostic of diagnostics) {\n if (!diagnostic.file) {continue;}\n\n if (diagnostic.category === ts.DiagnosticCategory.Error) {\n errors++;\n } else {\n warnings++;\n }\n }\n\n return { errors, warnings };\n}\n\n/**\n * Calculate type coverage (simplified version)\n */\nfunction calculateTypeCoverage(program: ts.Program): {\n coverage: number;\n totalSymbols: number;\n typedSymbols: number;\n anyCount: number;\n tsIgnoreCount: number;\n} {\n const checker = program.getTypeChecker();\n const sourceFiles = program.getSourceFiles().filter(\n (sf) => !sf.isDeclarationFile && !sf.fileName.includes('node_modules')\n );\n\n let totalSymbols = 0;\n let typedSymbols = 0;\n let anyCount = 0;\n let tsIgnoreCount = 0;\n\n for (const sourceFile of sourceFiles) {\n // Count @ts-ignore comments\n const text = sourceFile.getFullText();\n const tsIgnoreMatches = text.match(/@ts-ignore/g);\n tsIgnoreCount += tsIgnoreMatches ? tsIgnoreMatches.length : 0;\n\n // Simplified type counting (focus on type nodes)\n ts.forEachChild(sourceFile, function visit(node) {\n if (ts.isTypeNode(node)) {\n totalSymbols++;\n const type = checker.getTypeAtLocation(node);\n\n if (type.flags & ts.TypeFlags.Any) {\n anyCount++;\n } else {\n typedSymbols++;\n }\n }\n\n ts.forEachChild(node, visit);\n });\n }\n\n const coverage = totalSymbols > 0 ? (typedSymbols / totalSymbols) * 100 : 100;\n\n return {\n coverage: Math.round(coverage * 10) / 10,\n totalSymbols,\n typedSymbols,\n anyCount,\n tsIgnoreCount,\n };\n}\n\n/**\n * Analyze types across monorepo\n */\nexport async function analyzeTypes(\n rootDir: string,\n options: TypeAnalysisOptions = {}\n): Promise<TypeAnalysisResult> {\n const startTime = Date.now();\n const packages = findPackagesWithTsConfig(rootDir, options.packageFilter);\n\n const result: TypeAnalysisResult = {\n totalPackages: packages.length,\n packagesWithErrors: 0,\n totalErrors: 0,\n totalWarnings: 0,\n avgCoverage: 0,\n packages: [],\n duration: 0,\n };\n\n for (const { name, dir, tsconfigPath } of packages) {\n const program = createProgram(dir, tsconfigPath);\n if (!program) {\n // Skip packages with invalid tsconfig\n continue;\n }\n\n const { errors, warnings } = analyzePackageTypes(program);\n const coverage = calculateTypeCoverage(program);\n\n result.totalErrors += errors;\n result.totalWarnings += warnings;\n\n if (errors > 0) {\n result.packagesWithErrors++;\n }\n\n result.packages.push({\n name,\n errors,\n warnings,\n coverage: coverage.coverage,\n anyCount: coverage.anyCount,\n tsIgnoreCount: coverage.tsIgnoreCount,\n });\n }\n\n // Calculate average coverage\n if (result.packages.length > 0) {\n result.avgCoverage =\n result.packages.reduce((sum, p) => sum + p.coverage, 0) / result.packages.length;\n result.avgCoverage = Math.round(result.avgCoverage * 10) / 10;\n }\n\n result.duration = Date.now() - startTime;\n\n return result;\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kb-labs/quality-core",
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Core business logic for KB Labs Quality plugin - monorepo analysis, dependency management, and health checks.",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"./stats": {
|
|
14
|
+
"import": "./dist/stats/index.js",
|
|
15
|
+
"types": "./dist/stats/index.d.ts"
|
|
16
|
+
},
|
|
17
|
+
"./health": {
|
|
18
|
+
"import": "./dist/health/index.js",
|
|
19
|
+
"types": "./dist/health/index.d.ts"
|
|
20
|
+
},
|
|
21
|
+
"./dependencies": {
|
|
22
|
+
"import": "./dist/dependencies/index.js",
|
|
23
|
+
"types": "./dist/dependencies/index.d.ts"
|
|
24
|
+
},
|
|
25
|
+
"./build-order": {
|
|
26
|
+
"import": "./dist/build-order/index.js",
|
|
27
|
+
"types": "./dist/build-order/index.d.ts"
|
|
28
|
+
},
|
|
29
|
+
"./graph": {
|
|
30
|
+
"import": "./dist/graph/index.js",
|
|
31
|
+
"types": "./dist/graph/index.d.ts"
|
|
32
|
+
},
|
|
33
|
+
"./stale": {
|
|
34
|
+
"import": "./dist/stale/index.js",
|
|
35
|
+
"types": "./dist/stale/index.d.ts"
|
|
36
|
+
},
|
|
37
|
+
"./builds": {
|
|
38
|
+
"import": "./dist/builds/index.js",
|
|
39
|
+
"types": "./dist/builds/index.d.ts"
|
|
40
|
+
},
|
|
41
|
+
"./types": {
|
|
42
|
+
"import": "./dist/types/index.js",
|
|
43
|
+
"types": "./dist/types/index.d.ts"
|
|
44
|
+
},
|
|
45
|
+
"./tests": {
|
|
46
|
+
"import": "./dist/tests/index.js",
|
|
47
|
+
"types": "./dist/tests/index.d.ts"
|
|
48
|
+
},
|
|
49
|
+
"./dead-code": {
|
|
50
|
+
"import": "./dist/dead-code/index.js",
|
|
51
|
+
"types": "./dist/dead-code/index.d.ts"
|
|
52
|
+
},
|
|
53
|
+
"./dist/*": "./dist/*"
|
|
54
|
+
},
|
|
55
|
+
"files": [
|
|
56
|
+
"dist",
|
|
57
|
+
"README.md",
|
|
58
|
+
"LICENSE"
|
|
59
|
+
],
|
|
60
|
+
"sideEffects": false,
|
|
61
|
+
"scripts": {
|
|
62
|
+
"pretype-check": "pnpm --filter @kb-labs/quality-core build",
|
|
63
|
+
"clean": "rimraf dist",
|
|
64
|
+
"build": "tsup --config tsup.config.ts",
|
|
65
|
+
"dev": "tsup --config tsup.config.ts --watch",
|
|
66
|
+
"lint": "eslint src --ext .ts",
|
|
67
|
+
"lint:fix": "eslint . --fix",
|
|
68
|
+
"type-check": "tsc --noEmit",
|
|
69
|
+
"test": "vitest run --passWithNoTests",
|
|
70
|
+
"test:watch": "vitest"
|
|
71
|
+
},
|
|
72
|
+
"dependencies": {
|
|
73
|
+
"@kb-labs/quality-contracts": "^0.6.0",
|
|
74
|
+
"@kb-labs/sdk": "^1.5.0",
|
|
75
|
+
"globby": "^11.0.0",
|
|
76
|
+
"minimatch": "^10.0.1",
|
|
77
|
+
"typescript": "^5.6.3"
|
|
78
|
+
},
|
|
79
|
+
"devDependencies": {
|
|
80
|
+
"@kb-labs/devkit": "link:../../../../infra/kb-labs-devkit",
|
|
81
|
+
"@types/node": "^24.3.3",
|
|
82
|
+
"rimraf": "^6.0.1",
|
|
83
|
+
"tsup": "^8.5.0",
|
|
84
|
+
"vitest": "^3.2.4"
|
|
85
|
+
},
|
|
86
|
+
"engines": {
|
|
87
|
+
"node": ">=20.0.0",
|
|
88
|
+
"pnpm": ">=9.0.0"
|
|
89
|
+
}
|
|
90
|
+
}
|