@wix/pathgrade 0.36.0 → 1.0.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.
Files changed (117) hide show
  1. package/LICENSE +24 -0
  2. package/README.md +508 -0
  3. package/dist/adapter-kit/index.d.ts +17 -0
  4. package/dist/adapter-kit/index.js +14 -0
  5. package/dist/adapters/jest/index.d.ts +3 -0
  6. package/dist/adapters/jest/index.js +3 -0
  7. package/dist/adapters/jest/invocation-adapter.d.ts +15 -0
  8. package/dist/adapters/jest/invocation-adapter.js +115 -0
  9. package/dist/adapters/jest/lifecycle.d.ts +21 -0
  10. package/dist/adapters/jest/lifecycle.js +62 -0
  11. package/dist/adapters/jest/metadata.d.ts +6 -0
  12. package/dist/adapters/jest/metadata.js +35 -0
  13. package/dist/adapters/jest/reporter.cjs +10 -0
  14. package/dist/adapters/jest/reporter.d.ts +5 -0
  15. package/dist/adapters/jest/reporter.js +67 -0
  16. package/dist/adapters/jest/results.d.ts +31 -0
  17. package/dist/adapters/jest/results.js +144 -0
  18. package/dist/adapters/jest/runner-adapter.d.ts +9 -0
  19. package/dist/adapters/jest/runner-adapter.js +49 -0
  20. package/dist/adapters/jest/setup.d.ts +1 -0
  21. package/dist/adapters/jest/setup.js +10 -0
  22. package/dist/adapters/node-test/index.d.ts +8 -0
  23. package/dist/adapters/node-test/index.js +106 -0
  24. package/dist/adapters/node-test/invocation-adapter.d.ts +5 -0
  25. package/dist/adapters/node-test/invocation-adapter.js +47 -0
  26. package/dist/adapters/node-test/runner-adapter.d.ts +2 -0
  27. package/dist/adapters/node-test/runner-adapter.js +116 -0
  28. package/dist/adapters/vitest/index.d.ts +9 -0
  29. package/dist/adapters/vitest/index.js +82 -0
  30. package/dist/adapters/vitest/lifecycle.d.ts +24 -0
  31. package/dist/adapters/vitest/lifecycle.js +41 -0
  32. package/dist/adapters/vitest/reporter.d.ts +12 -0
  33. package/dist/adapters/vitest/reporter.js +75 -0
  34. package/dist/adapters/vitest/setup.d.ts +1 -0
  35. package/dist/adapters/vitest/setup.js +3 -0
  36. package/dist/agents/codex-app-server/agent.js +5 -1
  37. package/dist/agents/codex-app-server/protocol/index.js +3 -3
  38. package/dist/agents/codex-app-server/turn-completion.d.ts +1 -0
  39. package/dist/agents/codex-app-server/turn-completion.js +9 -0
  40. package/dist/commands/affected.js +22 -15
  41. package/dist/commands/run-args.d.ts +4 -3
  42. package/dist/commands/run-args.js +12 -6
  43. package/dist/commands/run-changed.d.ts +4 -4
  44. package/dist/commands/run-changed.js +45 -58
  45. package/dist/config/pathgrade.d.ts +48 -0
  46. package/dist/config/pathgrade.js +204 -0
  47. package/dist/pathgrade.d.ts +2 -2
  48. package/dist/pathgrade.js +35 -34
  49. package/dist/plugin/index.d.ts +2 -9
  50. package/dist/plugin/index.js +1 -81
  51. package/dist/plugin/lifecycle.d.ts +1 -45
  52. package/dist/plugin/lifecycle.js +1 -146
  53. package/dist/plugin/reporter.d.ts +1 -37
  54. package/dist/plugin/reporter.js +1 -280
  55. package/dist/plugin/setup.d.ts +1 -1
  56. package/dist/plugin/setup.js +1 -3
  57. package/dist/providers/copy-filter.d.ts +5 -0
  58. package/dist/providers/copy-filter.js +9 -0
  59. package/dist/providers/sandbox.js +2 -2
  60. package/dist/providers/workspace.d.ts +6 -0
  61. package/dist/providers/workspace.js +34 -14
  62. package/dist/reporters/diagnostics.d.ts +2 -40
  63. package/dist/reporters/diagnostics.js +1 -80
  64. package/dist/reporters/report-summary.d.ts +6 -0
  65. package/dist/reporters/report-summary.js +29 -0
  66. package/dist/reporting/artifacts.d.ts +2 -0
  67. package/dist/reporting/artifacts.js +20 -0
  68. package/dist/reporting/core.d.ts +2 -0
  69. package/dist/reporting/core.js +173 -0
  70. package/dist/reporting/types.d.ts +57 -0
  71. package/dist/reporting/types.js +1 -0
  72. package/dist/reporting/vitest-edge.d.ts +1 -0
  73. package/dist/reporting/vitest-edge.js +1 -0
  74. package/dist/runners/adapter-loader.d.ts +17 -0
  75. package/dist/runners/adapter-loader.js +67 -0
  76. package/dist/runners/adapter.d.ts +65 -0
  77. package/dist/runners/adapter.js +1 -0
  78. package/dist/runners/invocation.d.ts +10 -0
  79. package/dist/runners/invocation.js +1 -0
  80. package/dist/runners/lifecycle-hooks.d.ts +2 -0
  81. package/dist/runners/lifecycle-hooks.js +18 -0
  82. package/dist/runners/model-builders.d.ts +4 -0
  83. package/dist/runners/model-builders.js +67 -0
  84. package/dist/runners/model-validation.d.ts +12 -0
  85. package/dist/runners/model-validation.js +213 -0
  86. package/dist/runners/model.d.ts +117 -0
  87. package/dist/runners/model.js +1 -0
  88. package/dist/runners/orchestrator.d.ts +30 -0
  89. package/dist/runners/orchestrator.js +74 -0
  90. package/dist/runners/report-projection.d.ts +6 -0
  91. package/dist/runners/report-projection.js +62 -0
  92. package/dist/runners/selection.d.ts +6 -0
  93. package/dist/runners/selection.js +10 -0
  94. package/dist/runners/vitest-adapter.d.ts +8 -0
  95. package/dist/runners/vitest-adapter.js +108 -0
  96. package/dist/runners/vitest-invocation.d.ts +10 -0
  97. package/dist/runners/vitest-invocation.js +44 -0
  98. package/dist/runners/vitest-lifecycle.d.ts +43 -0
  99. package/dist/runners/vitest-lifecycle.js +150 -0
  100. package/dist/sdk/agent.js +15 -16
  101. package/dist/sdk/case-context.d.ts +20 -0
  102. package/dist/sdk/case-context.js +31 -0
  103. package/dist/sdk/diagnostics.d.ts +40 -0
  104. package/dist/sdk/diagnostics.js +80 -0
  105. package/dist/sdk/eval-runtime.d.ts +4 -0
  106. package/dist/sdk/eval-runtime.js +11 -1
  107. package/dist/sdk/evaluate.js +2 -1
  108. package/dist/sdk/index.d.ts +2 -0
  109. package/dist/sdk/index.js +1 -0
  110. package/dist/sdk/lifecycle.d.ts +40 -0
  111. package/dist/sdk/lifecycle.js +170 -0
  112. package/dist/sdk/result-capture.d.ts +18 -0
  113. package/dist/sdk/result-capture.js +49 -0
  114. package/dist/sdk/types.d.ts +1 -1
  115. package/dist/types.d.ts +2 -2
  116. package/dist/utils/llm-providers/anthropic.js +9 -2
  117. package/package.json +73 -36
@@ -0,0 +1,115 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { spawn } from 'node:child_process';
4
+ import { createRequire } from 'node:module';
5
+ import { getPathgradeDir, printReportSummary, readSidecar, runWithAdapter, } from '@wix/pathgrade/adapter-kit';
6
+ import { removeJestMetadata, withJestMetadataEnv } from './metadata.js';
7
+ import { createJestAdapter } from './runner-adapter.js';
8
+ export function createJestInvocationAdapter(input) {
9
+ const spawnJest = input.spawnJest ?? defaultSpawnJest;
10
+ return {
11
+ name: 'jest',
12
+ async run(runInput) {
13
+ const selection = await readSidecar(runInput.cwd, msg => {
14
+ process.stderr.write(`[pathgrade] ${msg}\n`);
15
+ }) ?? undefined;
16
+ const include = runInput.selectedFiles && runInput.selectedFiles.length > 0
17
+ ? runInput.selectedFiles
18
+ : input.config.evals.include;
19
+ const exclude = runInput.selectedFiles ? [] : input.config.evals.exclude;
20
+ const discovered = await createJestAdapter().discover({
21
+ cwd: runInput.cwd,
22
+ include,
23
+ exclude,
24
+ selection,
25
+ });
26
+ if (discovered.units.length === 0) {
27
+ return await runWithAdapter({
28
+ adapter: createJestAdapter({ results: { testResults: [] } }),
29
+ options: {
30
+ cwd: runInput.cwd,
31
+ discovery: {
32
+ cwd: runInput.cwd,
33
+ include,
34
+ exclude,
35
+ selection,
36
+ },
37
+ runnerArgs: [],
38
+ env: runInput.env,
39
+ artifactRoot: getPathgradeDir(runInput.cwd),
40
+ reporterMode: input.config.reporter ?? 'cli',
41
+ threshold: input.config.ci.threshold,
42
+ selection,
43
+ printSummary: summaries => {
44
+ printReportSummary(summaries, {
45
+ forceVerbose: input.config.diagnostics
46
+ || runInput.env.PATHGRADE_DIAGNOSTICS === '1',
47
+ });
48
+ },
49
+ log: () => {
50
+ process.stdout.write(`\n Results written to ${getPathgradeDir(runInput.cwd)}\n\n`);
51
+ },
52
+ onThresholdFailure: ({ overallPassRate, threshold }) => {
53
+ process.stdout.write(`\n CI THRESHOLD FAILED avg score ${overallPassRate.toFixed(3)} < threshold ${threshold}\n\n`);
54
+ },
55
+ },
56
+ });
57
+ }
58
+ const env = withJestMetadataEnv(runInput.env, runInput.cwd);
59
+ removeJestMetadata(env.PATHGRADE_JEST_METADATA_PATH);
60
+ return await spawnJest({
61
+ cwd: runInput.cwd,
62
+ env,
63
+ argv: [
64
+ ...discovered.units.map(unit => unit.sourceRef).filter((sourceRef) => typeof sourceRef === 'string'),
65
+ '--setupFilesAfterEnv',
66
+ resolveAdapterFile('setup'),
67
+ '--reporters',
68
+ 'default',
69
+ '--reporters',
70
+ resolveReporterFile(),
71
+ ...runInput.runnerArgs,
72
+ ],
73
+ });
74
+ },
75
+ };
76
+ }
77
+ export function createPathgradeInvocationAdapter(input) {
78
+ return createJestInvocationAdapter(input);
79
+ }
80
+ function resolveAdapterFile(name) {
81
+ return path.resolve(import.meta.dirname, `${name}.js`);
82
+ }
83
+ function resolveReporterFile() {
84
+ return path.resolve(import.meta.dirname, 'reporter.cjs');
85
+ }
86
+ async function defaultSpawnJest(req) {
87
+ const jestBinPath = resolveLocalJestBinPath(req.cwd);
88
+ return await new Promise(resolve => {
89
+ const child = spawn(process.execPath, [jestBinPath, ...req.argv], {
90
+ stdio: 'inherit',
91
+ env: req.env,
92
+ cwd: req.cwd,
93
+ });
94
+ child.on('close', code => resolve(code ?? 0));
95
+ child.on('error', () => resolve(1));
96
+ });
97
+ }
98
+ export function resolveLocalJestBinPath(cwd) {
99
+ let packageJsonPath;
100
+ try {
101
+ const req = createRequire(path.join(cwd, 'package.json'));
102
+ packageJsonPath = req.resolve('jest/package.json');
103
+ }
104
+ catch {
105
+ throw new Error('pathgrade: Jest adapter requires Jest to be installed in the project. Install it with `npm install --save-dev jest` or the equivalent for your package manager.');
106
+ }
107
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
108
+ const bin = typeof packageJson.bin === 'string'
109
+ ? packageJson.bin
110
+ : packageJson.bin?.jest;
111
+ if (!bin) {
112
+ throw new Error(`pathgrade: could not find the Jest executable declared by ${packageJsonPath}.`);
113
+ }
114
+ return path.resolve(path.dirname(packageJsonPath), bin);
115
+ }
@@ -0,0 +1,21 @@
1
+ import { type CaseContext, type CaseContextProviderHandle, type ResultObserverHandle, type PathgradeTestMeta, type AdapterLifecycleHooks, type EvalResultEvent } from '@wix/pathgrade/adapter-kit';
2
+ export type JestHook = (fn: () => void | Promise<void>) => void;
3
+ export interface JestState {
4
+ testPath?: string;
5
+ currentTestName?: string;
6
+ }
7
+ export interface JestLifecycleInstallInput {
8
+ beforeEach: JestHook;
9
+ afterEach: JestHook;
10
+ afterAll?: JestHook;
11
+ getState: () => JestState;
12
+ lifecycle?: AdapterLifecycleHooks;
13
+ subscribeToResults?: (callback: (event: EvalResultEvent) => void) => ResultObserverHandle;
14
+ installCaseContextProvider?: (provider: () => CaseContext | null) => CaseContextProviderHandle;
15
+ }
16
+ export interface JestLifecycleInstallHandle {
17
+ restore(): void;
18
+ }
19
+ export declare function installJestLifecycle(input: JestLifecycleInstallInput): JestLifecycleInstallHandle;
20
+ export declare function getJestLifecycleMetadata(): Map<string, PathgradeTestMeta[]>;
21
+ export declare function resetJestLifecycleMetadata(): void;
@@ -0,0 +1,62 @@
1
+ import { installCaseContextProvider, subscribeToEvalResults, createRunnerLifecycleHooks, } from '@wix/pathgrade/adapter-kit';
2
+ import { appendJestMetadata } from './metadata.js';
3
+ import { jestCaseId } from './results.js';
4
+ const metadataByCaseId = new Map();
5
+ export function installJestLifecycle(input) {
6
+ const lifecycle = input.lifecycle ?? createRunnerLifecycleHooks();
7
+ const occurrenceCounts = new Map();
8
+ let activeCase = null;
9
+ let restored = false;
10
+ const resultCapture = (input.subscribeToResults ?? defaultSubscribeToResults)(event => lifecycle.onResult(event));
11
+ const contextProvider = (input.installCaseContextProvider ?? installCaseContextProvider)(() => activeCase);
12
+ metadataByCaseId.clear();
13
+ input.beforeEach(() => {
14
+ const state = input.getState();
15
+ const filePath = state.testPath ?? 'jest';
16
+ const caseName = state.currentTestName ?? 'Jest test';
17
+ const caseId = jestCaseId({
18
+ filePath,
19
+ fullName: caseName,
20
+ occurrenceCounts,
21
+ });
22
+ activeCase = {
23
+ caseId,
24
+ caseName,
25
+ filePath,
26
+ scope: 'runner-case',
27
+ };
28
+ });
29
+ input.afterEach(async () => {
30
+ const current = activeCase;
31
+ if (!current)
32
+ return;
33
+ const metadata = await lifecycle.flushCase(current.caseId);
34
+ if (metadata.length > 0) {
35
+ metadataByCaseId.set(current.caseId, metadata);
36
+ appendJestMetadata(current.caseId, metadata);
37
+ }
38
+ activeCase = null;
39
+ });
40
+ input.afterAll?.(async () => {
41
+ await lifecycle.cleanupRun();
42
+ restore();
43
+ });
44
+ return { restore };
45
+ function restore() {
46
+ if (restored)
47
+ return;
48
+ restored = true;
49
+ activeCase = null;
50
+ resultCapture.unsubscribe();
51
+ contextProvider.restore();
52
+ }
53
+ }
54
+ export function getJestLifecycleMetadata() {
55
+ return new Map(metadataByCaseId);
56
+ }
57
+ export function resetJestLifecycleMetadata() {
58
+ metadataByCaseId.clear();
59
+ }
60
+ function defaultSubscribeToResults(callback) {
61
+ return subscribeToEvalResults(({ result, agent }) => callback({ result, agent }), { owner: 'adapter', key: 'jest-lifecycle' });
62
+ }
@@ -0,0 +1,6 @@
1
+ import type { PathgradeTestMeta } from '@wix/pathgrade/adapter-kit';
2
+ export declare function resolveJestMetadataPath(cwd?: string): string;
3
+ export declare function withJestMetadataEnv(env: NodeJS.ProcessEnv, cwd: string): NodeJS.ProcessEnv;
4
+ export declare function appendJestMetadata(caseId: string, metadata: PathgradeTestMeta[]): void;
5
+ export declare function readJestMetadata(metadataPath?: string): Map<string, PathgradeTestMeta[]>;
6
+ export declare function removeJestMetadata(metadataPath?: string): void;
@@ -0,0 +1,35 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ const METADATA_ENV = 'PATHGRADE_JEST_METADATA_PATH';
4
+ const FALLBACK_METADATA_FILE = 'jest-metadata.jsonl';
5
+ export function resolveJestMetadataPath(cwd = process.cwd()) {
6
+ return process.env[METADATA_ENV] ?? path.join(cwd, '.pathgrade', FALLBACK_METADATA_FILE);
7
+ }
8
+ export function withJestMetadataEnv(env, cwd) {
9
+ return {
10
+ ...env,
11
+ [METADATA_ENV]: env[METADATA_ENV] ?? path.join(cwd, '.pathgrade', `${process.pid}-${Date.now()}-${FALLBACK_METADATA_FILE}`),
12
+ };
13
+ }
14
+ export function appendJestMetadata(caseId, metadata) {
15
+ if (metadata.length === 0)
16
+ return;
17
+ const metadataPath = resolveJestMetadataPath();
18
+ fs.mkdirSync(path.dirname(metadataPath), { recursive: true });
19
+ fs.appendFileSync(metadataPath, `${JSON.stringify({ caseId, metadata })}\n`, 'utf8');
20
+ }
21
+ export function readJestMetadata(metadataPath = resolveJestMetadataPath()) {
22
+ if (!fs.existsSync(metadataPath))
23
+ return new Map();
24
+ const metadataByCaseId = new Map();
25
+ for (const line of fs.readFileSync(metadataPath, 'utf8').split('\n')) {
26
+ if (line.trim().length === 0)
27
+ continue;
28
+ const entry = JSON.parse(line);
29
+ metadataByCaseId.set(entry.caseId, entry.metadata);
30
+ }
31
+ return metadataByCaseId;
32
+ }
33
+ export function removeJestMetadata(metadataPath = resolveJestMetadataPath()) {
34
+ fs.rmSync(metadataPath, { force: true });
35
+ }
@@ -0,0 +1,10 @@
1
+ class PathgradeJestReporterBridge {
2
+ async onRunComplete(...args) {
3
+ const mod = await import('./reporter.js');
4
+ const Reporter = mod.default;
5
+ const reporter = new Reporter();
6
+ return await reporter.onRunComplete(...args);
7
+ }
8
+ }
9
+
10
+ module.exports = PathgradeJestReporterBridge;
@@ -0,0 +1,5 @@
1
+ import type { JestAggregatedResult } from './results.js';
2
+ export default class PathgradeJestReporter {
3
+ onRunComplete(_contexts: unknown, results: JestAggregatedResult): Promise<void>;
4
+ private openBrowserViewer;
5
+ }
@@ -0,0 +1,67 @@
1
+ import * as path from 'node:path';
2
+ import { execSync } from 'node:child_process';
3
+ import { fmt, getPathgradeDir, printReportSummary, readSidecar, resolvePathgradeConfig, runWithAdapter, } from '@wix/pathgrade/adapter-kit';
4
+ import { getJestLifecycleMetadata } from './lifecycle.js';
5
+ import { readJestMetadata, removeJestMetadata } from './metadata.js';
6
+ import { createJestAdapter } from './runner-adapter.js';
7
+ export default class PathgradeJestReporter {
8
+ async onRunComplete(_contexts, results) {
9
+ const cwd = process.cwd();
10
+ const config = await resolvePathgradeConfig({ cwd });
11
+ const mode = config.reporter ?? 'cli';
12
+ const outputDir = getPathgradeDir(cwd);
13
+ const metadataByCaseId = new Map([
14
+ ...readJestMetadata(),
15
+ ...getJestLifecycleMetadata(),
16
+ ]);
17
+ const exitCode = await runWithAdapter({
18
+ adapter: createJestAdapter({
19
+ results,
20
+ metadataByCaseId,
21
+ }),
22
+ options: {
23
+ cwd,
24
+ discovery: { cwd },
25
+ runnerArgs: [],
26
+ env: process.env,
27
+ artifactRoot: outputDir,
28
+ reporterMode: mode,
29
+ threshold: config.ci.threshold,
30
+ writeEmptyReport: false,
31
+ warn: warning => console.warn(warning),
32
+ log: () => console.log(`\n ${fmt.dim('Results written to')} ${outputDir}\n`),
33
+ loadSelection: async () => (await readSidecar(cwd, msg => {
34
+ console.warn(`[pathgrade] ${msg}`);
35
+ })) ?? undefined,
36
+ printSummary: summaries => {
37
+ printReportSummary(summaries, {
38
+ forceVerbose: config.diagnostics
39
+ || process.env.PATHGRADE_DIAGNOSTICS === '1',
40
+ });
41
+ },
42
+ openBrowser: () => this.openBrowserViewer(),
43
+ onThresholdFailure: ({ overallPassRate, threshold }) => {
44
+ process.exitCode = 1;
45
+ console.log(`\n ${fmt.fail('CI THRESHOLD FAILED')} avg score ${fmt.bold(overallPassRate.toFixed(3))} < threshold ${fmt.bold(String(threshold))}\n`);
46
+ },
47
+ },
48
+ });
49
+ if (exitCode !== 0 && (process.exitCode === undefined || process.exitCode === 0)) {
50
+ process.exitCode = exitCode;
51
+ }
52
+ removeJestMetadata();
53
+ }
54
+ openBrowserViewer() {
55
+ const viewerPath = path.resolve(import.meta.dirname, '..', '..', 'viewer.html');
56
+ try {
57
+ const openCmd = process.platform === 'darwin' ? 'open'
58
+ : process.platform === 'win32' ? 'start'
59
+ : 'xdg-open';
60
+ execSync(`${openCmd} "${viewerPath}"`, { stdio: 'ignore' });
61
+ console.log(`\n ${fmt.dim('Opened viewer in browser')}\n`);
62
+ }
63
+ catch {
64
+ console.log(`\n ${fmt.dim('Open manually:')} ${viewerPath}\n`);
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,31 @@
1
+ import type { AdapterRunHandle, NormalizedRunSnapshot, PathgradeTestMeta } from '@wix/pathgrade/adapter-kit';
2
+ export interface JestAggregatedResult {
3
+ testResults?: JestFileResult[];
4
+ }
5
+ export interface JestFileResult {
6
+ testFilePath: string;
7
+ assertionResults?: JestAssertionResult[];
8
+ testResults?: JestAssertionResult[];
9
+ }
10
+ export interface JestAssertionResult {
11
+ ancestorTitles?: string[];
12
+ title: string;
13
+ fullName?: string;
14
+ status: string;
15
+ duration?: number | null;
16
+ failureMessages?: string[];
17
+ location?: {
18
+ line?: number | null;
19
+ column?: number | null;
20
+ } | null;
21
+ }
22
+ export declare function normalizeJestRunResults(input: {
23
+ run: AdapterRunHandle;
24
+ results: JestAggregatedResult;
25
+ metadataByCaseId?: Map<string, PathgradeTestMeta[]>;
26
+ }): NormalizedRunSnapshot;
27
+ export declare function jestCaseId(input: {
28
+ filePath: string;
29
+ fullName: string;
30
+ occurrenceCounts?: Map<string, number>;
31
+ }): string;
@@ -0,0 +1,144 @@
1
+ export function normalizeJestRunResults(input) {
2
+ const runId = `${input.run.adapterName}:run`;
3
+ const files = input.results.testResults ?? [];
4
+ const units = files.map((fileResult, index) => ({
5
+ id: unitId(index),
6
+ runId,
7
+ displayName: fileResult.testFilePath,
8
+ groupingHints: [{
9
+ kind: 'source',
10
+ key: fileResult.testFilePath,
11
+ label: fileResult.testFilePath,
12
+ order: index,
13
+ }],
14
+ nativeReferences: [{ kind: 'jest-file', id: fileResult.testFilePath }],
15
+ }));
16
+ const occurrenceCounts = new Map();
17
+ return {
18
+ version: 1,
19
+ completeness: 'final',
20
+ model: {
21
+ run: {
22
+ id: runId,
23
+ adapterName: input.run.adapterName,
24
+ status: input.run.status,
25
+ },
26
+ units,
27
+ cases: files.flatMap((fileResult, fileIndex) => assertionsForFile(fileResult).map(assertion => {
28
+ const caseId = jestCaseId({
29
+ filePath: fileResult.testFilePath,
30
+ fullName: assertion.fullName ?? fullCaseName(assertion),
31
+ occurrenceCounts,
32
+ });
33
+ return runCase({
34
+ runId,
35
+ unitId: unitId(fileIndex),
36
+ caseId,
37
+ assertion,
38
+ filePath: fileResult.testFilePath,
39
+ evaluations: input.metadataByCaseId?.get(caseId) ?? [],
40
+ });
41
+ })),
42
+ },
43
+ };
44
+ }
45
+ function assertionsForFile(fileResult) {
46
+ return fileResult.assertionResults ?? fileResult.testResults ?? [];
47
+ }
48
+ export function jestCaseId(input) {
49
+ const base = `jest:${input.filePath}:${slug(input.fullName)}`;
50
+ const current = input.occurrenceCounts?.get(base) ?? 0;
51
+ const occurrence = current + 1;
52
+ input.occurrenceCounts?.set(base, occurrence);
53
+ return `${base}:${occurrence}`;
54
+ }
55
+ function runCase(input) {
56
+ const state = normalizeState(input.assertion.status);
57
+ const attemptId = `${input.caseId}:attempt-1`;
58
+ const evaluations = input.evaluations.map((entry, index) => ({
59
+ id: `${attemptId}:evaluation-${index + 1}`,
60
+ attemptId,
61
+ score: entry.score,
62
+ ...(entry.trial ? { trial: entry.trial } : {}),
63
+ ...(entry.diagnostics ? { diagnostics: entry.diagnostics } : {}),
64
+ }));
65
+ const failureMessage = input.assertion.failureMessages?.find(message => message.trim().length > 0);
66
+ return {
67
+ id: input.caseId,
68
+ runId: input.runId,
69
+ unitId: input.unitId,
70
+ name: fullCaseName(input.assertion),
71
+ state,
72
+ scoringPolicy: scoringPolicyForCase(state, evaluations),
73
+ groupingHints: [{
74
+ kind: 'source',
75
+ key: input.filePath,
76
+ label: input.filePath,
77
+ order: 0,
78
+ }],
79
+ nativeReferences: [{
80
+ kind: 'jest-case',
81
+ id: input.caseId,
82
+ metadata: {
83
+ status: input.assertion.status,
84
+ ...(input.assertion.location?.line ? { line: input.assertion.location.line } : {}),
85
+ ...(input.assertion.location?.column ? { column: input.assertion.location.column } : {}),
86
+ },
87
+ }],
88
+ attempts: [{
89
+ id: attemptId,
90
+ caseId: input.caseId,
91
+ outcome: outcomeForCase(state, failureMessage),
92
+ durationMs: input.assertion.duration ?? 0,
93
+ ...(evaluations.length > 0 ? { evaluations } : {}),
94
+ ...(failureMessage ? {
95
+ assertions: [{
96
+ id: `${attemptId}:assertion-1`,
97
+ attemptId,
98
+ name: 'Jest assertion failure',
99
+ status: 'failed',
100
+ severity: 'error',
101
+ message: failureMessage,
102
+ }],
103
+ } : {}),
104
+ }],
105
+ };
106
+ }
107
+ function fullCaseName(assertion) {
108
+ const ancestors = assertion.ancestorTitles ?? [];
109
+ return [...ancestors, assertion.title].filter(Boolean).join(' > ');
110
+ }
111
+ function normalizeState(status) {
112
+ if (status === 'passed')
113
+ return 'passed';
114
+ if (status === 'skipped')
115
+ return 'skipped';
116
+ if (status === 'pending' || status === 'todo' || status === 'disabled')
117
+ return 'pending';
118
+ return 'failed';
119
+ }
120
+ function scoringPolicyForCase(state, evaluations) {
121
+ if (state === 'skipped' || state === 'pending')
122
+ return { kind: 'non-scoring', reason: state };
123
+ if (evaluations.length > 0)
124
+ return { kind: 'from-evaluations' };
125
+ return { kind: 'score', score: state === 'passed' ? 1 : 0 };
126
+ }
127
+ function outcomeForCase(state, failureMessage) {
128
+ if (state === 'passed')
129
+ return { kind: 'passed' };
130
+ if (state === 'failed')
131
+ return { kind: 'failed', ...(failureMessage ? { reason: failureMessage } : {}) };
132
+ return { kind: 'not-run', reason: state };
133
+ }
134
+ function unitId(index) {
135
+ return `jest:unit-${index + 1}`;
136
+ }
137
+ function slug(value) {
138
+ const clean = value
139
+ .trim()
140
+ .toLowerCase()
141
+ .replaceAll(/[^a-z0-9]+/g, '-')
142
+ .replaceAll(/^-|-$/g, '');
143
+ return clean || 'case';
144
+ }
@@ -0,0 +1,9 @@
1
+ import { type JestAggregatedResult } from './results.js';
2
+ import { type PathgradeTestMeta, RunnerAdapter } from '@wix/pathgrade/adapter-kit';
3
+ interface JestRunNative {
4
+ results?: JestAggregatedResult;
5
+ metadataByCaseId?: Map<string, PathgradeTestMeta[]>;
6
+ }
7
+ export declare function createJestAdapter(options?: JestRunNative): RunnerAdapter;
8
+ export declare function createPathgradeAdapter(): RunnerAdapter;
9
+ export {};
@@ -0,0 +1,49 @@
1
+ import { normalizeJestRunResults } from './results.js';
2
+ import { discoverPathgradeEvalFiles, } from '@wix/pathgrade/adapter-kit';
3
+ export function createJestAdapter(options = {}) {
4
+ return {
5
+ name: 'jest',
6
+ async discover(input) {
7
+ const include = input.include ?? ['**/*.eval.ts'];
8
+ const exclude = input.exclude ?? [];
9
+ const files = discoverPathgradeEvalFiles({
10
+ cwd: input.cwd,
11
+ include,
12
+ exclude,
13
+ });
14
+ return {
15
+ units: files.map(file => ({
16
+ id: file,
17
+ displayName: file,
18
+ sourceRef: file,
19
+ native: { file, cwd: input.cwd },
20
+ })),
21
+ };
22
+ },
23
+ async invoke(input) {
24
+ return {
25
+ adapterName: this.name,
26
+ status: input.signal?.aborted ? 'cancelled' : 'completed',
27
+ exitCode: input.signal?.aborted ? 1 : 0,
28
+ native: options,
29
+ };
30
+ },
31
+ async collectNormalizedRunSnapshot(run) {
32
+ const native = readJestRunNative(run);
33
+ return normalizeJestRunResults({
34
+ run,
35
+ results: native.results ?? { testResults: [] },
36
+ metadataByCaseId: native.metadataByCaseId,
37
+ });
38
+ },
39
+ };
40
+ }
41
+ export function createPathgradeAdapter() {
42
+ return createJestAdapter();
43
+ }
44
+ function readJestRunNative(run) {
45
+ if (typeof run.native === 'object' && run.native !== null) {
46
+ return run.native;
47
+ }
48
+ return {};
49
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import { installJestLifecycle } from './lifecycle.js';
2
+ const jestGlobals = globalThis;
3
+ if (jestGlobals.beforeEach && jestGlobals.afterEach && jestGlobals.expect?.getState) {
4
+ installJestLifecycle({
5
+ beforeEach: jestGlobals.beforeEach,
6
+ afterEach: jestGlobals.afterEach,
7
+ afterAll: jestGlobals.afterAll,
8
+ getState: () => jestGlobals.expect?.getState?.() ?? {},
9
+ });
10
+ }
@@ -0,0 +1,8 @@
1
+ import type { TestContext } from 'node:test';
2
+ type NodeTestFn = (t: TestContext) => unknown | Promise<unknown>;
3
+ type NodeTestOptions = Record<string, unknown>;
4
+ export declare function test(name: string, fn: NodeTestFn): void;
5
+ export declare function test(name: string, options: NodeTestOptions, fn: NodeTestFn): void;
6
+ export default test;
7
+ export { createNodeTestAdapter } from './runner-adapter.js';
8
+ export { createNodeTestInvocationAdapter } from './invocation-adapter.js';