phantom-pr 0.1.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/LICENSE.md +0 -0
- package/README.md +143 -0
- package/dist/adapters/git.d.ts +28 -0
- package/dist/adapters/git.js +112 -0
- package/dist/adapters/git.js.map +1 -0
- package/dist/adapters/github.d.ts +71 -0
- package/dist/adapters/github.js +194 -0
- package/dist/adapters/github.js.map +1 -0
- package/dist/cli.d.ts +47 -0
- package/dist/cli.js +201 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/context.d.ts +2 -0
- package/dist/commands/context.js +275 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/full.d.ts +13 -0
- package/dist/commands/full.js +590 -0
- package/dist/commands/full.js.map +1 -0
- package/dist/commands/gen_test.d.ts +2 -0
- package/dist/commands/gen_test.js +94 -0
- package/dist/commands/gen_test.js.map +1 -0
- package/dist/commands/index.d.ts +2 -0
- package/dist/commands/index.js +62 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/plan.d.ts +2 -0
- package/dist/commands/plan.js +107 -0
- package/dist/commands/plan.js.map +1 -0
- package/dist/commands/pr.d.ts +9 -0
- package/dist/commands/pr.js +400 -0
- package/dist/commands/pr.js.map +1 -0
- package/dist/commands/pr_list.d.ts +2 -0
- package/dist/commands/pr_list.js +158 -0
- package/dist/commands/pr_list.js.map +1 -0
- package/dist/commands/status.d.ts +6 -0
- package/dist/commands/status.js +132 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/test.d.ts +2 -0
- package/dist/commands/test.js +143 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/commands/testability.d.ts +10 -0
- package/dist/commands/testability.js +406 -0
- package/dist/commands/testability.js.map +1 -0
- package/dist/commands/tests.d.ts +9 -0
- package/dist/commands/tests.js +801 -0
- package/dist/commands/tests.js.map +1 -0
- package/dist/core/code/exports.d.ts +5 -0
- package/dist/core/code/exports.js +68 -0
- package/dist/core/code/exports.js.map +1 -0
- package/dist/core/config/forkPolicy.d.ts +25 -0
- package/dist/core/config/forkPolicy.js +35 -0
- package/dist/core/config/forkPolicy.js.map +1 -0
- package/dist/core/config/load.d.ts +13 -0
- package/dist/core/config/load.js +35 -0
- package/dist/core/config/load.js.map +1 -0
- package/dist/core/config/types.d.ts +87 -0
- package/dist/core/config/types.js +2 -0
- package/dist/core/config/types.js.map +1 -0
- package/dist/core/config/validate.d.ts +4 -0
- package/dist/core/config/validate.js +246 -0
- package/dist/core/config/validate.js.map +1 -0
- package/dist/core/context/packer.d.ts +31 -0
- package/dist/core/context/packer.js +345 -0
- package/dist/core/context/packer.js.map +1 -0
- package/dist/core/context/types.d.ts +41 -0
- package/dist/core/context/types.js +2 -0
- package/dist/core/context/types.js.map +1 -0
- package/dist/core/converge/loop.d.ts +13 -0
- package/dist/core/converge/loop.js +35 -0
- package/dist/core/converge/loop.js.map +1 -0
- package/dist/core/converge/types.d.ts +15 -0
- package/dist/core/converge/types.js +2 -0
- package/dist/core/converge/types.js.map +1 -0
- package/dist/core/generator/llm/diffApply.d.ts +26 -0
- package/dist/core/generator/llm/diffApply.js +276 -0
- package/dist/core/generator/llm/diffApply.js.map +1 -0
- package/dist/core/generator/llm/qualityGate.d.ts +34 -0
- package/dist/core/generator/llm/qualityGate.js +324 -0
- package/dist/core/generator/llm/qualityGate.js.map +1 -0
- package/dist/core/generator/llmGenerator.d.ts +34 -0
- package/dist/core/generator/llmGenerator.js +245 -0
- package/dist/core/generator/llmGenerator.js.map +1 -0
- package/dist/core/generator/quality.d.ts +17 -0
- package/dist/core/generator/quality.js +31 -0
- package/dist/core/generator/quality.js.map +1 -0
- package/dist/core/generator/registry.d.ts +26 -0
- package/dist/core/generator/registry.js +29 -0
- package/dist/core/generator/registry.js.map +1 -0
- package/dist/core/generator/smokeGenerator.d.ts +11 -0
- package/dist/core/generator/smokeGenerator.js +27 -0
- package/dist/core/generator/smokeGenerator.js.map +1 -0
- package/dist/core/generator/types.d.ts +48 -0
- package/dist/core/generator/types.js +2 -0
- package/dist/core/generator/types.js.map +1 -0
- package/dist/core/index/indexer.d.ts +29 -0
- package/dist/core/index/indexer.js +167 -0
- package/dist/core/index/indexer.js.map +1 -0
- package/dist/core/jest/parser.d.ts +17 -0
- package/dist/core/jest/parser.js +90 -0
- package/dist/core/jest/parser.js.map +1 -0
- package/dist/core/llm/provider.d.ts +55 -0
- package/dist/core/llm/provider.js +105 -0
- package/dist/core/llm/provider.js.map +1 -0
- package/dist/core/logger.d.ts +19 -0
- package/dist/core/logger.js +44 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/plan/planner.d.ts +16 -0
- package/dist/core/plan/planner.js +91 -0
- package/dist/core/plan/planner.js.map +1 -0
- package/dist/core/process/exec.d.ts +22 -0
- package/dist/core/process/exec.js +83 -0
- package/dist/core/process/exec.js.map +1 -0
- package/dist/core/redact.d.ts +6 -0
- package/dist/core/redact.js +49 -0
- package/dist/core/redact.js.map +1 -0
- package/dist/core/repo/boundary.d.ts +10 -0
- package/dist/core/repo/boundary.js +58 -0
- package/dist/core/repo/boundary.js.map +1 -0
- package/dist/core/stableJson.d.ts +1 -0
- package/dist/core/stableJson.js +32 -0
- package/dist/core/stableJson.js.map +1 -0
- package/dist/core/state/policy.d.ts +20 -0
- package/dist/core/state/policy.js +51 -0
- package/dist/core/state/policy.js.map +1 -0
- package/dist/core/state/state.d.ts +67 -0
- package/dist/core/state/state.js +142 -0
- package/dist/core/state/state.js.map +1 -0
- package/dist/core/state/storage.d.ts +9 -0
- package/dist/core/state/storage.js +25 -0
- package/dist/core/state/storage.js.map +1 -0
- package/dist/core/targets/resolve.d.ts +28 -0
- package/dist/core/targets/resolve.js +96 -0
- package/dist/core/targets/resolve.js.map +1 -0
- package/dist/core/testGenerator/conventions.d.ts +7 -0
- package/dist/core/testGenerator/conventions.js +29 -0
- package/dist/core/testGenerator/conventions.js.map +1 -0
- package/dist/core/testGenerator/generate.d.ts +35 -0
- package/dist/core/testGenerator/generate.js +127 -0
- package/dist/core/testGenerator/generate.js.map +1 -0
- package/dist/core/testRunner/hints.d.ts +12 -0
- package/dist/core/testRunner/hints.js +133 -0
- package/dist/core/testRunner/hints.js.map +1 -0
- package/dist/core/testRunner/infer.d.ts +24 -0
- package/dist/core/testRunner/infer.js +65 -0
- package/dist/core/testRunner/infer.js.map +1 -0
- package/dist/core/testRunner/resolve.d.ts +12 -0
- package/dist/core/testRunner/resolve.js +31 -0
- package/dist/core/testRunner/resolve.js.map +1 -0
- package/dist/core/testRunner/runner.d.ts +24 -0
- package/dist/core/testRunner/runner.js +145 -0
- package/dist/core/testRunner/runner.js.map +1 -0
- package/dist/core/testability/heuristics.d.ts +7 -0
- package/dist/core/testability/heuristics.js +35 -0
- package/dist/core/testability/heuristics.js.map +1 -0
- package/dist/core/tests/fixers.d.ts +14 -0
- package/dist/core/tests/fixers.js +59 -0
- package/dist/core/tests/fixers.js.map +1 -0
- package/dist/core/tests/types.d.ts +98 -0
- package/dist/core/tests/types.js +2 -0
- package/dist/core/tests/types.js.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type PlanTargetLike = {
|
|
2
|
+
id: string;
|
|
3
|
+
paths: string[];
|
|
4
|
+
reasons?: string[];
|
|
5
|
+
estimatedRisk?: string;
|
|
6
|
+
};
|
|
7
|
+
export type PlanLike = {
|
|
8
|
+
schemaVersion?: number;
|
|
9
|
+
targets?: PlanTargetLike[];
|
|
10
|
+
};
|
|
11
|
+
export type TargetSuggestion = {
|
|
12
|
+
id: string;
|
|
13
|
+
rootPath: string;
|
|
14
|
+
paths: string[];
|
|
15
|
+
};
|
|
16
|
+
export type ResolveTargetResult = {
|
|
17
|
+
found: true;
|
|
18
|
+
target: PlanTargetLike;
|
|
19
|
+
} | {
|
|
20
|
+
found: false;
|
|
21
|
+
suggestions: TargetSuggestion[];
|
|
22
|
+
};
|
|
23
|
+
export declare function resolveTargetIdOrPath(opts: {
|
|
24
|
+
root: string;
|
|
25
|
+
plan: PlanLike;
|
|
26
|
+
targetInput: string;
|
|
27
|
+
maxSuggestions?: number;
|
|
28
|
+
}): ResolveTargetResult;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
function toPosix(p) {
|
|
4
|
+
return p.split(path.sep).join('/');
|
|
5
|
+
}
|
|
6
|
+
function normalizeRel(p) {
|
|
7
|
+
let x = toPosix(p).trim();
|
|
8
|
+
if (x.startsWith('./'))
|
|
9
|
+
x = x.slice(2);
|
|
10
|
+
x = path.posix.normalize(x);
|
|
11
|
+
if (x === '.')
|
|
12
|
+
x = '';
|
|
13
|
+
return x;
|
|
14
|
+
}
|
|
15
|
+
function commonPrefixSegments(a, b) {
|
|
16
|
+
const as = normalizeRel(a).split('/').filter(Boolean);
|
|
17
|
+
const bs = normalizeRel(b).split('/').filter(Boolean);
|
|
18
|
+
const n = Math.min(as.length, bs.length);
|
|
19
|
+
let i = 0;
|
|
20
|
+
for (; i < n; i++) {
|
|
21
|
+
if (as[i] !== bs[i])
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
return i;
|
|
25
|
+
}
|
|
26
|
+
function rootPathForTarget(t) {
|
|
27
|
+
const first = t.paths[0] ?? '';
|
|
28
|
+
const dir = path.posix.dirname(first);
|
|
29
|
+
return dir === '.' ? '' : dir;
|
|
30
|
+
}
|
|
31
|
+
function scoreTargetByPath(inputPath, t) {
|
|
32
|
+
let best = 0;
|
|
33
|
+
for (const p of t.paths) {
|
|
34
|
+
best = Math.max(best, commonPrefixSegments(inputPath, p));
|
|
35
|
+
}
|
|
36
|
+
// Also consider rootPath prefixing.
|
|
37
|
+
best = Math.max(best, commonPrefixSegments(inputPath, rootPathForTarget(t)));
|
|
38
|
+
return best;
|
|
39
|
+
}
|
|
40
|
+
function toSuggestion(t) {
|
|
41
|
+
const paths = [...new Set(t.paths)].sort((a, b) => a.localeCompare(b));
|
|
42
|
+
return { id: t.id, rootPath: rootPathForTarget(t), paths };
|
|
43
|
+
}
|
|
44
|
+
export function resolveTargetIdOrPath(opts) {
|
|
45
|
+
const maxSuggestions = Math.max(1, Math.min(5, opts.maxSuggestions ?? 5));
|
|
46
|
+
const targets = Array.isArray(opts.plan.targets) ? opts.plan.targets : [];
|
|
47
|
+
const input = opts.targetInput.trim();
|
|
48
|
+
const inputNorm = normalizeRel(input);
|
|
49
|
+
// 1) Exact id match.
|
|
50
|
+
for (const t of targets) {
|
|
51
|
+
if (t && typeof t.id === 'string' && t.id === input)
|
|
52
|
+
return { found: true, target: t };
|
|
53
|
+
}
|
|
54
|
+
// Determine whether input is an existing path in the repo.
|
|
55
|
+
const abs = path.resolve(opts.root, input);
|
|
56
|
+
const pathExists = inputNorm !== '' && fs.existsSync(abs);
|
|
57
|
+
if (pathExists) {
|
|
58
|
+
// 2) Existing path: prefer target whose rootPath is the longest prefix of provided path.
|
|
59
|
+
const candidates = [];
|
|
60
|
+
for (const t of targets) {
|
|
61
|
+
if (!t || typeof t.id !== 'string')
|
|
62
|
+
continue;
|
|
63
|
+
const root = rootPathForTarget(t);
|
|
64
|
+
const rootNorm = normalizeRel(root);
|
|
65
|
+
if (!rootNorm)
|
|
66
|
+
continue;
|
|
67
|
+
const prefix = rootNorm.endsWith('/') ? rootNorm : `${rootNorm}/`;
|
|
68
|
+
if (inputNorm === rootNorm || inputNorm.startsWith(prefix))
|
|
69
|
+
candidates.push({ t, root: rootNorm });
|
|
70
|
+
}
|
|
71
|
+
candidates.sort((a, b) => {
|
|
72
|
+
if (b.root.length !== a.root.length)
|
|
73
|
+
return b.root.length - a.root.length;
|
|
74
|
+
return a.t.id.localeCompare(b.t.id);
|
|
75
|
+
});
|
|
76
|
+
const first = candidates[0];
|
|
77
|
+
if (first)
|
|
78
|
+
return { found: true, target: first.t };
|
|
79
|
+
}
|
|
80
|
+
// 3) Closest match by prefix segments across target paths (deterministic).
|
|
81
|
+
const scored = targets
|
|
82
|
+
.filter((t) => !!t && typeof t.id === 'string' && Array.isArray(t.paths))
|
|
83
|
+
.map((t) => ({ t, score: scoreTargetByPath(inputNorm, t) }))
|
|
84
|
+
.sort((a, b) => {
|
|
85
|
+
if (b.score !== a.score)
|
|
86
|
+
return b.score - a.score;
|
|
87
|
+
return a.t.id.localeCompare(b.t.id);
|
|
88
|
+
});
|
|
89
|
+
const best = scored[0];
|
|
90
|
+
if (best && best.score > 0)
|
|
91
|
+
return { found: true, target: best.t };
|
|
92
|
+
// Suggestions: top N by score, then id. If all scores are 0, just first N by id.
|
|
93
|
+
const suggestions = scored.slice(0, maxSuggestions).map((s) => toSuggestion(s.t));
|
|
94
|
+
return { found: false, suggestions };
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../../src/core/targets/resolve.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type TestConvention = 'colocated_test_ts' | 'dir_tests';
|
|
2
|
+
export declare function detectTestConvention(indexPaths: readonly string[]): TestConvention;
|
|
3
|
+
export declare function computeTestPath(opts: {
|
|
4
|
+
convention: TestConvention;
|
|
5
|
+
targetPath: string;
|
|
6
|
+
testExt: 'ts' | 'js';
|
|
7
|
+
}): string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
function hasAny(paths, pred) {
|
|
2
|
+
for (const p of paths)
|
|
3
|
+
if (pred(p))
|
|
4
|
+
return true;
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
export function detectTestConvention(indexPaths) {
|
|
8
|
+
// Prefer an existing convention in the repo; otherwise default to colocated for simplicity.
|
|
9
|
+
const hasDirTests = hasAny(indexPaths, (p) => p.includes('/__tests__/') && (p.endsWith('.test.ts') || p.endsWith('.test.js')));
|
|
10
|
+
if (hasDirTests)
|
|
11
|
+
return 'dir_tests';
|
|
12
|
+
const hasColocated = hasAny(indexPaths, (p) => p.startsWith('src/') && (p.endsWith('.test.ts') || p.endsWith('.test.js')));
|
|
13
|
+
if (hasColocated)
|
|
14
|
+
return 'colocated_test_ts';
|
|
15
|
+
return 'colocated_test_ts';
|
|
16
|
+
}
|
|
17
|
+
export function computeTestPath(opts) {
|
|
18
|
+
const target = opts.targetPath;
|
|
19
|
+
const dot = target.lastIndexOf('.');
|
|
20
|
+
const base = dot === -1 ? target : target.slice(0, dot);
|
|
21
|
+
const fileName = base.split('/').pop() ?? base;
|
|
22
|
+
const dir = base.includes('/') ? base.split('/').slice(0, -1).join('/') : '';
|
|
23
|
+
if (opts.convention === 'dir_tests') {
|
|
24
|
+
const testsDir = dir ? `${dir}/__tests__` : '__tests__';
|
|
25
|
+
return `${testsDir}/${fileName}.test.${opts.testExt}`;
|
|
26
|
+
}
|
|
27
|
+
return `${base}.test.${opts.testExt}`;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=conventions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conventions.js","sourceRoot":"","sources":["../../../src/core/testGenerator/conventions.ts"],"names":[],"mappings":"AAEA,SAAS,MAAM,CAAC,KAAwB,EAAE,IAA4B;IACpE,KAAK,MAAM,CAAC,IAAI,KAAK;QAAE,IAAI,IAAI,CAAC,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;IAChD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,UAA6B;IAChE,4FAA4F;IAC5F,MAAM,WAAW,GAAG,MAAM,CACxB,UAAU,EACV,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CACvF,CAAC;IACF,IAAI,WAAW;QAAE,OAAO,WAAW,CAAC;IAEpC,MAAM,YAAY,GAAG,MAAM,CACzB,UAAU,EACV,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAClF,CAAC;IACF,IAAI,YAAY;QAAE,OAAO,mBAAmB,CAAC;IAE7C,OAAO,mBAAmB,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAI/B;IACC,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC;IAC/B,MAAM,GAAG,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACxD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC;IAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAE7E,IAAI,IAAI,CAAC,UAAU,KAAK,WAAW,EAAE,CAAC;QACpC,MAAM,QAAQ,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,YAAY,CAAC,CAAC,CAAC,WAAW,CAAC;QACxD,OAAO,GAAG,QAAQ,IAAI,QAAQ,SAAS,IAAI,CAAC,OAAO,EAAE,CAAC;IACxD,CAAC;IAED,OAAO,GAAG,IAAI,SAAS,IAAI,CAAC,OAAO,EAAE,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type GenerateTestResult = {
|
|
2
|
+
created: true;
|
|
3
|
+
testPath: string;
|
|
4
|
+
reason: string;
|
|
5
|
+
generatedFiles: string[];
|
|
6
|
+
targetId: string;
|
|
7
|
+
targetPath: string;
|
|
8
|
+
assumptionsUsed: string[];
|
|
9
|
+
warnings: string[];
|
|
10
|
+
} | {
|
|
11
|
+
created: false;
|
|
12
|
+
testPath: string | null;
|
|
13
|
+
reason: string;
|
|
14
|
+
generatedFiles: string[];
|
|
15
|
+
targetId: string;
|
|
16
|
+
targetPath: string;
|
|
17
|
+
assumptionsUsed: string[];
|
|
18
|
+
warnings: string[];
|
|
19
|
+
};
|
|
20
|
+
type IndexFile = {
|
|
21
|
+
path: string;
|
|
22
|
+
};
|
|
23
|
+
export declare function previewGeneratedTestFiles(opts: {
|
|
24
|
+
targetPath: string;
|
|
25
|
+
indexFiles: IndexFile[];
|
|
26
|
+
}): {
|
|
27
|
+
testPath: string;
|
|
28
|
+
wouldCreate: boolean;
|
|
29
|
+
};
|
|
30
|
+
export declare function generateJestTest(opts: {
|
|
31
|
+
root: string;
|
|
32
|
+
targetPath: string;
|
|
33
|
+
indexFiles: IndexFile[];
|
|
34
|
+
}): GenerateTestResult;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import { computeTestPath, detectTestConvention } from './conventions.js';
|
|
5
|
+
import { extractNamedExportsFromJsLike } from '../code/exports.js';
|
|
6
|
+
function toPosix(p) {
|
|
7
|
+
return p.split(path.sep).join('/');
|
|
8
|
+
}
|
|
9
|
+
function isJsLike(targetPosix) {
|
|
10
|
+
const ext = path.posix.extname(targetPosix).toLowerCase();
|
|
11
|
+
return ext === '.js' || ext === '.jsx' || ext === '.cjs' || ext === '.mjs';
|
|
12
|
+
}
|
|
13
|
+
export function previewGeneratedTestFiles(opts) {
|
|
14
|
+
const targetPosix = toPosix(opts.targetPath);
|
|
15
|
+
const indexPaths = opts.indexFiles.map((f) => f.path);
|
|
16
|
+
const convention = detectTestConvention(indexPaths);
|
|
17
|
+
const testExt = isJsLike(targetPosix) ? 'js' : 'ts';
|
|
18
|
+
const testPath = computeTestPath({ convention, targetPath: targetPosix, testExt });
|
|
19
|
+
const wouldCreate = !indexPaths.includes(testPath);
|
|
20
|
+
return { testPath, wouldCreate };
|
|
21
|
+
}
|
|
22
|
+
function uniqSorted(xs) {
|
|
23
|
+
return [...new Set(xs)].sort((a, b) => a.localeCompare(b));
|
|
24
|
+
}
|
|
25
|
+
function stableIdForTargetPath(targetPosix) {
|
|
26
|
+
const h = crypto.createHash('sha1').update(targetPosix).digest('hex').slice(0, 8);
|
|
27
|
+
return `tg_${h}`;
|
|
28
|
+
}
|
|
29
|
+
function detectModuleSystem(rootAbs, targetPosix) {
|
|
30
|
+
const ext = path.posix.extname(targetPosix).toLowerCase();
|
|
31
|
+
if (ext === '.cjs')
|
|
32
|
+
return 'cjs';
|
|
33
|
+
if (ext === '.mjs')
|
|
34
|
+
return 'esm';
|
|
35
|
+
try {
|
|
36
|
+
const pkgPath = path.join(rootAbs, 'package.json');
|
|
37
|
+
if (!fs.existsSync(pkgPath))
|
|
38
|
+
return 'cjs';
|
|
39
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
40
|
+
return pkg.type === 'module' ? 'esm' : 'cjs';
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return 'cjs';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function renderTest(opts) {
|
|
47
|
+
const lines = [];
|
|
48
|
+
lines.push('// Generated by test-agent. Safe smoke test; edit as needed.');
|
|
49
|
+
lines.push('');
|
|
50
|
+
lines.push(`describe('${opts.targetPath}', () => {`);
|
|
51
|
+
if (opts.moduleSystem === 'cjs') {
|
|
52
|
+
lines.push(` it('loads and has expected exports', () => {`);
|
|
53
|
+
lines.push(` // eslint-disable-next-line @typescript-eslint/no-var-requires`);
|
|
54
|
+
lines.push(` const mod = require('${opts.importPath}');`);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
lines.push(` it('loads and has expected exports', async () => {`);
|
|
58
|
+
lines.push(` const mod = await import('${opts.importPath}');`);
|
|
59
|
+
}
|
|
60
|
+
lines.push(` expect(mod).toBeDefined();`);
|
|
61
|
+
if (opts.moduleSystem === 'esm' && opts.exports.hasDefault)
|
|
62
|
+
lines.push(` expect(mod.default).toBeDefined();`);
|
|
63
|
+
for (const name of opts.exports.named)
|
|
64
|
+
lines.push(` expect(mod.${name}).toBeDefined();`);
|
|
65
|
+
lines.push(' });');
|
|
66
|
+
lines.push('});');
|
|
67
|
+
lines.push('');
|
|
68
|
+
return lines.join('\n');
|
|
69
|
+
}
|
|
70
|
+
function relativeImport(fromTestPath, toTargetPath) {
|
|
71
|
+
const fromDir = path.posix.dirname(fromTestPath);
|
|
72
|
+
const rel = path.posix.relative(fromDir, toTargetPath);
|
|
73
|
+
const noExt = rel.replace(/\.[^.]+$/, '');
|
|
74
|
+
return noExt.startsWith('.') ? noExt : `./${noExt}`;
|
|
75
|
+
}
|
|
76
|
+
export function generateJestTest(opts) {
|
|
77
|
+
const rootAbs = path.resolve(opts.root);
|
|
78
|
+
const targetPosix = toPosix(opts.targetPath);
|
|
79
|
+
const targetId = stableIdForTargetPath(targetPosix);
|
|
80
|
+
const warnings = [];
|
|
81
|
+
const assumptionsUsed = [];
|
|
82
|
+
const indexPaths = opts.indexFiles.map((f) => f.path);
|
|
83
|
+
const convention = detectTestConvention(indexPaths);
|
|
84
|
+
assumptionsUsed.push(`testConvention=${convention}`);
|
|
85
|
+
const targetExt = path.posix.extname(targetPosix).toLowerCase();
|
|
86
|
+
const testExt = isJsLike(targetPosix) ? 'js' : 'ts';
|
|
87
|
+
assumptionsUsed.push(`testExt=${testExt}`);
|
|
88
|
+
const testPath = computeTestPath({ convention, targetPath: targetPosix, testExt });
|
|
89
|
+
// Idempotency: if any likely test file already exists, do not create a new one.
|
|
90
|
+
const likelyExisting = indexPaths.includes(testPath);
|
|
91
|
+
const testAbs = path.join(rootAbs, ...testPath.split('/'));
|
|
92
|
+
if (likelyExisting || fs.existsSync(testAbs)) {
|
|
93
|
+
return {
|
|
94
|
+
created: false,
|
|
95
|
+
testPath,
|
|
96
|
+
reason: 'Test file already exists; skipping',
|
|
97
|
+
generatedFiles: [],
|
|
98
|
+
targetId,
|
|
99
|
+
targetPath: targetPosix,
|
|
100
|
+
assumptionsUsed,
|
|
101
|
+
warnings,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const targetAbs = path.join(rootAbs, ...targetPosix.split('/'));
|
|
105
|
+
const source = fs.readFileSync(targetAbs, 'utf8');
|
|
106
|
+
const exp = extractNamedExportsFromJsLike(source);
|
|
107
|
+
if (exp.named.length === 0 && !exp.hasDefault)
|
|
108
|
+
warnings.push('No obvious exports found; generating load-only test');
|
|
109
|
+
const moduleSystem = detectModuleSystem(rootAbs, targetPosix);
|
|
110
|
+
assumptionsUsed.push(`moduleSystem=${moduleSystem}`);
|
|
111
|
+
assumptionsUsed.push(moduleSystem === 'cjs' ? 'importStyle=require' : 'importStyle=dynamic_import');
|
|
112
|
+
const importPath = relativeImport(testPath, targetPosix);
|
|
113
|
+
const content = renderTest({ targetPath: targetPosix, importPath, exports: exp, moduleSystem });
|
|
114
|
+
fs.mkdirSync(path.dirname(testAbs), { recursive: true });
|
|
115
|
+
fs.writeFileSync(testAbs, content, 'utf8');
|
|
116
|
+
return {
|
|
117
|
+
created: true,
|
|
118
|
+
testPath,
|
|
119
|
+
reason: 'Generated smoke test',
|
|
120
|
+
generatedFiles: [testPath],
|
|
121
|
+
targetId,
|
|
122
|
+
targetPath: targetPosix,
|
|
123
|
+
assumptionsUsed,
|
|
124
|
+
warnings,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=generate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../../../src/core/testGenerator/generate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACzE,OAAO,EAAE,6BAA6B,EAAE,MAAM,oBAAoB,CAAC;AA0BnE,SAAS,OAAO,CAAC,CAAS;IACxB,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACrC,CAAC;AAED,SAAS,QAAQ,CAAC,WAAmB;IACnC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,WAAW,EAAE,CAAC;IAC1D,OAAO,GAAG,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,MAAM,CAAC;AAC7E,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,IAGzC;IACC,MAAM,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC7C,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACtD,MAAM,UAAU,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC;IACpD,MAAM,OAAO,GAAgB,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACjE,MAAM,QAAQ,GAAG,eAAe,CAAC,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC;IACnF,MAAM,WAAW,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACnD,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;AACnC,CAAC;AAED,SAAS,UAAU,CAAC,EAAY;IAC9B,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED,SAAS,qBAAqB,CAAC,WAAmB;IAChD,MAAM,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAClF,OAAO,MAAM,CAAC,EAAE,CAAC;AACnB,CAAC;AAED,SAAS,kBAAkB,CAAC,OAAe,EAAE,WAAmB;IAC9D,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,WAAW,EAAE,CAAC;IAC1D,IAAI,GAAG,KAAK,MAAM;QAAE,OAAO,KAAK,CAAC;IACjC,IAAI,GAAG,KAAK,MAAM;QAAE,OAAO,KAAK,CAAC;IACjC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;QACnD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,OAAO,KAAK,CAAC;QAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAsB,CAAC;QAC9E,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,IAKnB;IACC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,8DAA8D,CAAC,CAAC;IAC3E,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,aAAa,IAAI,CAAC,UAAU,YAAY,CAAC,CAAC;IACrD,IAAI,IAAI,CAAC,YAAY,KAAK,KAAK,EAAE,CAAC;QAChC,KAAK,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;QAC7D,KAAK,CAAC,IAAI,CAAC,oEAAoE,CAAC,CAAC;QACjF,KAAK,CAAC,IAAI,CAAC,4BAA4B,IAAI,CAAC,UAAU,KAAK,CAAC,CAAC;IAC/D,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CAAC,sDAAsD,CAAC,CAAC;QACnE,KAAK,CAAC,IAAI,CAAC,iCAAiC,IAAI,CAAC,UAAU,KAAK,CAAC,CAAC;IACpE,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;IAC7C,IAAI,IAAI,CAAC,YAAY,KAAK,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,UAAU;QAAE,KAAK,CAAC,IAAI,CAAC,wCAAwC,CAAC,CAAC;IACjH,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK;QAAE,KAAK,CAAC,IAAI,CAAC,kBAAkB,IAAI,kBAAkB,CAAC,CAAC;IAC5F,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,cAAc,CAAC,YAAoB,EAAE,YAAoB;IAChE,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IACjD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IACvD,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAC1C,OAAO,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,IAIhC;IACC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC7C,MAAM,QAAQ,GAAG,qBAAqB,CAAC,WAAW,CAAC,CAAC;IACpD,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,eAAe,GAAa,EAAE,CAAC;IAErC,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACtD,MAAM,UAAU,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC;IACpD,eAAe,CAAC,IAAI,CAAC,kBAAkB,UAAU,EAAE,CAAC,CAAC;IAErD,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,WAAW,EAAE,CAAC;IAChE,MAAM,OAAO,GAAgB,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACjE,eAAe,CAAC,IAAI,CAAC,WAAW,OAAO,EAAE,CAAC,CAAC;IAE3C,MAAM,QAAQ,GAAG,eAAe,CAAC,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC;IAEnF,gFAAgF;IAChF,MAAM,cAAc,GAAG,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACrD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3D,IAAI,cAAc,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7C,OAAO;YACL,OAAO,EAAE,KAAK;YACd,QAAQ;YACR,MAAM,EAAE,oCAAoC;YAC5C,cAAc,EAAE,EAAE;YAClB,QAAQ;YACR,UAAU,EAAE,WAAW;YACvB,eAAe;YACf,QAAQ;SACT,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IAChE,MAAM,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAClD,MAAM,GAAG,GAAG,6BAA6B,CAAC,MAAM,CAAC,CAAC;IAClD,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU;QAAE,QAAQ,CAAC,IAAI,CAAC,qDAAqD,CAAC,CAAC;IACpH,MAAM,YAAY,GAAG,kBAAkB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAC9D,eAAe,CAAC,IAAI,CAAC,gBAAgB,YAAY,EAAE,CAAC,CAAC;IACrD,eAAe,CAAC,IAAI,CAAC,YAAY,KAAK,KAAK,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,4BAA4B,CAAC,CAAC;IAEpG,MAAM,UAAU,GAAG,cAAc,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IACzD,MAAM,OAAO,GAAG,UAAU,CAAC,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,CAAC,CAAC;IAEhG,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzD,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAE3C,OAAO;QACL,OAAO,EAAE,IAAI;QACb,QAAQ;QACR,MAAM,EAAE,sBAAsB;QAC9B,cAAc,EAAE,CAAC,QAAQ,CAAC;QAC1B,QAAQ;QACR,UAAU,EAAE,WAAW;QACvB,eAAe;QACf,QAAQ;KACT,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type RunnerKind = 'jest' | 'vitest' | 'pytest' | 'unknown';
|
|
2
|
+
export type RunnerHints = {
|
|
3
|
+
failingFiles: string[];
|
|
4
|
+
failingTests: string[];
|
|
5
|
+
};
|
|
6
|
+
export declare function extractRunnerHints(opts: {
|
|
7
|
+
command: string;
|
|
8
|
+
rawOutput: string;
|
|
9
|
+
}): {
|
|
10
|
+
runner: RunnerKind;
|
|
11
|
+
hints: RunnerHints;
|
|
12
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
function uniqSorted(values) {
|
|
2
|
+
const out = [...new Set(values.map((s) => s.trim()).filter((s) => s !== ''))];
|
|
3
|
+
out.sort((a, b) => a.localeCompare(b));
|
|
4
|
+
return out;
|
|
5
|
+
}
|
|
6
|
+
function detectRunner(command, output) {
|
|
7
|
+
const c = command.toLowerCase();
|
|
8
|
+
const o = output.toLowerCase();
|
|
9
|
+
if (c.includes('pytest') || o.includes('pytest') || o.includes('collected ') || o.includes('== fail'))
|
|
10
|
+
return 'pytest';
|
|
11
|
+
// Vitest: detect by command, explicit banner, or common UI glyphs in Vitest output.
|
|
12
|
+
// Keep this best-effort (false positives are acceptable; hints remain best-effort).
|
|
13
|
+
if (c.includes('vitest') || o.includes('vitest'))
|
|
14
|
+
return 'vitest';
|
|
15
|
+
if (output.includes('✓') || output.includes('×') || output.includes('❯')) {
|
|
16
|
+
// Heuristic: Vitest uses these glyphs heavily; combined with FAIL lines it’s a good signal.
|
|
17
|
+
if (o.includes('fail'))
|
|
18
|
+
return 'vitest';
|
|
19
|
+
}
|
|
20
|
+
if (c.includes('jest') || o.includes('jest') || o.includes('test suites:'))
|
|
21
|
+
return 'jest';
|
|
22
|
+
return 'unknown';
|
|
23
|
+
}
|
|
24
|
+
function extractFileLikePathsFromFailLines(output) {
|
|
25
|
+
// Best-effort: collect file-like paths pointing to tests, but only from failing lines.
|
|
26
|
+
// This avoids polluting hints with passing test files (common in Vitest output).
|
|
27
|
+
const out = [];
|
|
28
|
+
const re = /([A-Za-z]:)?[^\s'"]*(?:__tests__|\.test\.)[^\s'"]*/g;
|
|
29
|
+
for (const line of output.split('\n')) {
|
|
30
|
+
const l = line.trim();
|
|
31
|
+
if (!l.startsWith('FAIL '))
|
|
32
|
+
continue;
|
|
33
|
+
let m;
|
|
34
|
+
while ((m = re.exec(l))) {
|
|
35
|
+
const v = m[0] ?? '';
|
|
36
|
+
if (v.includes('__tests__') || v.includes('.test.'))
|
|
37
|
+
out.push(v);
|
|
38
|
+
if (out.length >= 200)
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
if (out.length >= 200)
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
function extractJestVitestHints(output) {
|
|
47
|
+
const failingFiles = [];
|
|
48
|
+
const failingTests = [];
|
|
49
|
+
for (const line of output.split('\n')) {
|
|
50
|
+
const l = line.trim();
|
|
51
|
+
// Jest and Vitest both commonly print: "FAIL path/to/file.test.ts"
|
|
52
|
+
if (l.startsWith('FAIL ')) {
|
|
53
|
+
const rest = l.slice('FAIL '.length).trim();
|
|
54
|
+
if (rest)
|
|
55
|
+
failingFiles.push(rest.split(/\s+/)[0] ?? rest);
|
|
56
|
+
}
|
|
57
|
+
// Jest: "● suite › test"
|
|
58
|
+
if (l.startsWith('● ')) {
|
|
59
|
+
const rest = l.slice(2).trim();
|
|
60
|
+
if (rest)
|
|
61
|
+
failingTests.push(rest);
|
|
62
|
+
}
|
|
63
|
+
// Vitest often: "FAIL file.test.ts > suite > test"
|
|
64
|
+
if (l.startsWith('FAIL ') && l.includes('>')) {
|
|
65
|
+
const parts = l.split('>').map((x) => x.trim()).filter(Boolean);
|
|
66
|
+
if (parts.length >= 2)
|
|
67
|
+
failingTests.push(parts.slice(1).join(' > '));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
failingFiles.push(...extractFileLikePathsFromFailLines(output));
|
|
71
|
+
return { failingFiles: uniqSorted(failingFiles), failingTests: uniqSorted(failingTests) };
|
|
72
|
+
}
|
|
73
|
+
function extractPytestHints(output) {
|
|
74
|
+
const failingFiles = [];
|
|
75
|
+
const failingTests = [];
|
|
76
|
+
for (const line of output.split('\n')) {
|
|
77
|
+
const l = line.trim();
|
|
78
|
+
// Pytest: "FAILED path/to/test_file.py::TestClass::test_x - ..."
|
|
79
|
+
if (l.startsWith('FAILED ')) {
|
|
80
|
+
const rest = l.slice('FAILED '.length).trim();
|
|
81
|
+
const nodeid = rest.split(/\s+-\s+|\s+/)[0] ?? rest;
|
|
82
|
+
if (nodeid)
|
|
83
|
+
failingTests.push(nodeid);
|
|
84
|
+
const file = nodeid.split('::')[0];
|
|
85
|
+
if (file && file.endsWith('.py'))
|
|
86
|
+
failingFiles.push(file);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Also accept nodeids anywhere in the output.
|
|
90
|
+
const nodeidRe = /([A-Za-z]:)?[^\s'"]+\.py(?:::[^\s'"]+)+/g;
|
|
91
|
+
let m;
|
|
92
|
+
while ((m = nodeidRe.exec(output))) {
|
|
93
|
+
const nodeid = m[0] ?? '';
|
|
94
|
+
failingTests.push(nodeid);
|
|
95
|
+
const file = nodeid.split('::')[0];
|
|
96
|
+
if (file && file.endsWith('.py'))
|
|
97
|
+
failingFiles.push(file);
|
|
98
|
+
if (failingTests.length >= 200)
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
// Traceback-ish file references like "path/to/test_x.py:12: AssertionError"
|
|
102
|
+
const fileLineRe = /([A-Za-z]:)?[^\s'"]+\.py:\d+:/g;
|
|
103
|
+
while ((m = fileLineRe.exec(output))) {
|
|
104
|
+
const hit = m[0] ?? '';
|
|
105
|
+
const file = hit.split(':')[0] ?? '';
|
|
106
|
+
if (file && file.endsWith('.py'))
|
|
107
|
+
failingFiles.push(file);
|
|
108
|
+
if (failingFiles.length >= 200)
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
return { failingFiles: uniqSorted(failingFiles), failingTests: uniqSorted(failingTests) };
|
|
112
|
+
}
|
|
113
|
+
export function extractRunnerHints(opts) {
|
|
114
|
+
const runner = detectRunner(opts.command, opts.rawOutput);
|
|
115
|
+
try {
|
|
116
|
+
if (runner === 'pytest')
|
|
117
|
+
return { runner, hints: extractPytestHints(opts.rawOutput) };
|
|
118
|
+
if (runner === 'jest' || runner === 'vitest')
|
|
119
|
+
return { runner, hints: extractJestVitestHints(opts.rawOutput) };
|
|
120
|
+
// unknown: try both extractors but keep runner=unknown.
|
|
121
|
+
const a = extractJestVitestHints(opts.rawOutput);
|
|
122
|
+
const b = extractPytestHints(opts.rawOutput);
|
|
123
|
+
const hints = {
|
|
124
|
+
failingFiles: uniqSorted([...a.failingFiles, ...b.failingFiles]),
|
|
125
|
+
failingTests: uniqSorted([...a.failingTests, ...b.failingTests]),
|
|
126
|
+
};
|
|
127
|
+
return { runner: 'unknown', hints };
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return { runner, hints: { failingFiles: [], failingTests: [] } };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=hints.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hints.js","sourceRoot":"","sources":["../../../src/core/testRunner/hints.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type InferredRunner = 'jest' | 'vitest' | 'pytest' | 'unknown';
|
|
2
|
+
export type InferredSource = 'config' | 'policy' | 'package_json' | 'inferred' | 'none';
|
|
3
|
+
export type InferInput = {
|
|
4
|
+
rootAbs: string;
|
|
5
|
+
packageJson: {
|
|
6
|
+
scripts?: Record<string, string>;
|
|
7
|
+
} | null;
|
|
8
|
+
pythonBoundary: {
|
|
9
|
+
boundaryFileRel: string | null;
|
|
10
|
+
} | null;
|
|
11
|
+
config: {
|
|
12
|
+
testCommand: string | null;
|
|
13
|
+
};
|
|
14
|
+
policy: {
|
|
15
|
+
workingTestCommand: string | null;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export type InferResult = {
|
|
19
|
+
runner: InferredRunner;
|
|
20
|
+
command: string | null;
|
|
21
|
+
source: InferredSource;
|
|
22
|
+
reason: string;
|
|
23
|
+
};
|
|
24
|
+
export declare function inferTestCommandAndRunner(input: InferInput): InferResult;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
function runnerFromCommand(command) {
|
|
2
|
+
const c = command.toLowerCase();
|
|
3
|
+
if (c.includes('pytest'))
|
|
4
|
+
return 'pytest';
|
|
5
|
+
if (c.includes('vitest'))
|
|
6
|
+
return 'vitest';
|
|
7
|
+
if (c.includes('jest'))
|
|
8
|
+
return 'jest';
|
|
9
|
+
return 'unknown';
|
|
10
|
+
}
|
|
11
|
+
function runnerFromScriptText(script) {
|
|
12
|
+
const s = script.toLowerCase();
|
|
13
|
+
if (s.includes('vitest'))
|
|
14
|
+
return 'vitest';
|
|
15
|
+
if (s.includes('jest'))
|
|
16
|
+
return 'jest';
|
|
17
|
+
return 'unknown';
|
|
18
|
+
}
|
|
19
|
+
const SCRIPT_ORDER = ['test:unit', 'test', 'unit', 'test-ci'];
|
|
20
|
+
function firstMatchingScript(scripts) {
|
|
21
|
+
if (!scripts)
|
|
22
|
+
return null;
|
|
23
|
+
for (const name of SCRIPT_ORDER) {
|
|
24
|
+
const v = scripts[name];
|
|
25
|
+
if (typeof v === 'string' && v.trim() !== '')
|
|
26
|
+
return { name, value: v };
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
export function inferTestCommandAndRunner(input) {
|
|
31
|
+
const fromConfig = input.config.testCommand?.trim() ?? '';
|
|
32
|
+
if (fromConfig) {
|
|
33
|
+
return {
|
|
34
|
+
runner: runnerFromCommand(fromConfig),
|
|
35
|
+
command: fromConfig,
|
|
36
|
+
source: 'config',
|
|
37
|
+
reason: 'config.test.command',
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const fromPolicy = input.policy.workingTestCommand?.trim() ?? '';
|
|
41
|
+
if (fromPolicy) {
|
|
42
|
+
return {
|
|
43
|
+
runner: runnerFromCommand(fromPolicy),
|
|
44
|
+
command: fromPolicy,
|
|
45
|
+
source: 'policy',
|
|
46
|
+
reason: 'policy.workingTestCommand',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const hit = firstMatchingScript(input.packageJson?.scripts);
|
|
50
|
+
if (hit) {
|
|
51
|
+
const runner = runnerFromScriptText(hit.value);
|
|
52
|
+
return {
|
|
53
|
+
runner,
|
|
54
|
+
command: `npm run ${hit.name}`,
|
|
55
|
+
source: 'package_json',
|
|
56
|
+
reason: `package.json scripts.${hit.name}`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (input.pythonBoundary) {
|
|
60
|
+
const marker = input.pythonBoundary.boundaryFileRel ? ` (${input.pythonBoundary.boundaryFileRel})` : '';
|
|
61
|
+
return { runner: 'pytest', command: 'python -m pytest -q', source: 'inferred', reason: `python boundary${marker}` };
|
|
62
|
+
}
|
|
63
|
+
return { runner: 'unknown', command: null, source: 'none', reason: 'no_match' };
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=infer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"infer.js","sourceRoot":"","sources":["../../../src/core/testRunner/infer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type ResolvedTestCommand = {
|
|
2
|
+
command: string;
|
|
3
|
+
source: 'config' | 'policy' | 'package_json' | 'inferred';
|
|
4
|
+
reason: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function resolveTestCommand(opts: {
|
|
7
|
+
root: string;
|
|
8
|
+
configTestCommand: string | null;
|
|
9
|
+
policyWorkingTestCommand: string | null;
|
|
10
|
+
boundaryKind?: 'js' | 'py' | 'unknown';
|
|
11
|
+
boundaryFileRel?: string | null;
|
|
12
|
+
}): ResolvedTestCommand | null;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { inferTestCommandAndRunner } from './infer.js';
|
|
4
|
+
function readPackageJson(root) {
|
|
5
|
+
const p = path.join(root, 'package.json');
|
|
6
|
+
if (!fs.existsSync(p))
|
|
7
|
+
return null;
|
|
8
|
+
try {
|
|
9
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function resolveTestCommand(opts) {
|
|
16
|
+
const pkg = readPackageJson(opts.root);
|
|
17
|
+
const inferred = inferTestCommandAndRunner({
|
|
18
|
+
rootAbs: opts.root,
|
|
19
|
+
packageJson: pkg,
|
|
20
|
+
pythonBoundary: opts.boundaryKind === 'py' ? { boundaryFileRel: opts.boundaryFileRel ?? null } : null,
|
|
21
|
+
config: { testCommand: opts.configTestCommand },
|
|
22
|
+
policy: { workingTestCommand: opts.policyWorkingTestCommand },
|
|
23
|
+
});
|
|
24
|
+
if (!inferred.command)
|
|
25
|
+
return null;
|
|
26
|
+
if (inferred.source === 'none')
|
|
27
|
+
return null;
|
|
28
|
+
// resolveTestCommand historically only returns a command when we have one; keep that contract.
|
|
29
|
+
return { command: inferred.command, source: inferred.source === 'package_json' ? 'package_json' : inferred.source, reason: inferred.reason };
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=resolve.js.map
|