Skip to content

Commit ee7d965

Browse files
committed
fix(vitest): resolve reportsDirectory against workspace root
When reportsDirectory is set with {workspaceRoot} token in nx.json targetDefaults, Nx's resolveNxTokensInOptions strips the token and produces a workspace-root-relative path. The vitest executor then passed this path directly to vitest, which resolved it relative to the project root, causing coverage output to land in the wrong location (e.g. apps/my-app/coverage/apps/my-app/ instead of coverage/apps/my-app/). This fix resolves non-absolute reportsDirectory paths against the workspace root before passing them to vitest, ensuring coverage output goes to the intended location.
1 parent 0568059 commit ee7d965

File tree

2 files changed

+45
-2
lines changed

2 files changed

+45
-2
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { workspaceRoot } from '@nx/devkit';
2+
import { isAbsolute, join } from 'path';
3+
import { resolveReportsDirectory } from './utils';
4+
5+
describe('resolveReportsDirectory', () => {
6+
it('should resolve a workspace-root-relative path to absolute', () => {
7+
// After Nx's resolveNxTokensInOptions processes "{workspaceRoot}/coverage/apps/my-app",
8+
// the value becomes "coverage/apps/my-app" (workspace-root-relative).
9+
const result = resolveReportsDirectory('coverage/apps/my-app');
10+
expect(isAbsolute(result)).toBe(true);
11+
expect(result).toBe(join(workspaceRoot, 'coverage/apps/my-app'));
12+
});
13+
14+
it('should return absolute paths unchanged', () => {
15+
const absolutePath = '/tmp/coverage/my-app';
16+
const result = resolveReportsDirectory(absolutePath);
17+
expect(result).toBe(absolutePath);
18+
});
19+
20+
it('should resolve a simple relative path to absolute', () => {
21+
// A plain relative path like "coverage" (no tokens originally)
22+
// also gets resolved against workspace root
23+
const result = resolveReportsDirectory('coverage');
24+
expect(isAbsolute(result)).toBe(true);
25+
expect(result).toBe(join(workspaceRoot, 'coverage'));
26+
});
27+
});

packages/vitest/src/executors/test/lib/utils.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import {
33
joinPathFragments,
44
logger,
55
stripIndents,
6+
workspaceRoot,
67
} from '@nx/devkit';
78
import { VitestExecutorOptions } from '../schema';
89
import { normalizeViteConfigFilePath } from '../../../utils/options-utils';
9-
import { relative } from 'path';
10+
import { isAbsolute, relative, resolve } from 'path';
1011
import {
1112
loadViteDynamicImport,
1213
loadVitestDynamicImport,
@@ -100,11 +101,26 @@ export async function getOptions(
100101
reporters: resolved?.config?.['test']?.reporters,
101102
coverage: {
102103
...(coverage ?? {}),
103-
...(reportsDirectory && { reportsDirectory }),
104+
...(reportsDirectory && {
105+
reportsDirectory: resolveReportsDirectory(reportsDirectory),
106+
}),
104107
},
105108
};
106109
}
107110

111+
/**
112+
* Nx's resolveNxTokensInOptions strips {workspaceRoot}/ from option values,
113+
* leaving a workspace-root-relative path. However, vitest resolves
114+
* reportsDirectory relative to the project root. This function converts
115+
* the path to absolute so vitest resolves it correctly.
116+
*/
117+
export function resolveReportsDirectory(reportsDirectory: string): string {
118+
if (isAbsolute(reportsDirectory)) {
119+
return reportsDirectory;
120+
}
121+
return resolve(workspaceRoot, reportsDirectory);
122+
}
123+
108124
export function getOptionsAsArgv(obj: Record<string, any>): string[] {
109125
const argv: string[] = [];
110126

0 commit comments

Comments
 (0)