@nx/plugin 23.2.0-canary.20260717-dc22f7e → 23.2.0-canary.20260722-6a072a2

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.
@@ -1,4 +1,4 @@
1
- import { type GeneratorCallback, type Tree } from '@nx/devkit';
1
+ import { Tree, type GeneratorCallback } from '@nx/devkit';
2
2
  import type { Schema } from './schema';
3
3
  export declare function e2eProjectGenerator(host: Tree, schema: Schema): Promise<GeneratorCallback>;
4
4
  export declare function e2eProjectGeneratorInternal(host: Tree, schema: Schema): Promise<GeneratorCallback>;
@@ -9,6 +9,7 @@ const jest_1 = require("@nx/jest");
9
9
  const js_1 = require("@nx/js");
10
10
  const internal_2 = require("@nx/js/internal");
11
11
  const path_1 = require("path");
12
+ const versions_1 = require("../../utils/versions");
12
13
  async function normalizeOptions(host, options) {
13
14
  const linter = await (0, internal_2.normalizeLinterOption)(host, options.linter);
14
15
  const projectName = options.rootProject ? 'e2e' : `${options.pluginName}-e2e`;
@@ -112,6 +113,112 @@ async function addJest(host, options) {
112
113
  }
113
114
  return jestTask;
114
115
  }
116
+ async function addVitest(host, options) {
117
+ const projectConfiguration = {
118
+ name: options.projectName,
119
+ root: options.projectRoot,
120
+ projectType: 'application',
121
+ sourceRoot: `${options.projectRoot}/src`,
122
+ implicitDependencies: [options.pluginName],
123
+ };
124
+ if (options.isTsSolutionSetup) {
125
+ (0, devkit_1.writeJson)(host, (0, devkit_1.joinPathFragments)(options.projectRoot, 'package.json'), {
126
+ name: options.projectName,
127
+ version: '0.0.1',
128
+ private: true,
129
+ });
130
+ (0, devkit_1.updateProjectConfiguration)(host, options.projectName, projectConfiguration);
131
+ }
132
+ else {
133
+ projectConfiguration.targets = {};
134
+ (0, devkit_1.addProjectConfiguration)(host, options.projectName, projectConfiguration);
135
+ }
136
+ (0, devkit_1.ensurePackage)('@nx/vitest', versions_1.nxVersion);
137
+ // Use `require` rather than a dynamic `import()`: `ensurePackage` makes the
138
+ // on-demand-installed package resolvable via CJS resolution only, so a true
139
+ // ESM dynamic import cannot see the temp install.
140
+ const { configurationGenerator: vitestConfigurationGenerator, } = require('@nx/vitest/generators');
141
+ const vitestTask = await vitestConfigurationGenerator(host, {
142
+ project: options.projectName,
143
+ testTarget: 'e2e',
144
+ skipFormat: true,
145
+ addPlugin: options.addPlugin,
146
+ testEnvironment: 'node',
147
+ coverageProvider: 'none',
148
+ });
149
+ (0, internal_2.addLocalRegistryScripts)(host);
150
+ // Vitest has no `globalTeardown` option. Its `globalSetup` file must export
151
+ // both `setup` and `teardown`, so wire the local registry scripts through a
152
+ // wrapper module. Without the teardown, the verdaccio process started in
153
+ // setup outlives the test run.
154
+ const vitestGlobalSetupPath = 'tools/scripts/vitest-global-setup.ts';
155
+ if (!host.exists(vitestGlobalSetupPath)) {
156
+ host.write(vitestGlobalSetupPath, `/**
157
+ * This script adapts the local registry scripts to vitest's globalSetup
158
+ * lifecycle: vitest calls the exported setup and teardown functions around
159
+ * the test run.
160
+ */
161
+
162
+ export { default as setup } from './start-local-registry';
163
+ export { default as teardown } from './stop-local-registry';
164
+ `);
165
+ }
166
+ // Add globalSetup to vitest config
167
+ // Check for both .mts and .ts extensions (mts is checked first as it's the default created by @nx/vitest)
168
+ const vitestConfigExtensions = ['mts', 'ts'];
169
+ let vitestConfigPath;
170
+ for (const ext of vitestConfigExtensions) {
171
+ const configPath = (0, devkit_1.joinPathFragments)(options.projectRoot, `vitest.config.${ext}`);
172
+ if (host.exists(configPath)) {
173
+ vitestConfigPath = configPath;
174
+ break;
175
+ }
176
+ }
177
+ if (vitestConfigPath) {
178
+ let vitestConfig = host.read(vitestConfigPath, 'utf-8');
179
+ const globalSetupPath = (0, path_1.join)((0, devkit_1.offsetFromRoot)(options.projectRoot), vitestGlobalSetupPath);
180
+ // Insert globalSetup in the test config
181
+ // Look for 'test: {' and insert our properties right after the opening brace
182
+ const testConfigRegex = /(test:\s*\{\s*)/;
183
+ const match = testConfigRegex.exec(vitestConfig);
184
+ if (match) {
185
+ // Extract the indentation from the next line to maintain consistent formatting
186
+ const afterMatch = vitestConfig.slice(match.index + match[0].length);
187
+ const nextLineMatch = afterMatch.match(/\n(\s*)/);
188
+ const indent = nextLineMatch ? nextLineMatch[1] : ' ';
189
+ vitestConfig = vitestConfig.replace(testConfigRegex, `$1\n${indent}globalSetup: '${globalSetupPath}',`);
190
+ host.write(vitestConfigPath, vitestConfig);
191
+ }
192
+ else {
193
+ // If we can't find the test config block, log a warning
194
+ throw new Error(`Could not find test configuration block in ${vitestConfigPath}. Please manually add the globalSetup property.`);
195
+ }
196
+ }
197
+ else {
198
+ // This should not happen as the vitest configuration generator should create the config file
199
+ throw new Error(`Could not find Vitest config for project ${options.projectName} at ${options.projectRoot}`);
200
+ }
201
+ const project = (0, devkit_1.readProjectConfiguration)(host, options.projectName);
202
+ project.targets ??= {};
203
+ if (project.targets.e2e) {
204
+ const e2eTarget = project.targets.e2e;
205
+ project.targets.e2e = {
206
+ ...e2eTarget,
207
+ dependsOn: [`^build`],
208
+ options: {
209
+ ...e2eTarget.options,
210
+ pool: 'forks',
211
+ poolOptions: {
212
+ forks: {
213
+ singleFork: true,
214
+ },
215
+ },
216
+ },
217
+ };
218
+ (0, devkit_1.updateProjectConfiguration)(host, options.projectName, project);
219
+ }
220
+ return vitestTask;
221
+ }
115
222
  async function addLintingToApplication(tree, options) {
116
223
  const lintTask = await (0, eslint_1.lintProjectGenerator)(tree, {
117
224
  linter: options.linter,
@@ -119,7 +226,7 @@ async function addLintingToApplication(tree, options) {
119
226
  tsConfigPaths: [
120
227
  (0, devkit_1.joinPathFragments)(options.projectRoot, 'tsconfig.app.json'),
121
228
  ],
122
- unitTestRunner: 'jest',
229
+ unitTestRunner: options.testRunner ?? 'jest',
123
230
  skipFormat: true,
124
231
  setParserOptionsProject: false,
125
232
  addPlugin: options.addPlugin,
@@ -145,11 +252,19 @@ async function e2eProjectGeneratorInternal(host, schema) {
145
252
  const tasks = [];
146
253
  validatePlugin(host, schema.pluginName);
147
254
  const options = await normalizeOptions(host, schema);
255
+ // Default to jest if no testRunner is specified
256
+ options.testRunner = options.testRunner ?? 'jest';
148
257
  addFiles(host, options);
149
258
  tasks.push(await (0, js_1.setupVerdaccio)(host, {
150
259
  skipFormat: true,
151
260
  }));
152
- tasks.push(await addJest(host, options));
261
+ // Add test runner based on the testRunner option
262
+ if (options.testRunner === 'vitest') {
263
+ tasks.push(await addVitest(host, options));
264
+ }
265
+ else {
266
+ tasks.push(await addJest(host, options));
267
+ }
153
268
  updatePluginPackageJson(host, options);
154
269
  if (options.linter !== 'none') {
155
270
  tasks.push(await addLintingToApplication(host, {
@@ -8,14 +8,14 @@ describe('<%= pluginName %>', () => {
8
8
  beforeAll(() => {
9
9
  projectDirectory = createTestProject();
10
10
 
11
- // The plugin has been built and published to a local registry in the jest globalSetup
11
+ // The plugin has been built and published to a local registry in the globalSetup
12
12
  // Install the plugin built with the latest source code into the test repo
13
13
  execSync(`<%= packageManagerCommands.addDev %> <%= pluginPackageName %>@e2e`, {
14
14
  cwd: projectDirectory,
15
15
  stdio: 'inherit',
16
16
  env: process.env,
17
17
  });
18
- });
18
+ }, 30_000);
19
19
 
20
20
  afterAll(() => {
21
21
  if (projectDirectory) {
@@ -6,6 +6,7 @@ export interface Schema {
6
6
  projectDirectory?: string;
7
7
  pluginOutputPath?: string;
8
8
  jestConfig?: string;
9
+ testRunner?: 'jest' | 'vitest';
9
10
  linter?: Linter | LinterType;
10
11
  skipFormat?: boolean;
11
12
  rootProject?: boolean;
@@ -30,6 +30,12 @@
30
30
  "type": "string",
31
31
  "description": "Jest config file."
32
32
  },
33
+ "testRunner": {
34
+ "type": "string",
35
+ "enum": ["jest", "vitest"],
36
+ "description": "Test runner to use for the e2e tests.",
37
+ "default": "jest"
38
+ },
33
39
  "linter": {
34
40
  "description": "The tool to use for running lint checks.",
35
41
  "type": "string",
@@ -83,6 +83,11 @@ async function pluginGeneratorInternal(host, schema) {
83
83
  '@nx/devkit': nxVersion,
84
84
  }, {
85
85
  [options.unitTestRunner === 'vitest' ? '@nx/vitest' : '@nx/jest']: nxVersion,
86
+ ...(options.e2eTestRunner === 'vitest'
87
+ ? { '@nx/vitest': nxVersion }
88
+ : options.e2eTestRunner === 'jest'
89
+ ? { '@nx/jest': nxVersion }
90
+ : {}),
86
91
  '@nx/js': nxVersion,
87
92
  '@nx/plugin': nxVersion,
88
93
  }));
@@ -103,6 +108,7 @@ async function pluginGeneratorInternal(host, schema) {
103
108
  linter: options.linter,
104
109
  useProjectJson: options.useProjectJson,
105
110
  addPlugin: options.addPlugin,
111
+ testRunner: options.e2eTestRunner,
106
112
  }));
107
113
  }
108
114
  if (options.linter === 'eslint' && !options.skipLintChecks) {
@@ -7,7 +7,7 @@ export interface Schema {
7
7
  skipTsConfig?: boolean; // default is false
8
8
  skipFormat?: boolean; // default is false
9
9
  skipLintChecks?: boolean; // default is false
10
- e2eTestRunner?: 'jest' | 'none';
10
+ e2eTestRunner?: 'jest' | 'vitest' | 'none';
11
11
  e2eProjectDirectory?: string;
12
12
  tags?: string;
13
13
  unitTestRunner?: 'jest' | 'vitest' | 'none';
@@ -67,7 +67,7 @@
67
67
  },
68
68
  "e2eTestRunner": {
69
69
  "type": "string",
70
- "enum": ["jest", "none"],
70
+ "enum": ["jest", "vitest", "none"],
71
71
  "description": "Test runner to use for end to end (E2E) tests.",
72
72
  "default": "none"
73
73
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/plugin",
3
- "version": "23.2.0-canary.20260717-dc22f7e",
3
+ "version": "23.2.0-canary.20260722-6a072a2",
4
4
  "private": false,
5
5
  "description": "This plugin is used to create Nx plugins! It contains generators for generating common plugin features like generators, executors, migrations and more.",
6
6
  "repository": {
@@ -36,13 +36,21 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "tslib": "^2.3.0",
39
- "@nx/devkit": "23.2.0-canary.20260717-dc22f7e",
40
- "@nx/jest": "23.2.0-canary.20260717-dc22f7e",
41
- "@nx/js": "23.2.0-canary.20260717-dc22f7e",
42
- "@nx/eslint": "23.2.0-canary.20260717-dc22f7e"
39
+ "@nx/devkit": "23.2.0-canary.20260722-6a072a2",
40
+ "@nx/jest": "23.2.0-canary.20260722-6a072a2",
41
+ "@nx/js": "23.2.0-canary.20260722-6a072a2",
42
+ "@nx/eslint": "23.2.0-canary.20260722-6a072a2"
43
43
  },
44
44
  "devDependencies": {
45
- "nx": "23.2.0-canary.20260717-dc22f7e"
45
+ "nx": "23.2.0-canary.20260722-6a072a2"
46
+ },
47
+ "peerDependencies": {
48
+ "@nx/vitest": "23.2.0-canary.20260722-6a072a2"
49
+ },
50
+ "peerDependenciesMeta": {
51
+ "@nx/vitest": {
52
+ "optional": true
53
+ }
46
54
  },
47
55
  "publishConfig": {
48
56
  "access": "public"