@nx/vitest 23.3.0-beta.3 → 23.3.0-beta.5
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.
|
@@ -9,6 +9,7 @@ const node_fs_1 = require("node:fs");
|
|
|
9
9
|
const promises_1 = require("node:fs/promises");
|
|
10
10
|
const node_path_1 = require("node:path");
|
|
11
11
|
const executor_utils_1 = require("../utils/executor-utils");
|
|
12
|
+
const setup_file_inputs_1 = require("./setup-file-inputs");
|
|
12
13
|
/**
|
|
13
14
|
* @deprecated The 'createDependencies' function is now a no-op. This functionality is included in 'createNodesV2'.
|
|
14
15
|
*/
|
|
@@ -42,6 +43,7 @@ exports.createNodes = [
|
|
|
42
43
|
lockfile,
|
|
43
44
|
...(tsconfigChainsByProjectRoot.get(root) ?? []),
|
|
44
45
|
]));
|
|
46
|
+
const setupTsconfigJsonCache = new Map();
|
|
45
47
|
try {
|
|
46
48
|
return await (0, devkit_1.createNodesFromFiles)(async (configFile, _, context, idx) => {
|
|
47
49
|
const projectRoot = (0, node_path_1.dirname)(configFile);
|
|
@@ -50,7 +52,10 @@ exports.createNodes = [
|
|
|
50
52
|
// Adding the config file path to the hash ensures that the final hash value is different
|
|
51
53
|
// for different config files.
|
|
52
54
|
const hash = hashes[idx] + configFile;
|
|
53
|
-
|
|
55
|
+
const cachedTargets = targetsCache.get(hash);
|
|
56
|
+
// Older cache entries predate `setupFileCandidates`.
|
|
57
|
+
if (cachedTargets === undefined ||
|
|
58
|
+
(cachedTargets && !cachedTargets.setupFileCandidates)) {
|
|
54
59
|
const result = await buildVitestTargets(configFile, projectRoot, normalizedOptions, context, pmc, tsconfigChainsByProjectRoot.get(projectRoot) ?? []);
|
|
55
60
|
// Cache the result even when it's null (a root orchestrator config)
|
|
56
61
|
// so the config isn't re-resolved on every project-graph build.
|
|
@@ -62,10 +67,12 @@ exports.createNodes = [
|
|
|
62
67
|
if (!cached) {
|
|
63
68
|
return { projects: {} };
|
|
64
69
|
}
|
|
65
|
-
const { metadata, targets } = cached;
|
|
70
|
+
const { metadata, targets, setupFileCandidates } = cached;
|
|
66
71
|
const project = {
|
|
67
72
|
root: projectRoot,
|
|
68
|
-
|
|
73
|
+
// Resolved on every run: the cache key does not cover setup files
|
|
74
|
+
// or their tsconfigs.
|
|
75
|
+
targets: withSetupFileInputs(targets, normalizedOptions.testTargetName, setupFileCandidates, projectRoot, context.workspaceRoot, setupTsconfigJsonCache),
|
|
69
76
|
metadata,
|
|
70
77
|
};
|
|
71
78
|
return {
|
|
@@ -105,9 +112,25 @@ async function buildVitestTargets(configFilePath, projectRoot, options, context,
|
|
|
105
112
|
// vitest/node not available or not needed, ignore
|
|
106
113
|
}
|
|
107
114
|
const { resolveConfig } = await (0, executor_utils_1.loadViteDynamicImport)();
|
|
115
|
+
// Vite fills a missing `root` with `process.cwd()`, which at graph time is
|
|
116
|
+
// the workspace root - not where the task runs. Capture what the config
|
|
117
|
+
// actually authored so a relative path can be resolved the way Vitest will.
|
|
118
|
+
let authoredViteRoot;
|
|
108
119
|
const viteBuildConfig = await (0, internal_1.retryOnRequireEsmRace)(() => resolveConfig({
|
|
109
120
|
configFile: absoluteConfigFilePath,
|
|
110
121
|
mode: 'development',
|
|
122
|
+
plugins: [
|
|
123
|
+
{
|
|
124
|
+
name: 'nx-capture-authored-vitest-root',
|
|
125
|
+
enforce: 'post',
|
|
126
|
+
config: {
|
|
127
|
+
order: 'post',
|
|
128
|
+
handler(config) {
|
|
129
|
+
authoredViteRoot = config.root;
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
],
|
|
111
134
|
}, 'build'));
|
|
112
135
|
// A root config that aggregates project configs via `test.projects` is just an
|
|
113
136
|
// orchestrator; the actual tests live in the individual project configs. Skip it
|
|
@@ -122,9 +145,11 @@ async function buildVitestTargets(configFilePath, projectRoot, options, context,
|
|
|
122
145
|
const { testOutputs, hasTest } = getOutputs(viteBuildConfig, projectRoot, context.workspaceRoot);
|
|
123
146
|
const namedInputs = (0, internal_1.getNamedInputs)(projectRoot, context);
|
|
124
147
|
const targets = {};
|
|
148
|
+
let setupFileCandidates = [];
|
|
125
149
|
// if file is vitest.config or vite.config has definition for test, create targets for test and/or atomized tests
|
|
126
150
|
if (configFilePath.includes('vitest.config') || hasTest) {
|
|
127
151
|
const isTypecheckEnabled = !!viteBuildConfig.test?.typecheck?.enabled;
|
|
152
|
+
setupFileCandidates = (0, setup_file_inputs_1.resolveSetupFileCandidates)(viteBuildConfig, projectRoot, context.workspaceRoot, authoredViteRoot);
|
|
128
153
|
targets[options.testTargetName] = await testTarget(namedInputs, testOutputs, projectRoot, options.testMode, pmc, isTypecheckEnabled, tsconfigInputs);
|
|
129
154
|
if (options.ciTargetName) {
|
|
130
155
|
const groupName = options.ciGroupName ?? (0, internal_1.deriveGroupNameFromTarget)(options.ciTargetName);
|
|
@@ -288,7 +313,28 @@ async function buildVitestTargets(configFilePath, projectRoot, options, context,
|
|
|
288
313
|
}
|
|
289
314
|
}
|
|
290
315
|
}
|
|
291
|
-
return { targets, metadata };
|
|
316
|
+
return { targets, metadata, setupFileCandidates };
|
|
317
|
+
}
|
|
318
|
+
function withSetupFileInputs(targets, testTargetName, setupFileCandidates, projectRoot, workspaceRoot, jsonCache) {
|
|
319
|
+
if (setupFileCandidates.length === 0)
|
|
320
|
+
return targets;
|
|
321
|
+
const { files, tsconfigs } = (0, setup_file_inputs_1.collectSetupFileInputs)(setupFileCandidates, projectRoot, workspaceRoot, jsonCache);
|
|
322
|
+
if (files.length === 0 && tsconfigs.length === 0)
|
|
323
|
+
return targets;
|
|
324
|
+
const setupInputs = [
|
|
325
|
+
...tsconfigs.map((f) => ({
|
|
326
|
+
json: `{workspaceRoot}/${f}`,
|
|
327
|
+
fields: ['compilerOptions'],
|
|
328
|
+
})),
|
|
329
|
+
// Whole-file, not just `compilerOptions`: these are sources Vitest
|
|
330
|
+
// executes, so any change to them changes the run.
|
|
331
|
+
...files.map((f) => `{workspaceRoot}/${f}`),
|
|
332
|
+
];
|
|
333
|
+
const inputs = [...targets[testTargetName].inputs, ...setupInputs];
|
|
334
|
+
return Object.fromEntries(Object.entries(targets).map(([name, target]) => [
|
|
335
|
+
name,
|
|
336
|
+
{ ...target, inputs },
|
|
337
|
+
]));
|
|
292
338
|
}
|
|
293
339
|
async function testTarget(namedInputs, outputs, projectRoot, testMode = 'watch', pmc, isTypecheckEnabled, tsconfigInputs) {
|
|
294
340
|
const command = testMode === 'run' ? 'vitest run' : 'vitest';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { RawTsconfigJsonCache } from '@nx/js/internal';
|
|
2
|
+
import type { ResolvedConfig } from 'vite';
|
|
3
|
+
/**
|
|
4
|
+
* Resolves the files Vitest loads from outside the project root - `setupFiles`
|
|
5
|
+
* and `globalSetup`. `collectSetupFileInputs` declares the ones that exist,
|
|
6
|
+
* together with the tsconfigs Vite reads to transform them.
|
|
7
|
+
*
|
|
8
|
+
* Reads the build-resolved config, as the inferred target's inputs are built
|
|
9
|
+
* from it. Setup files contributed by an `apply: 'serve'` plugin are therefore
|
|
10
|
+
* invisible here - the same build/serve divergence the atomized path documents.
|
|
11
|
+
*
|
|
12
|
+
* Solution-style tsconfigs are deliberately not followed. When the nearest
|
|
13
|
+
* tsconfig of a setup file is a solution file, Vite walks its `references` and
|
|
14
|
+
* reads every project's tsconfig; declaring those would attach one input per
|
|
15
|
+
* project in the workspace. Keep the setup file inside a project (or beside a
|
|
16
|
+
* leaf tsconfig) instead.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveSetupFileCandidates(viteConfig: ResolvedConfig, projectRoot: string, workspaceRoot: string,
|
|
19
|
+
/** `root` as the config authored it, before vite defaults it to `cwd`. */
|
|
20
|
+
authoredViteRoot?: string): string[];
|
|
21
|
+
export declare function collectSetupFileInputs(setupFileCandidates: string[], projectRoot: string, workspaceRoot: string, jsonCache: RawTsconfigJsonCache): {
|
|
22
|
+
files: string[];
|
|
23
|
+
tsconfigs: string[];
|
|
24
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveSetupFileCandidates = resolveSetupFileCandidates;
|
|
4
|
+
exports.collectSetupFileInputs = collectSetupFileInputs;
|
|
5
|
+
const internal_1 = require("@nx/js/internal");
|
|
6
|
+
const js_1 = require("@nx/js");
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_path_1 = require("node:path");
|
|
9
|
+
/**
|
|
10
|
+
* Resolves the files Vitest loads from outside the project root - `setupFiles`
|
|
11
|
+
* and `globalSetup`. `collectSetupFileInputs` declares the ones that exist,
|
|
12
|
+
* together with the tsconfigs Vite reads to transform them.
|
|
13
|
+
*
|
|
14
|
+
* Reads the build-resolved config, as the inferred target's inputs are built
|
|
15
|
+
* from it. Setup files contributed by an `apply: 'serve'` plugin are therefore
|
|
16
|
+
* invisible here - the same build/serve divergence the atomized path documents.
|
|
17
|
+
*
|
|
18
|
+
* Solution-style tsconfigs are deliberately not followed. When the nearest
|
|
19
|
+
* tsconfig of a setup file is a solution file, Vite walks its `references` and
|
|
20
|
+
* reads every project's tsconfig; declaring those would attach one input per
|
|
21
|
+
* project in the workspace. Keep the setup file inside a project (or beside a
|
|
22
|
+
* leaf tsconfig) instead.
|
|
23
|
+
*/
|
|
24
|
+
function resolveSetupFileCandidates(viteConfig, projectRoot, workspaceRoot,
|
|
25
|
+
/** `root` as the config authored it, before vite defaults it to `cwd`. */
|
|
26
|
+
authoredViteRoot) {
|
|
27
|
+
// At the workspace root everything is already covered by `default`.
|
|
28
|
+
if (projectRoot === '.')
|
|
29
|
+
return [];
|
|
30
|
+
const entries = [
|
|
31
|
+
viteConfig.test?.setupFiles,
|
|
32
|
+
viteConfig.test?.globalSetup,
|
|
33
|
+
].flatMap((value) => typeof value === 'string' ? [value] : Array.isArray(value) ? value : []);
|
|
34
|
+
if (entries.length === 0)
|
|
35
|
+
return [];
|
|
36
|
+
// Vitest resolves both options against its root, and the inferred target
|
|
37
|
+
// runs with `cwd` set to the project root - so a config that authors no
|
|
38
|
+
// root resolves them against the project, not `viteConfig.root` (which vite
|
|
39
|
+
// has already defaulted to the graph process's cwd). Mirrors
|
|
40
|
+
// `effectiveVitestRoot` in plugin.ts.
|
|
41
|
+
const fullProjectRoot = (0, node_path_1.resolve)(workspaceRoot, projectRoot);
|
|
42
|
+
const authoredRoot = viteConfig.test?.root ?? authoredViteRoot;
|
|
43
|
+
const configRoot = !authoredRoot
|
|
44
|
+
? fullProjectRoot
|
|
45
|
+
: (0, node_path_1.isAbsolute)(authoredRoot)
|
|
46
|
+
? authoredRoot
|
|
47
|
+
: (0, node_path_1.resolve)(fullProjectRoot, authoredRoot);
|
|
48
|
+
const candidates = new Set();
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
const absolutePath = (0, node_path_1.isAbsolute)(entry) ? entry : (0, node_path_1.resolve)(configRoot, entry);
|
|
51
|
+
const wsRelative = declarable(absolutePath, projectRoot, workspaceRoot);
|
|
52
|
+
if (wsRelative)
|
|
53
|
+
candidates.add(wsRelative);
|
|
54
|
+
}
|
|
55
|
+
return [...candidates];
|
|
56
|
+
}
|
|
57
|
+
function collectSetupFileInputs(setupFileCandidates, projectRoot, workspaceRoot, jsonCache) {
|
|
58
|
+
const rootTsConfigName = (0, js_1.getRootTsConfigFileName)();
|
|
59
|
+
const files = [];
|
|
60
|
+
const tsconfigs = new Set();
|
|
61
|
+
for (const file of setupFileCandidates) {
|
|
62
|
+
const absolutePath = (0, node_path_1.join)(workspaceRoot, file);
|
|
63
|
+
if (!(0, node_fs_1.existsSync)(absolutePath))
|
|
64
|
+
continue;
|
|
65
|
+
files.push(file);
|
|
66
|
+
// The tsconfig Vite resolves for the file: the nearest one walking up,
|
|
67
|
+
// plus its extends chain.
|
|
68
|
+
let dir = (0, node_path_1.dirname)(absolutePath);
|
|
69
|
+
while (dir.startsWith(workspaceRoot)) {
|
|
70
|
+
const candidate = (0, node_path_1.join)(dir, 'tsconfig.json');
|
|
71
|
+
if ((0, node_fs_1.existsSync)(candidate)) {
|
|
72
|
+
(0, internal_1.walkTsconfigExtendsChain)(candidate, (absPath) => {
|
|
73
|
+
const relativePath = declarable(absPath, projectRoot, workspaceRoot);
|
|
74
|
+
if (relativePath && relativePath !== rootTsConfigName) {
|
|
75
|
+
tsconfigs.add(relativePath);
|
|
76
|
+
}
|
|
77
|
+
return 'continue';
|
|
78
|
+
}, { jsonCache });
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
const parent = (0, node_path_1.dirname)(dir);
|
|
82
|
+
if (parent === dir)
|
|
83
|
+
break;
|
|
84
|
+
dir = parent;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { files, tsconfigs: [...tsconfigs] };
|
|
88
|
+
}
|
|
89
|
+
/** Workspace-relative path, or null when it is not ours to declare. */
|
|
90
|
+
function declarable(absolutePath, projectRoot, workspaceRoot) {
|
|
91
|
+
const wsRelative = (0, node_path_1.relative)(workspaceRoot, absolutePath).split(node_path_1.sep).join('/');
|
|
92
|
+
// Outside the workspace → cannot be expressed as an input.
|
|
93
|
+
if (wsRelative.startsWith('../') || wsRelative === '..')
|
|
94
|
+
return null;
|
|
95
|
+
// Inside node_modules → invalidated via the lockfile.
|
|
96
|
+
if (wsRelative.startsWith('node_modules/') ||
|
|
97
|
+
wsRelative.includes('/node_modules/'))
|
|
98
|
+
return null;
|
|
99
|
+
// Inside the project → covered by `default`.
|
|
100
|
+
if (wsRelative === projectRoot || wsRelative.startsWith(`${projectRoot}/`))
|
|
101
|
+
return null;
|
|
102
|
+
return wsRelative;
|
|
103
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nx/vitest",
|
|
3
3
|
"description": "The Nx Plugin for Vitest to enable fast unit testing with Vitest.",
|
|
4
|
-
"version": "23.3.0-beta.
|
|
4
|
+
"version": "23.3.0-beta.5",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist",
|
|
@@ -78,16 +78,16 @@
|
|
|
78
78
|
"executors": "./executors.json",
|
|
79
79
|
"generators": "./generators.json",
|
|
80
80
|
"dependencies": {
|
|
81
|
+
"@nx/devkit": "23.3.0-beta.5",
|
|
82
|
+
"@nx/js": "23.3.0-beta.5",
|
|
81
83
|
"tslib": "^2.3.0",
|
|
82
84
|
"semver": "^7.6.3",
|
|
83
|
-
"@phenomnomnominal/tsquery": "~6.2.0"
|
|
84
|
-
"@nx/devkit": "23.3.0-beta.3",
|
|
85
|
-
"@nx/js": "23.3.0-beta.3"
|
|
85
|
+
"@phenomnomnominal/tsquery": "~6.2.0"
|
|
86
86
|
},
|
|
87
87
|
"peerDependencies": {
|
|
88
|
+
"@nx/eslint": "23.3.0-beta.5",
|
|
88
89
|
"vitest": "^3.0.0 || ^4.0.0 || ^5.0.0",
|
|
89
|
-
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
|
|
90
|
-
"@nx/eslint": "23.3.0-beta.3"
|
|
90
|
+
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
|
|
91
91
|
},
|
|
92
92
|
"peerDependenciesMeta": {
|
|
93
93
|
"@nx/eslint": {
|
|
@@ -101,6 +101,6 @@
|
|
|
101
101
|
}
|
|
102
102
|
},
|
|
103
103
|
"devDependencies": {
|
|
104
|
-
"nx": "23.3.0-beta.
|
|
104
|
+
"nx": "23.3.0-beta.5"
|
|
105
105
|
}
|
|
106
106
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<p style="text-align: center;">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-dark.svg">
|
|
4
|
+
<img alt="Nx - Smart Monorepos · Fast Builds" src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-light.svg" width="100%">
|
|
5
|
+
</picture>
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
{{links}}
|
|
9
|
+
|
|
10
|
+
<hr>
|
|
11
|
+
|
|
12
|
+
# Nx: Smart Monorepos · Fast Builds
|
|
13
|
+
|
|
14
|
+
Get to green PRs in half the time. Nx optimizes your builds, scales your CI, and fixes failed PRs. Built for developers and AI agents.
|
|
15
|
+
|
|
16
|
+
This package is a [Vitest plugin for Nx](https://nx.dev/nx-api/vitest).
|
|
17
|
+
|
|
18
|
+
{{content}}
|