@aiready/testability 0.1.6 → 0.1.8
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/.turbo/turbo-build.log +9 -9
- package/.turbo/turbo-lint.log +5 -0
- package/.turbo/turbo-test.log +5 -4
- package/dist/chunk-D7AV63F3.mjs +332 -0
- package/dist/chunk-DDNB7FI4.mjs +333 -0
- package/dist/chunk-PAP7ZRNB.mjs +366 -0
- package/dist/cli.js +25 -55
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +25 -55
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
- package/src/analyzer.ts +31 -69
- package/src/scoring.ts +0 -1
- package/src/types.ts +3 -1
package/dist/cli.js
CHANGED
|
@@ -27,63 +27,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
27
|
var import_commander = require("commander");
|
|
28
28
|
|
|
29
29
|
// src/analyzer.ts
|
|
30
|
+
var import_core = require("@aiready/core");
|
|
30
31
|
var import_fs = require("fs");
|
|
31
32
|
var import_path = require("path");
|
|
32
33
|
var import_typescript_estree = require("@typescript-eslint/typescript-estree");
|
|
33
|
-
var import_core = require("@aiready/core");
|
|
34
|
-
var SRC_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
|
|
35
|
-
var DEFAULT_EXCLUDES = [
|
|
36
|
-
"node_modules",
|
|
37
|
-
"dist",
|
|
38
|
-
".git",
|
|
39
|
-
"coverage",
|
|
40
|
-
".turbo",
|
|
41
|
-
"build"
|
|
42
|
-
];
|
|
43
|
-
var TEST_PATTERNS = [
|
|
44
|
-
/\.(test|spec)\.(ts|tsx|js|jsx)$/,
|
|
45
|
-
/__tests__\//,
|
|
46
|
-
/\/tests?\//,
|
|
47
|
-
/\/e2e\//,
|
|
48
|
-
/\/fixtures\//
|
|
49
|
-
];
|
|
50
|
-
function isTestFile(filePath, extra) {
|
|
51
|
-
if (TEST_PATTERNS.some((p) => p.test(filePath))) return true;
|
|
52
|
-
if (extra) return extra.some((p) => filePath.includes(p));
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
function isSourceFile(filePath) {
|
|
56
|
-
return SRC_EXTENSIONS.has((0, import_path.extname)(filePath));
|
|
57
|
-
}
|
|
58
|
-
function collectFiles(dir, options, depth = 0) {
|
|
59
|
-
if (depth > (options.maxDepth ?? 20)) return [];
|
|
60
|
-
const excludes = [...DEFAULT_EXCLUDES, ...options.exclude ?? []];
|
|
61
|
-
const files = [];
|
|
62
|
-
let entries;
|
|
63
|
-
try {
|
|
64
|
-
entries = (0, import_fs.readdirSync)(dir);
|
|
65
|
-
} catch {
|
|
66
|
-
return files;
|
|
67
|
-
}
|
|
68
|
-
for (const entry of entries) {
|
|
69
|
-
if (excludes.some((ex) => entry === ex || entry.includes(ex))) continue;
|
|
70
|
-
const full = (0, import_path.join)(dir, entry);
|
|
71
|
-
let stat;
|
|
72
|
-
try {
|
|
73
|
-
stat = (0, import_fs.statSync)(full);
|
|
74
|
-
} catch {
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
if (stat.isDirectory()) {
|
|
78
|
-
files.push(...collectFiles(full, options, depth + 1));
|
|
79
|
-
} else if (stat.isFile() && isSourceFile(full)) {
|
|
80
|
-
if (!options.include || options.include.some((p) => full.includes(p))) {
|
|
81
|
-
files.push(full);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return files;
|
|
86
|
-
}
|
|
87
34
|
function countMethodsInInterface(node) {
|
|
88
35
|
if (node.type === "TSInterfaceDeclaration") {
|
|
89
36
|
return node.body.body.filter(
|
|
@@ -235,8 +182,24 @@ function detectTestFramework(rootDir) {
|
|
|
235
182
|
return false;
|
|
236
183
|
}
|
|
237
184
|
}
|
|
185
|
+
var TEST_PATTERNS = [
|
|
186
|
+
/\.(test|spec)\.(ts|tsx|js|jsx)$/,
|
|
187
|
+
/__tests__\//,
|
|
188
|
+
/\/tests?\//,
|
|
189
|
+
/\/e2e\//,
|
|
190
|
+
/\/fixtures\//
|
|
191
|
+
];
|
|
192
|
+
function isTestFile(filePath, extra) {
|
|
193
|
+
if (TEST_PATTERNS.some((p) => p.test(filePath))) return true;
|
|
194
|
+
if (extra) return extra.some((p) => filePath.includes(p));
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
238
197
|
async function analyzeTestability(options) {
|
|
239
|
-
const allFiles =
|
|
198
|
+
const allFiles = await (0, import_core.scanFiles)({
|
|
199
|
+
...options,
|
|
200
|
+
include: options.include || ["**/*.{ts,tsx,js,jsx}"],
|
|
201
|
+
includeTests: true
|
|
202
|
+
});
|
|
240
203
|
const sourceFiles = allFiles.filter(
|
|
241
204
|
(f) => !isTestFile(f, options.testPatterns)
|
|
242
205
|
);
|
|
@@ -250,7 +213,14 @@ async function analyzeTestability(options) {
|
|
|
250
213
|
totalInterfaces: 0,
|
|
251
214
|
externalStateMutations: 0
|
|
252
215
|
};
|
|
216
|
+
let processed = 0;
|
|
253
217
|
for (const f of sourceFiles) {
|
|
218
|
+
processed++;
|
|
219
|
+
options.onProgress?.(
|
|
220
|
+
processed,
|
|
221
|
+
sourceFiles.length,
|
|
222
|
+
`testability: analyzing files`
|
|
223
|
+
);
|
|
254
224
|
const a = analyzeFileTestability(f);
|
|
255
225
|
for (const key of Object.keys(aggregated)) {
|
|
256
226
|
aggregated[key] += a[key];
|
package/dist/cli.mjs
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -13,6 +13,8 @@ interface TestabilityOptions {
|
|
|
13
13
|
include?: string[];
|
|
14
14
|
/** File glob patterns to exclude */
|
|
15
15
|
exclude?: string[];
|
|
16
|
+
/** Progress callback */
|
|
17
|
+
onProgress?: (processed: number, total: number, message: string) => void;
|
|
16
18
|
}
|
|
17
19
|
interface TestabilityIssue extends Issue {
|
|
18
20
|
type: 'low-testability';
|
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ interface TestabilityOptions {
|
|
|
13
13
|
include?: string[];
|
|
14
14
|
/** File glob patterns to exclude */
|
|
15
15
|
exclude?: string[];
|
|
16
|
+
/** Progress callback */
|
|
17
|
+
onProgress?: (processed: number, total: number, message: string) => void;
|
|
16
18
|
}
|
|
17
19
|
interface TestabilityIssue extends Issue {
|
|
18
20
|
type: 'low-testability';
|
package/dist/index.js
CHANGED
|
@@ -26,63 +26,10 @@ __export(index_exports, {
|
|
|
26
26
|
module.exports = __toCommonJS(index_exports);
|
|
27
27
|
|
|
28
28
|
// src/analyzer.ts
|
|
29
|
+
var import_core = require("@aiready/core");
|
|
29
30
|
var import_fs = require("fs");
|
|
30
31
|
var import_path = require("path");
|
|
31
32
|
var import_typescript_estree = require("@typescript-eslint/typescript-estree");
|
|
32
|
-
var import_core = require("@aiready/core");
|
|
33
|
-
var SRC_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
|
|
34
|
-
var DEFAULT_EXCLUDES = [
|
|
35
|
-
"node_modules",
|
|
36
|
-
"dist",
|
|
37
|
-
".git",
|
|
38
|
-
"coverage",
|
|
39
|
-
".turbo",
|
|
40
|
-
"build"
|
|
41
|
-
];
|
|
42
|
-
var TEST_PATTERNS = [
|
|
43
|
-
/\.(test|spec)\.(ts|tsx|js|jsx)$/,
|
|
44
|
-
/__tests__\//,
|
|
45
|
-
/\/tests?\//,
|
|
46
|
-
/\/e2e\//,
|
|
47
|
-
/\/fixtures\//
|
|
48
|
-
];
|
|
49
|
-
function isTestFile(filePath, extra) {
|
|
50
|
-
if (TEST_PATTERNS.some((p) => p.test(filePath))) return true;
|
|
51
|
-
if (extra) return extra.some((p) => filePath.includes(p));
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
function isSourceFile(filePath) {
|
|
55
|
-
return SRC_EXTENSIONS.has((0, import_path.extname)(filePath));
|
|
56
|
-
}
|
|
57
|
-
function collectFiles(dir, options, depth = 0) {
|
|
58
|
-
if (depth > (options.maxDepth ?? 20)) return [];
|
|
59
|
-
const excludes = [...DEFAULT_EXCLUDES, ...options.exclude ?? []];
|
|
60
|
-
const files = [];
|
|
61
|
-
let entries;
|
|
62
|
-
try {
|
|
63
|
-
entries = (0, import_fs.readdirSync)(dir);
|
|
64
|
-
} catch {
|
|
65
|
-
return files;
|
|
66
|
-
}
|
|
67
|
-
for (const entry of entries) {
|
|
68
|
-
if (excludes.some((ex) => entry === ex || entry.includes(ex))) continue;
|
|
69
|
-
const full = (0, import_path.join)(dir, entry);
|
|
70
|
-
let stat;
|
|
71
|
-
try {
|
|
72
|
-
stat = (0, import_fs.statSync)(full);
|
|
73
|
-
} catch {
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
if (stat.isDirectory()) {
|
|
77
|
-
files.push(...collectFiles(full, options, depth + 1));
|
|
78
|
-
} else if (stat.isFile() && isSourceFile(full)) {
|
|
79
|
-
if (!options.include || options.include.some((p) => full.includes(p))) {
|
|
80
|
-
files.push(full);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return files;
|
|
85
|
-
}
|
|
86
33
|
function countMethodsInInterface(node) {
|
|
87
34
|
if (node.type === "TSInterfaceDeclaration") {
|
|
88
35
|
return node.body.body.filter(
|
|
@@ -234,8 +181,24 @@ function detectTestFramework(rootDir) {
|
|
|
234
181
|
return false;
|
|
235
182
|
}
|
|
236
183
|
}
|
|
184
|
+
var TEST_PATTERNS = [
|
|
185
|
+
/\.(test|spec)\.(ts|tsx|js|jsx)$/,
|
|
186
|
+
/__tests__\//,
|
|
187
|
+
/\/tests?\//,
|
|
188
|
+
/\/e2e\//,
|
|
189
|
+
/\/fixtures\//
|
|
190
|
+
];
|
|
191
|
+
function isTestFile(filePath, extra) {
|
|
192
|
+
if (TEST_PATTERNS.some((p) => p.test(filePath))) return true;
|
|
193
|
+
if (extra) return extra.some((p) => filePath.includes(p));
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
237
196
|
async function analyzeTestability(options) {
|
|
238
|
-
const allFiles =
|
|
197
|
+
const allFiles = await (0, import_core.scanFiles)({
|
|
198
|
+
...options,
|
|
199
|
+
include: options.include || ["**/*.{ts,tsx,js,jsx}"],
|
|
200
|
+
includeTests: true
|
|
201
|
+
});
|
|
239
202
|
const sourceFiles = allFiles.filter(
|
|
240
203
|
(f) => !isTestFile(f, options.testPatterns)
|
|
241
204
|
);
|
|
@@ -249,7 +212,14 @@ async function analyzeTestability(options) {
|
|
|
249
212
|
totalInterfaces: 0,
|
|
250
213
|
externalStateMutations: 0
|
|
251
214
|
};
|
|
215
|
+
let processed = 0;
|
|
252
216
|
for (const f of sourceFiles) {
|
|
217
|
+
processed++;
|
|
218
|
+
options.onProgress?.(
|
|
219
|
+
processed,
|
|
220
|
+
sourceFiles.length,
|
|
221
|
+
`testability: analyzing files`
|
|
222
|
+
);
|
|
253
223
|
const a = analyzeFileTestability(f);
|
|
254
224
|
for (const key of Object.keys(aggregated)) {
|
|
255
225
|
aggregated[key] += a[key];
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiready/testability",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Measures how safely and verifiably AI-generated changes can be made to your codebase",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"chalk": "^5.3.0",
|
|
41
41
|
"commander": "^14.0.0",
|
|
42
42
|
"glob": "^13.0.0",
|
|
43
|
-
"@aiready/core": "0.9.
|
|
43
|
+
"@aiready/core": "0.9.35"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/node": "^24.0.0",
|
package/src/analyzer.ts
CHANGED
|
@@ -10,82 +10,17 @@
|
|
|
10
10
|
* 5. Observability (return values vs. external state mutations)
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
13
|
+
import { scanFiles, calculateTestabilityIndex } from '@aiready/core';
|
|
14
|
+
import { readFileSync, existsSync } from 'fs';
|
|
15
|
+
import { join } from 'path';
|
|
15
16
|
import { parse } from '@typescript-eslint/typescript-estree';
|
|
16
17
|
import type { TSESTree } from '@typescript-eslint/types';
|
|
17
|
-
import { calculateTestabilityIndex } from '@aiready/core';
|
|
18
18
|
import type {
|
|
19
19
|
TestabilityOptions,
|
|
20
20
|
TestabilityIssue,
|
|
21
21
|
TestabilityReport,
|
|
22
22
|
} from './types';
|
|
23
23
|
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
// File classification
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
|
|
28
|
-
const SRC_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx']);
|
|
29
|
-
const DEFAULT_EXCLUDES = [
|
|
30
|
-
'node_modules',
|
|
31
|
-
'dist',
|
|
32
|
-
'.git',
|
|
33
|
-
'coverage',
|
|
34
|
-
'.turbo',
|
|
35
|
-
'build',
|
|
36
|
-
];
|
|
37
|
-
const TEST_PATTERNS = [
|
|
38
|
-
/\.(test|spec)\.(ts|tsx|js|jsx)$/,
|
|
39
|
-
/__tests__\//,
|
|
40
|
-
/\/tests?\//,
|
|
41
|
-
/\/e2e\//,
|
|
42
|
-
/\/fixtures\//,
|
|
43
|
-
];
|
|
44
|
-
|
|
45
|
-
function isTestFile(filePath: string, extra?: string[]): boolean {
|
|
46
|
-
if (TEST_PATTERNS.some((p) => p.test(filePath))) return true;
|
|
47
|
-
if (extra) return extra.some((p) => filePath.includes(p));
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function isSourceFile(filePath: string): boolean {
|
|
52
|
-
return SRC_EXTENSIONS.has(extname(filePath));
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function collectFiles(
|
|
56
|
-
dir: string,
|
|
57
|
-
options: TestabilityOptions,
|
|
58
|
-
depth = 0
|
|
59
|
-
): string[] {
|
|
60
|
-
if (depth > (options.maxDepth ?? 20)) return [];
|
|
61
|
-
const excludes = [...DEFAULT_EXCLUDES, ...(options.exclude ?? [])];
|
|
62
|
-
const files: string[] = [];
|
|
63
|
-
let entries: string[];
|
|
64
|
-
try {
|
|
65
|
-
entries = readdirSync(dir);
|
|
66
|
-
} catch {
|
|
67
|
-
return files;
|
|
68
|
-
}
|
|
69
|
-
for (const entry of entries) {
|
|
70
|
-
if (excludes.some((ex) => entry === ex || entry.includes(ex))) continue;
|
|
71
|
-
const full = join(dir, entry);
|
|
72
|
-
let stat;
|
|
73
|
-
try {
|
|
74
|
-
stat = statSync(full);
|
|
75
|
-
} catch {
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
if (stat.isDirectory()) {
|
|
79
|
-
files.push(...collectFiles(full, options, depth + 1));
|
|
80
|
-
} else if (stat.isFile() && isSourceFile(full)) {
|
|
81
|
-
if (!options.include || options.include.some((p) => full.includes(p))) {
|
|
82
|
-
files.push(full);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return files;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
24
|
// ---------------------------------------------------------------------------
|
|
90
25
|
// Per-file analysis
|
|
91
26
|
// ---------------------------------------------------------------------------
|
|
@@ -324,10 +259,29 @@ function detectTestFramework(rootDir: string): boolean {
|
|
|
324
259
|
// Main analyzer
|
|
325
260
|
// ---------------------------------------------------------------------------
|
|
326
261
|
|
|
262
|
+
const TEST_PATTERNS = [
|
|
263
|
+
/\.(test|spec)\.(ts|tsx|js|jsx)$/,
|
|
264
|
+
/__tests__\//,
|
|
265
|
+
/\/tests?\//,
|
|
266
|
+
/\/e2e\//,
|
|
267
|
+
/\/fixtures\//,
|
|
268
|
+
];
|
|
269
|
+
|
|
270
|
+
function isTestFile(filePath: string, extra?: string[]): boolean {
|
|
271
|
+
if (TEST_PATTERNS.some((p) => p.test(filePath))) return true;
|
|
272
|
+
if (extra) return extra.some((p) => filePath.includes(p));
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
|
|
327
276
|
export async function analyzeTestability(
|
|
328
277
|
options: TestabilityOptions
|
|
329
278
|
): Promise<TestabilityReport> {
|
|
330
|
-
|
|
279
|
+
// Use core scanFiles which respects .gitignore recursively
|
|
280
|
+
const allFiles = await scanFiles({
|
|
281
|
+
...options,
|
|
282
|
+
include: options.include || ['**/*.{ts,tsx,js,jsx}'],
|
|
283
|
+
includeTests: true,
|
|
284
|
+
});
|
|
331
285
|
|
|
332
286
|
const sourceFiles = allFiles.filter(
|
|
333
287
|
(f) => !isTestFile(f, options.testPatterns)
|
|
@@ -344,7 +298,15 @@ export async function analyzeTestability(
|
|
|
344
298
|
externalStateMutations: 0,
|
|
345
299
|
};
|
|
346
300
|
|
|
301
|
+
let processed = 0;
|
|
347
302
|
for (const f of sourceFiles) {
|
|
303
|
+
processed++;
|
|
304
|
+
options.onProgress?.(
|
|
305
|
+
processed,
|
|
306
|
+
sourceFiles.length,
|
|
307
|
+
`testability: analyzing files`
|
|
308
|
+
);
|
|
309
|
+
|
|
348
310
|
const a = analyzeFileTestability(f);
|
|
349
311
|
for (const key of Object.keys(aggregated) as Array<keyof FileAnalysis>) {
|
|
350
312
|
aggregated[key] += a[key];
|
package/src/scoring.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Issue } from '@aiready/core';
|
|
2
2
|
|
|
3
3
|
export interface TestabilityOptions {
|
|
4
4
|
/** Root directory to scan */
|
|
@@ -13,6 +13,8 @@ export interface TestabilityOptions {
|
|
|
13
13
|
include?: string[];
|
|
14
14
|
/** File glob patterns to exclude */
|
|
15
15
|
exclude?: string[];
|
|
16
|
+
/** Progress callback */
|
|
17
|
+
onProgress?: (processed: number, total: number, message: string) => void;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
export interface TestabilityIssue extends Issue {
|