@nx/cypress 24.0.0-beta.2 → 24.0.0-beta.3

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 (27) hide show
  1. package/dist/internal.d.ts +1 -1
  2. package/dist/src/executors/cypress/{cypress.impl.d.ts → schema.d.ts} +0 -8
  3. package/dist/src/executors/cypress/schema.js +2 -0
  4. package/dist/src/generators/component-configuration/component-configuration.js +7 -57
  5. package/dist/src/generators/component-configuration/schema.d.ts +0 -2
  6. package/dist/src/generators/configuration/configuration.d.ts +0 -1
  7. package/dist/src/generators/configuration/configuration.js +13 -64
  8. package/dist/src/generators/init/init.js +2 -45
  9. package/dist/src/generators/init/schema.d.ts +0 -1
  10. package/dist/src/migrations/update-23-3-0/adapt-query-command-overwrites.md +1 -1
  11. package/dist/src/migrations/update-23-3-0/ai-instructions-for-cypress-16.md +2 -2
  12. package/dist/src/migrations/update-24-0-0/finish-inferred-conversion.md +12 -0
  13. package/dist/src/migrations/update-24-0-0/remove-deprecated-executors.d.ts +6 -0
  14. package/dist/src/migrations/update-24-0-0/remove-deprecated-executors.js +10 -0
  15. package/dist/src/migrations/update-24-0-0/remove-deprecated-executors.md +61 -0
  16. package/dist/src/plugins/plugin.js +1 -1
  17. package/dist/src/utils/add-linter.d.ts +0 -1
  18. package/dist/src/utils/add-linter.js +0 -1
  19. package/executors.json +1 -7
  20. package/migrations.json +7 -0
  21. package/package.json +6 -7
  22. package/dist/src/executors/cypress/cypress.impl.js +0 -136
  23. package/dist/src/executors/cypress/schema.json +0 -186
  24. package/dist/src/utils/deprecation.d.ts +0 -3
  25. package/dist/src/utils/deprecation.js +0 -20
  26. package/dist/src/utils/start-dev-server.d.ts +0 -9
  27. package/dist/src/utils/start-dev-server.js +0 -165
@@ -2,4 +2,4 @@ export * from './src/utils/versions';
2
2
  export * from './src/utils/ct-helpers';
3
3
  export * from './src/utils/find-target-options';
4
4
  export * from './src/utils/config';
5
- export type { CypressExecutorOptions } from './src/executors/cypress/cypress.impl';
5
+ export type { CypressExecutorOptions } from './src/executors/cypress/schema';
@@ -1,4 +1,3 @@
1
- import { ExecutorContext } from '@nx/devkit';
2
1
  export type Json = {
3
2
  [k: string]: any;
4
3
  };
@@ -7,10 +6,6 @@ export interface CypressExecutorOptions extends Json {
7
6
  watch?: boolean;
8
7
  devServerTarget?: string;
9
8
  headed?: boolean;
10
- /**
11
- * @deprecated Cypress runs headless by default. Use the --watch flag to
12
- * control head/headless behavior instead. It will be removed in Nx v23.
13
- **/
14
9
  headless?: boolean;
15
10
  exit?: boolean;
16
11
  key?: string;
@@ -33,6 +28,3 @@ export interface CypressExecutorOptions extends Json {
33
28
  runnerUi?: boolean;
34
29
  autoCancelAfterFailures?: boolean | number;
35
30
  }
36
- export default function cypressExecutor(options: CypressExecutorOptions, context: ExecutorContext): Promise<{
37
- success: any;
38
- }>;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -8,21 +8,17 @@ const devkit_1 = require("@nx/devkit");
8
8
  const internal_1 = require("@nx/devkit/internal");
9
9
  const internal_2 = require("@nx/js/internal");
10
10
  const assert_supported_cypress_version_1 = require("../../utils/assert-supported-cypress-version");
11
- const deprecation_1 = require("../../utils/deprecation");
12
11
  const versions_1 = require("../../utils/versions");
13
12
  const base_setup_1 = require("../base-setup/base-setup");
14
- const init_1 = tslib_1.__importDefault(require("../init/init"));
13
+ const init_1 = tslib_1.__importStar(require("../init/init"));
15
14
  function componentConfigurationGenerator(tree, options) {
16
- return componentConfigurationGeneratorInternal(tree, {
17
- addPlugin: false,
18
- ...options,
19
- });
15
+ return componentConfigurationGeneratorInternal(tree, options);
20
16
  }
21
17
  async function componentConfigurationGeneratorInternal(tree, options) {
22
18
  (0, assert_supported_cypress_version_1.assertSupportedCypressVersion)(tree);
23
19
  (0, internal_2.assertNotUsingTsSolutionSetup)(tree, 'cypress', 'component-configuration');
24
20
  const tasks = [];
25
- const opts = normalizeOptions(tree, options);
21
+ const opts = normalizeOptions(options);
26
22
  if (opts.bundler === 'vite') {
27
23
  (0, versions_1.assertViteSupportsInstalledCypress)(tree);
28
24
  }
@@ -38,29 +34,18 @@ async function componentConfigurationGeneratorInternal(tree, options) {
38
34
  skipFormat: true,
39
35
  }));
40
36
  }
41
- const nxJson = (0, devkit_1.readNxJson)(tree);
42
- const hasPlugin = nxJson.plugins?.some((p) => typeof p === 'string'
43
- ? p === '@nx/cypress/plugin'
44
- : p.plugin === '@nx/cypress/plugin');
37
+ await (0, init_1.addPlugin)(tree, await (0, devkit_1.createProjectGraphAsync)(), false);
45
38
  const projectConfig = (0, devkit_1.readProjectConfiguration)(tree, opts.project);
46
39
  addProjectFiles(tree, projectConfig, opts);
47
- if (!hasPlugin || opts.addExplicitTargets) {
48
- (0, deprecation_1.warnCypressExecutorGenerating)();
49
- addTargetToProject(tree, projectConfig, opts);
50
- }
51
- updateNxJsonConfiguration(tree, hasPlugin);
40
+ updateNxJsonConfiguration(tree);
52
41
  updateTsConfigForComponentTesting(tree, projectConfig);
53
42
  if (!opts.skipFormat) {
54
43
  await (0, devkit_1.formatFiles)(tree);
55
44
  }
56
45
  return (0, devkit_1.runTasksInSerial)(...tasks);
57
46
  }
58
- function normalizeOptions(tree, options) {
59
- const nxJson = (0, devkit_1.readNxJson)(tree);
60
- const addPlugin = process.env.NX_ADD_PLUGINS !== 'false' &&
61
- nxJson.useInferencePlugins !== false;
47
+ function normalizeOptions(options) {
62
48
  return {
63
- addPlugin,
64
49
  ...options,
65
50
  framework: options.framework ?? null,
66
51
  directory: options.directory ?? 'cypress',
@@ -107,17 +92,7 @@ function addProjectFiles(tree, projectConfig, opts) {
107
92
  ext: '',
108
93
  });
109
94
  }
110
- function addTargetToProject(tree, projectConfig, opts) {
111
- projectConfig.targets['component-test'] = {
112
- executor: '@nx/cypress:cypress',
113
- options: {
114
- cypressConfig: (0, devkit_1.joinPathFragments)(projectConfig.root, 'cypress.config.ts'),
115
- testingType: 'component',
116
- },
117
- };
118
- (0, devkit_1.updateProjectConfiguration)(tree, opts.project, projectConfig);
119
- }
120
- function updateNxJsonConfiguration(tree, hasPlugin) {
95
+ function updateNxJsonConfiguration(tree) {
121
96
  const nxJson = (0, devkit_1.readNxJson)(tree);
122
97
  const productionFileSet = nxJson.namedInputs?.production;
123
98
  if (productionFileSet) {
@@ -128,31 +103,6 @@ function updateNxJsonConfiguration(tree, hasPlugin) {
128
103
  '!{projectRoot}/cypress.config.[jt]s',
129
104
  ]));
130
105
  }
131
- if (!hasPlugin) {
132
- const cacheableOperations = nxJson.tasksRunnerOptions?.default?.options?.cacheableOperations;
133
- if (cacheableOperations &&
134
- !cacheableOperations.includes('component-test')) {
135
- cacheableOperations.push('component-test');
136
- }
137
- // Either a `target: 'component-test'` default or a default keyed on
138
- // the executor we're scaffolding will apply to the new target — pick
139
- // the more specific (target-keyed) when both are present.
140
- const existingForTarget = (0, internal_1.findTargetDefault)(nxJson.targetDefaults, {
141
- target: 'component-test',
142
- });
143
- const existingForExecutor = (0, internal_1.findTargetDefault)(nxJson.targetDefaults, {
144
- executor: '@nx/cypress:cypress',
145
- });
146
- const existing = existingForTarget ?? existingForExecutor;
147
- (0, internal_1.upsertTargetDefault)(tree, nxJson, {
148
- target: 'component-test',
149
- cache: existing?.cache ?? true,
150
- inputs: existing?.inputs ?? [
151
- 'default',
152
- productionFileSet ? '^production' : '^default',
153
- ],
154
- });
155
- }
156
106
  (0, devkit_1.updateNxJson)(tree, nxJson);
157
107
  }
158
108
  function updateTsConfigForComponentTesting(tree, projectConfig) {
@@ -4,12 +4,10 @@ export interface CypressComponentConfigurationSchema {
4
4
  directory?: string;
5
5
  bundler?: 'webpack' | 'vite';
6
6
  jsx?: boolean;
7
- addPlugin?: boolean;
8
7
  skipPackageJson?: boolean;
9
8
 
10
9
  /**
11
10
  * @internal
12
11
  */
13
- addExplicitTargets?: boolean;
14
12
  framework?: 'next';
15
13
  }
@@ -21,7 +21,6 @@ export interface CypressE2EConfigSchema {
21
21
  webServerCommands?: Record<string, string>;
22
22
  ciWebServerCommand?: string;
23
23
  ciBaseUrl?: string;
24
- addPlugin?: boolean;
25
24
  }
26
25
  export declare function configurationGenerator(tree: Tree, options: CypressE2EConfigSchema): Promise<GeneratorCallback>;
27
26
  export declare function configurationGeneratorInternal(tree: Tree, options: CypressE2EConfigSchema): Promise<GeneratorCallback>;
@@ -11,15 +11,11 @@ const path_1 = require("path");
11
11
  const add_linter_1 = require("../../utils/add-linter");
12
12
  const assert_supported_cypress_version_1 = require("../../utils/assert-supported-cypress-version");
13
13
  const config_1 = require("../../utils/config");
14
- const deprecation_1 = require("../../utils/deprecation");
15
14
  const versions_1 = require("../../utils/versions");
16
15
  const base_setup_1 = require("../base-setup/base-setup");
17
16
  const init_1 = tslib_1.__importStar(require("../init/init"));
18
17
  function configurationGenerator(tree, options) {
19
- return configurationGeneratorInternal(tree, {
20
- addPlugin: false,
21
- ...options,
22
- });
18
+ return configurationGeneratorInternal(tree, options);
23
19
  }
24
20
  async function configurationGeneratorInternal(tree, options) {
25
21
  (0, assert_supported_cypress_version_1.assertSupportedCypressVersion)(tree);
@@ -33,18 +29,10 @@ async function configurationGeneratorInternal(tree, options) {
33
29
  skipFormat: true,
34
30
  }));
35
31
  }
36
- else if (opts.addPlugin) {
32
+ else {
37
33
  await (0, init_1.addPlugin)(tree, projectGraph, false);
38
34
  }
39
- const nxJson = (0, devkit_1.readNxJson)(tree);
40
- const hasPlugin = nxJson.plugins?.some((p) => typeof p === 'string'
41
- ? p === '@nx/cypress/plugin'
42
- : p.plugin === '@nx/cypress/plugin');
43
- await addFiles(tree, opts, projectGraph, hasPlugin);
44
- if (!hasPlugin) {
45
- (0, deprecation_1.warnCypressExecutorGenerating)();
46
- addTarget(tree, opts, projectGraph);
47
- }
35
+ await addFiles(tree, opts, projectGraph);
48
36
  const projectTsConfigPath = (0, devkit_1.joinPathFragments)(opts.projectRoot, 'tsconfig.json');
49
37
  if (tree.exists(projectTsConfigPath)) {
50
38
  (0, devkit_1.updateJson)(tree, projectTsConfigPath, (json) => {
@@ -130,10 +118,6 @@ In this case you need to provide a devServerTarget,'<projectName>:<targetName>[:
130
118
  if (!options.baseUrl && !devServerTarget) {
131
119
  throw new Error('Either baseUrl or devServerTarget must be provided');
132
120
  }
133
- const nxJson = (0, devkit_1.readNxJson)(tree);
134
- options.addPlugin ??=
135
- process.env.NX_ADD_PLUGINS !== 'false' &&
136
- nxJson.useInferencePlugins !== false;
137
121
  return {
138
122
  ...options,
139
123
  bundler: options.bundler ?? 'webpack',
@@ -166,7 +150,7 @@ async function promptForMissingServeData(projectName) {
166
150
  baseUrl: `http://localhost:${port}`,
167
151
  };
168
152
  }
169
- async function addFiles(tree, options, projectGraph, hasPlugin) {
153
+ async function addFiles(tree, options, projectGraph) {
170
154
  const projectConfig = (0, devkit_1.readProjectConfiguration)(tree, options.project);
171
155
  const hasTsConfig = tree.exists((0, devkit_1.joinPathFragments)(projectConfig.root, 'tsconfig.json'));
172
156
  const offsetFromProjectRoot = options.directory
@@ -194,12 +178,12 @@ async function addFiles(tree, options, projectGraph, hasPlugin) {
194
178
  let webServerCommands;
195
179
  let ciWebServerCommand;
196
180
  let ciBaseUrl;
197
- if (hasPlugin && options.webServerCommands && options.ciWebServerCommand) {
181
+ if (options.webServerCommands && options.ciWebServerCommand) {
198
182
  webServerCommands = options.webServerCommands;
199
183
  ciWebServerCommand = options.ciWebServerCommand;
200
184
  ciBaseUrl = options.ciBaseUrl;
201
185
  }
202
- else if (hasPlugin && options.devServerTarget) {
186
+ else if (options.devServerTarget) {
203
187
  webServerCommands = {};
204
188
  webServerCommands.default = 'nx run ' + options.devServerTarget;
205
189
  const parsedTarget = (0, devkit_1.parseTargetString)(options.devServerTarget, projectGraph);
@@ -214,6 +198,13 @@ async function addFiles(tree, options, projectGraph, hasPlugin) {
214
198
  ciWebServerCommand = `nx run ${parsedTarget.project}:serve-static`;
215
199
  }
216
200
  }
201
+ if (typeof options.port === 'number') {
202
+ for (const name of Object.keys(webServerCommands ?? {})) {
203
+ webServerCommands[name] += ` --port=${options.port}`;
204
+ }
205
+ if (ciWebServerCommand)
206
+ ciWebServerCommand += ` --port=${options.port}`;
207
+ }
217
208
  const e2ePresetOptions = {
218
209
  cypressDir: options.directory,
219
210
  bundler: options.bundler === 'vite' ? 'vite' : undefined,
@@ -242,48 +233,6 @@ async function addFiles(tree, options, projectGraph, hasPlugin) {
242
233
  (0, devkit_1.toJS)(tree);
243
234
  }
244
235
  }
245
- function addTarget(tree, opts, projectGraph) {
246
- const projectConfig = (0, devkit_1.readProjectConfiguration)(tree, opts.project);
247
- projectConfig.targets ??= {};
248
- projectConfig.targets.e2e = {
249
- executor: '@nx/cypress:cypress',
250
- options: {
251
- cypressConfig: (0, devkit_1.joinPathFragments)(projectConfig.root, `cypress.config.${opts.js ? 'js' : 'ts'}`),
252
- testingType: 'e2e',
253
- },
254
- };
255
- if (opts.devServerTarget) {
256
- const parsedTarget = (0, devkit_1.parseTargetString)(opts.devServerTarget, projectGraph);
257
- projectConfig.targets.e2e.options = {
258
- ...projectConfig.targets.e2e.options,
259
- devServerTarget: opts.devServerTarget,
260
- port: opts.port,
261
- };
262
- const devServerProjectConfig = (0, devkit_1.readProjectConfiguration)(tree, parsedTarget.project);
263
- // Add production e2e target if serve target is found
264
- if (parsedTarget.configuration !== 'production' &&
265
- devServerProjectConfig.targets?.[parsedTarget.target]?.configurations?.['production']) {
266
- projectConfig.targets.e2e.configurations ??= {};
267
- projectConfig.targets.e2e.configurations['production'] = {
268
- devServerTarget: `${parsedTarget.project}:${parsedTarget.target}:production`,
269
- };
270
- }
271
- // Add ci/static e2e target if serve target is found
272
- if (devServerProjectConfig.targets?.['serve-static']) {
273
- projectConfig.targets.e2e.configurations ??= {};
274
- projectConfig.targets.e2e.configurations.ci = {
275
- devServerTarget: `${parsedTarget.project}:serve-static`,
276
- };
277
- }
278
- }
279
- else if (opts.baseUrl) {
280
- projectConfig.targets.e2e.options = {
281
- ...projectConfig.targets.e2e.options,
282
- baseUrl: opts.baseUrl,
283
- };
284
- }
285
- (0, devkit_1.updateProjectConfiguration)(tree, opts.project, projectConfig);
286
- }
287
236
  function createPackageJson(tree, options) {
288
237
  const projectConfig = (0, devkit_1.readProjectConfiguration)(tree, options.project);
289
238
  const packageJsonPath = (0, devkit_1.joinPathFragments)(projectConfig.root, 'package.json');
@@ -8,40 +8,6 @@ const devkit_1 = require("@nx/devkit");
8
8
  const plugin_1 = require("../../plugins/plugin");
9
9
  const assert_supported_cypress_version_1 = require("../../utils/assert-supported-cypress-version");
10
10
  const versions_1 = require("../../utils/versions");
11
- function setupE2ETargetDefaults(tree) {
12
- const nxJson = (0, devkit_1.readNxJson)(tree);
13
- if (!nxJson.namedInputs) {
14
- return;
15
- }
16
- // E2e targets depend on all their project's sources + production sources of dependencies
17
- const productionFileSet = !!nxJson.namedInputs?.production;
18
- const existing = findExistingE2eDefault(nxJson.targetDefaults);
19
- const patch = {};
20
- if (existing?.cache === undefined)
21
- patch.cache = true;
22
- if (existing?.inputs === undefined) {
23
- patch.inputs = ['default', productionFileSet ? '^production' : '^default'];
24
- }
25
- if (Object.keys(patch).length > 0) {
26
- (0, internal_1.upsertTargetDefault)(tree, nxJson, { target: 'e2e', ...patch });
27
- (0, devkit_1.updateNxJson)(tree, nxJson);
28
- }
29
- }
30
- function findExistingE2eDefault(td) {
31
- if (!td)
32
- return undefined;
33
- const value = td['e2e'];
34
- if (value === undefined)
35
- return undefined;
36
- if (Array.isArray(value)) {
37
- const found = value.find((e) => e.filter === undefined);
38
- if (!found)
39
- return undefined;
40
- const { filter: _f, ...rest } = found;
41
- return rest;
42
- }
43
- return value;
44
- }
45
11
  function updateDependencies(tree, options) {
46
12
  const tasks = [];
47
13
  tasks.push((0, devkit_1.removeDependenciesFromPackageJson)(tree, ['@nx/cypress'], []));
@@ -86,21 +52,12 @@ function updateProductionFileset(tree) {
86
52
  (0, devkit_1.updateNxJson)(tree, nxJson);
87
53
  }
88
54
  async function cypressInitGenerator(tree, options) {
89
- return cypressInitGeneratorInternal(tree, { addPlugin: false, ...options });
55
+ return cypressInitGeneratorInternal(tree, options);
90
56
  }
91
57
  async function cypressInitGeneratorInternal(tree, options) {
92
58
  (0, assert_supported_cypress_version_1.assertSupportedCypressVersion)(tree);
93
59
  updateProductionFileset(tree);
94
- const nxJson = (0, devkit_1.readNxJson)(tree);
95
- options.addPlugin ??=
96
- process.env.NX_ADD_PLUGINS !== 'false' &&
97
- nxJson.useInferencePlugins !== false;
98
- if (options.addPlugin) {
99
- await addPlugin(tree, await (0, devkit_1.createProjectGraphAsync)(), options.updatePackageScripts);
100
- }
101
- else {
102
- setupE2ETargetDefaults(tree);
103
- }
60
+ await addPlugin(tree, await (0, devkit_1.createProjectGraphAsync)(), options.updatePackageScripts);
104
61
  let installTask = () => { };
105
62
  if (!options.skipPackageJson) {
106
63
  installTask = updateDependencies(tree, options);
@@ -3,5 +3,4 @@ export interface Schema {
3
3
  skipPackageJson?: boolean;
4
4
  keepExistingVersions?: boolean;
5
5
  updatePackageScripts?: boolean;
6
- addPlugin?: boolean;
7
6
  }
@@ -8,7 +8,7 @@ Cypress 16 turned `cy.getCookie()`, `cy.getCookies()`, `cy.getAllCookies()`, `cy
8
8
 
9
9
  Confirm both conditions before changing anything. If either fails, make no changes and stop.
10
10
 
11
- 1. `<files_changed>` or `<advisory_context>` lists at least one file with a renamed `Cypress.Commands.overwriteQuery()` call. Search the workspace for `Cypress.Commands.overwriteQuery(` if the wrapper sections are absent.
11
+ 1. `impl.changes` or `impl.agentContext` in the step's `nx_migrate_prompt` block lists at least one file with a renamed `Cypress.Commands.overwriteQuery()` call. Search the workspace for `Cypress.Commands.overwriteQuery(` if those fields are absent.
12
12
  2. The workspace is on Cypress 16 or later (`cypress` in `package.json`).
13
13
 
14
14
  ## Step 1: Verify the pre-pass
@@ -9,7 +9,7 @@ Migrate the workspace's Cypress projects from Cypress 15 to 16. Cypress 16 remov
9
9
  1. Confirm `cypress` in `package.json` resolves to 16 or later. If it does not, make no changes and stop.
10
10
  2. Confirm Node is 22, 24, or 26+ (`node -v`). Cypress 16 declares `engines` `^22.0.0 || ^24.0.0 || >=26.0.0` and refuses to install on Node 20 or 25.
11
11
  3. List the Cypress projects: every project with a `cypress.config.{ts,js,mjs,cjs}` file. Search their spec and support files for the strings in the steps below.
12
- 4. Review `<advisory_context>` from the deterministic migrations: each entry (a non-literal `experimentalFastVisibility`, a removed `execTimeout`, a removed `experimentalSourceRewriting: true`, an unresolved spread, a renamed `overwriteQuery` callback) is pending work for the matching step.
12
+ 4. When the step's `nx_migrate_prompt` block has `impl.agentContext`, review the deterministic migrations' entries in it: each entry (a non-literal `experimentalFastVisibility`, a removed `execTimeout`, a removed `experimentalSourceRewriting: true`, an unresolved spread, a renamed `overwriteQuery` callback) is pending work for the matching step.
13
13
 
14
14
  ## Step 1: Replace `Cypress.env()`
15
15
 
@@ -139,7 +139,7 @@ it('renders on mobile', { viewportWidth: 375 }, () => {
139
139
 
140
140
  `cy.getCookie()`, `cy.getCookies()`, `cy.getAllCookies()`, `cy.getAllLocalStorage()` and `cy.getAllSessionStorage()` are queries: they retry assertions chained with `.should()` and follow `defaultCommandTimeout` (4000ms) instead of `responseTimeout` (30000ms). A `.then()` callback still runs once and does not retry. Change nothing unless a test fails; then move the assertion from `.then()` onto `.should()` so it retries, or pass `{ timeout }` when a cookie read needs longer than 4 seconds.
141
141
 
142
- The deterministic migration renamed `Cypress.Commands.overwrite()` to `overwriteQuery()` for those five names; `<advisory_context>` lists each renamed call. Do not re-apply the rename. Adapt each renamed callback to the query contract: `overwriteQuery` calls the callback with the command as `this`, the original query function as the first argument, and then the arguments the test passed, and the callback must return a function that takes the subject. Cypress calls that returned function, possibly many times while retrying, to compute the result. It must not return a chainable, call `cy.*` commands or use `.then()`. The original query reads `this` as well, so every callback must be a `function` expression that calls `originalFn.call(this, ...)`. An arrow callback, or a plain `originalFn(...)` call, fails to typecheck (TS2684, `QueryFnWithOriginalFn` declares `this: Command`) and throws at runtime. Convert arrow callbacks even when they only forward their arguments:
142
+ The deterministic migration renamed `Cypress.Commands.overwrite()` to `overwriteQuery()` for those five names; `impl.agentContext` lists each renamed call when present, and otherwise search the workspace for `Cypress.Commands.overwriteQuery(`. Do not re-apply the rename. Adapt each renamed callback to the query contract: `overwriteQuery` calls the callback with the command as `this`, the original query function as the first argument, and then the arguments the test passed, and the callback must return a function that takes the subject. Cypress calls that returned function, possibly many times while retrying, to compute the result. It must not return a chainable, call `cy.*` commands or use `.then()`. The original query reads `this` as well, so every callback must be a `function` expression that calls `originalFn.call(this, ...)`. An arrow callback, or a plain `originalFn(...)` call, fails to typecheck (TS2684, `QueryFnWithOriginalFn` declares `this: Command`) and throws at runtime. Convert arrow callbacks even when they only forward their arguments:
143
143
 
144
144
  **Before:**
145
145
 
@@ -0,0 +1,12 @@
1
+ # Replace removed @nx/cypress executors with inferred tasks
2
+
3
+ Nx 24 removed `@nx/cypress:cypress` (including old `@nrwl` aliases).
4
+
5
+ Before changing anything, check for leftover work: a project target, a package.json target or a `targetDefaults` entry still uses a removed executor, or the advisory context reports a conversion error. If none applies, make no changes and stop. Treat each advisory-context item as pending work.
6
+
7
+ 1. Inspect all affected projects and workspace defaults. Repair config-loading errors first. Do not restore executor registrations or install older Nx packages.
8
+ 2. Preserve Cypress config, browser choice, testing type, CI options and dev-server orchestration. Ensure webServerCommands/target dependencies start the intended app.
9
+ 3. Register `@nx/cypress/plugin` with options/includes that preserve custom target names and mixed inferred/explicit projects. Replace remaining removed-executor targets, including those in package.json and filtered targetDefaults. Transfer their behavior before removing obsolete defaults. Preserve configurations, dependencies, cache inputs/outputs and environment behavior. Do not silently delete unsupported options or unrelated targets. Migrate old @nrwl aliases too.
10
+ 4. Run project graph creation with the daemon and graph cache disabled (`NX_DAEMON=false NX_CACHE_PROJECT_GRAPH=false`), using the workspace package manager. Inspect resolved project targets and verify each migrated entrypoint exists and is inferred (a thin alias to an inferred target is acceptable). Resolve graph errors before completing this migration, even if task validation was skipped. Configs must load for subsequent migrations.
11
+ 5. By default run relevant real builds/tests with task cache disabled, including alternate configurations. Honor an existing preference to skip expensive task validation; otherwise offer that option when validation is slow. Skipping expensive tasks never skips conversion or the graph check. Report each skipped task as unverified. Never weaken assertions or change application behavior to make checks pass.
12
+ 6. Report converted projects, graph validation, task results and any skipped checks. Unconverted targets or a broken graph mean the migration is incomplete. Investigate task failures and distinguish missing environment prerequisites from conversion regressions; do not claim a working workspace with unresolved regressions.
@@ -0,0 +1,6 @@
1
+ import type { Tree } from '@nx/devkit';
2
+ export default function update(tree: Tree): Promise<{
3
+ nextSteps?: string[];
4
+ agentContext?: string[];
5
+ skipAgentic?: boolean;
6
+ }>;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = update;
4
+ const internal_1 = require("@nx/devkit/internal");
5
+ function update(tree) {
6
+ return (0, internal_1.migrateRemovedExecutors)(tree, ['@nx/cypress:cypress', '@nrwl/cypress:cypress'], async (tree, options) => {
7
+ const { convertToInferred } = require('../../generators/convert-to-inferred/convert-to-inferred');
8
+ return convertToInferred(tree, options);
9
+ });
10
+ }
@@ -0,0 +1,61 @@
1
+ #### Convert `@nx/cypress:cypress` targets to inferred tasks
2
+
3
+ Nx 24 removes the `@nx/cypress:cypress` executor and its `@nrwl/cypress:cypress` alias. The migration registers `@nx/cypress/plugin` in `nx.json` and converts the targets that use those executors to inferred tasks. It moves the dev server and `baseUrl` settings into `cypress.config.*`. A target with nothing left to override is removed from project configuration. When several projects share the same remaining options, those options move to a `targetDefaults` entry scoped to the plugin. `targetDefaults` entries keyed by a removed executor are removed once no target uses that executor.
4
+
5
+ The migration lists the targets the converter cannot convert safely. In agentic runs, an AI prompt finishes them and validates the project graph.
6
+
7
+ #### Sample code changes
8
+
9
+ ##### Before
10
+
11
+ ```json title="myapp-e2e/project.json"
12
+ {
13
+ "name": "myapp-e2e",
14
+ "targets": {
15
+ "e2e": {
16
+ "executor": "@nx/cypress:cypress",
17
+ "options": {
18
+ "cypressConfig": "myapp-e2e/cypress.config.ts",
19
+ "testingType": "e2e",
20
+ "devServerTarget": "myapp:serve"
21
+ },
22
+ "configurations": {
23
+ "ci": {
24
+ "devServerTarget": "myapp:static-serve"
25
+ }
26
+ }
27
+ }
28
+ }
29
+ }
30
+ ```
31
+
32
+ ##### After
33
+
34
+ ```json title="myapp-e2e/project.json"
35
+ {
36
+ "name": "myapp-e2e",
37
+ "targets": {
38
+ "e2e": {
39
+ "options": {
40
+ "config-file": "./cypress.config.ts"
41
+ }
42
+ }
43
+ }
44
+ }
45
+ ```
46
+
47
+ ```json title="nx.json"
48
+ {
49
+ "plugins": [
50
+ {
51
+ "plugin": "@nx/cypress/plugin",
52
+ "options": {
53
+ "ciTargetName": "e2e-ci",
54
+ "componentTestingTargetName": "component-test",
55
+ "openTargetName": "open-cypress",
56
+ "targetName": "e2e"
57
+ }
58
+ }
59
+ ]
60
+ }
61
+ ```
@@ -297,7 +297,7 @@ async function buildCypressTargets(configFilePath, projectRoot, options, context
297
297
  else {
298
298
  targets[targetName].parallelism = false;
299
299
  }
300
- targets[targetName] = (0, internal_1.applyTestFileDirectives)(targets[targetName], context.workspaceRoot, file, ['depends-on', 'ultracache']);
300
+ targets[targetName] = (0, internal_1.applyTestFileDirectives)(targets[targetName], context.workspaceRoot, file, { ignoreServerInputs: true });
301
301
  }
302
302
  targets[options.ciTargetName] = {
303
303
  executor: 'nx:noop',
@@ -21,6 +21,5 @@ export interface CyLinterOptions {
21
21
  * This is useful when adding linting to a brand new project vs an existing one
22
22
  **/
23
23
  overwriteExisting?: boolean;
24
- addPlugin?: boolean;
25
24
  }
26
25
  export declare function addLinterToCyProject(tree: Tree, options: CyLinterOptions): Promise<GeneratorCallback>;
@@ -24,7 +24,6 @@ async function addLinterToCyProject(tree, options) {
24
24
  enableTypedLinting,
25
25
  skipPackageJson: options.skipPackageJson,
26
26
  rootProject: options.rootProject,
27
- addPlugin: options.addPlugin,
28
27
  }));
29
28
  }
30
29
  // Everything below configures ESLint — predefined configs, `extends`, ignore
package/executors.json CHANGED
@@ -1,9 +1,3 @@
1
1
  {
2
- "executors": {
3
- "cypress": {
4
- "implementation": "./dist/src/executors/cypress/cypress.impl",
5
- "schema": "./dist/src/executors/cypress/schema.json",
6
- "description": "Run Cypress E2E tests."
7
- }
8
- }
2
+ "executors": {}
9
3
  }
package/migrations.json CHANGED
@@ -108,6 +108,13 @@
108
108
  "implementation": "./dist/src/migrations/update-23-3-0/upgrade-to-cypress-16",
109
109
  "prompt": "./dist/src/migrations/update-23-3-0/ai-instructions-for-cypress-16.md",
110
110
  "documentation": "./dist/src/migrations/update-23-3-0/upgrade-to-cypress-16.md"
111
+ },
112
+ "remove-deprecated-executors": {
113
+ "version": "24.0.0-beta.3",
114
+ "description": "Converts targets using the removed `@nx/cypress` executors to inferred tasks; an AI prompt finishes the targets the converter cannot convert safely and validates the project graph.",
115
+ "implementation": "./dist/src/migrations/update-24-0-0/remove-deprecated-executors",
116
+ "prompt": "./dist/src/migrations/update-24-0-0/finish-inferred-conversion.md",
117
+ "documentation": "./dist/src/migrations/update-24-0-0/remove-deprecated-executors.md"
111
118
  }
112
119
  },
113
120
  "packageJsonUpdates": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/cypress",
3
- "version": "24.0.0-beta.2",
3
+ "version": "24.0.0-beta.3",
4
4
  "private": false,
5
5
  "type": "commonjs",
6
6
  "files": [
@@ -10,7 +10,7 @@
10
10
  "executors.json",
11
11
  "generators.json"
12
12
  ],
13
- "description": "The Nx Plugin for Cypress contains executors and generators allowing your workspace to use the powerful Cypress integration testing capabilities.",
13
+ "description": "The Nx Plugin for Cypress infers tasks and provides generators allowing your workspace to use the powerful Cypress integration testing capabilities.",
14
14
  "repository": {
15
15
  "type": "git",
16
16
  "url": "https://github.com/nrwl/nx.git",
@@ -84,17 +84,16 @@
84
84
  "supportsOptionalMigrations": true
85
85
  },
86
86
  "dependencies": {
87
- "@nx/devkit": "24.0.0-beta.2",
88
- "@nx/eslint": "24.0.0-beta.2",
89
- "@nx/js": "24.0.0-beta.2",
87
+ "@nx/devkit": "24.0.0-beta.3",
88
+ "@nx/eslint": "24.0.0-beta.3",
89
+ "@nx/js": "24.0.0-beta.3",
90
90
  "@phenomnomnominal/tsquery": "~6.2.0",
91
- "detect-port": "^2.1.0",
92
91
  "semver": "^7.6.3",
93
92
  "tree-kill": "^1.2.2",
94
93
  "tslib": "^2.3.0"
95
94
  },
96
95
  "devDependencies": {
97
- "nx": "24.0.0-beta.2"
96
+ "nx": "24.0.0-beta.3"
98
97
  },
99
98
  "peerDependencies": {
100
99
  "cypress": ">= 13 < 17"
@@ -1,136 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.default = cypressExecutor;
4
- const devkit_1 = require("@nx/devkit");
5
- const fs_1 = require("fs");
6
- const path_1 = require("path");
7
- const ct_helpers_1 = require("../../utils/ct-helpers");
8
- const deprecation_1 = require("../../utils/deprecation");
9
- const start_dev_server_1 = require("../../utils/start-dev-server");
10
- const Cypress = require('cypress'); // @NOTE: Importing via ES6 messes the whole test dependencies.
11
- async function cypressExecutor(options, context) {
12
- (0, deprecation_1.warnCypressExecutorDeprecation)();
13
- options = normalizeOptions(options, context);
14
- // this is used by cypress component testing presets to build the executor contexts with the correct configuration options.
15
- process.env.NX_CYPRESS_TARGET_CONFIGURATION = context.configurationName;
16
- let success;
17
- const generatorInstance = (0, start_dev_server_1.startDevServer)(options, context);
18
- for await (const devServerValues of generatorInstance) {
19
- try {
20
- success = await runCypress(devServerValues.baseUrl, {
21
- ...options,
22
- portLockFilePath: devServerValues.portLockFilePath,
23
- });
24
- if (!options.watch) {
25
- generatorInstance.return();
26
- break;
27
- }
28
- }
29
- catch (e) {
30
- devkit_1.logger.error(e.message);
31
- success = false;
32
- if (!options.watch)
33
- break;
34
- }
35
- }
36
- return { success };
37
- }
38
- function normalizeOptions(options, context) {
39
- options.env = options.env || {};
40
- options.testingType ??= 'e2e';
41
- if (options.testingType === 'component') {
42
- const project = context?.projectGraph?.nodes?.[context.projectName];
43
- if (project?.data?.root) {
44
- options.ctTailwindPath = (0, ct_helpers_1.getTempTailwindPath)(context);
45
- }
46
- }
47
- return options;
48
- }
49
- /**
50
- * @whatItDoes Initialize the Cypress test runner with the provided project configuration.
51
- * By default, Cypress will run tests from the CLI without the GUI and provide directly the results in the console output.
52
- * If `watch` is `true`: Open Cypress in the interactive GUI to interact directly with the application.
53
- */
54
- async function runCypress(baseUrl, opts) {
55
- // Cypress expects the folder where a cypress config is present
56
- const projectFolderPath = (0, path_1.dirname)(opts.cypressConfig);
57
- const options = {
58
- project: projectFolderPath,
59
- configFile: (0, path_1.basename)(opts.cypressConfig),
60
- };
61
- // If not, will use the `baseUrl` normally from `cypress.json`
62
- if (baseUrl) {
63
- options.config = { baseUrl };
64
- }
65
- if (opts.browser) {
66
- options.browser = opts.browser;
67
- }
68
- if (opts.env) {
69
- options.env = {
70
- ...options.env,
71
- ...opts.env,
72
- };
73
- }
74
- if (opts.spec) {
75
- options.spec = opts.spec;
76
- }
77
- options.tag = opts.tag;
78
- options.exit = opts.exit;
79
- options.headed = opts.headed;
80
- options.runnerUi = opts.runnerUi;
81
- if (opts.headless) {
82
- options.headless = opts.headless;
83
- }
84
- options.record = opts.record;
85
- options.key = opts.key;
86
- options.parallel = opts.parallel;
87
- options.ciBuildId = opts.ciBuildId?.toString();
88
- options.group = opts.group;
89
- // renamed in cy 10
90
- options.config ??= {};
91
- options.config[opts.testingType] = {
92
- excludeSpecPattern: opts.ignoreTestFiles,
93
- };
94
- if (opts.reporter) {
95
- options.reporter = opts.reporter;
96
- }
97
- if (opts.reporterOptions) {
98
- options.reporterOptions = opts.reporterOptions;
99
- }
100
- if (opts.quiet) {
101
- options.quiet = opts.quiet;
102
- }
103
- if (opts.autoCancelAfterFailures !== undefined) {
104
- options.autoCancelAfterFailures = opts.autoCancelAfterFailures;
105
- }
106
- options.testingType = opts.testingType;
107
- const result = await (opts.watch
108
- ? Cypress.open(options)
109
- : Cypress.run(options));
110
- cleanupTmpFile(opts.ctTailwindPath);
111
- cleanupTmpFile(opts.portLockFilePath);
112
- if (process.env.NX_VERBOSE_LOGGING === 'true' && opts.portLockFilePath) {
113
- (0, fs_1.readdirSync)((0, path_1.dirname)(opts.portLockFilePath)).forEach((f) => {
114
- if (f.endsWith('.txt')) {
115
- devkit_1.logger.debug(`Lock file ${f} still present`);
116
- }
117
- });
118
- }
119
- /**
120
- * `cypress.open` is returning `0` and is not of the same type as `cypress.run`.
121
- * `cypress.open` is the graphical UI, so it will be obvious to know what wasn't
122
- * working. Forcing the build to success when `cypress.open` is used.
123
- */
124
- return !result.totalFailed && !result.failures;
125
- }
126
- function cleanupTmpFile(path) {
127
- try {
128
- if (path && (0, fs_1.existsSync)(path)) {
129
- (0, fs_1.unlinkSync)(path);
130
- }
131
- return true;
132
- }
133
- catch (err) {
134
- return false;
135
- }
136
- }
@@ -1,186 +0,0 @@
1
- {
2
- "version": 2,
3
- "title": "Cypress Target",
4
- "description": "Run Cypress for e2e, integration and component testing.",
5
- "type": "object",
6
- "outputCapture": "pipe",
7
- "cli": "nx",
8
- "x-deprecated": "The `@nx/cypress:cypress` executor is deprecated and will be removed in Nx v24. Run `nx g @nx/cypress:convert-to-inferred` to migrate to the `@nx/cypress/plugin` inferred plugin. See https://nx.dev/docs/guides/tasks--caching/convert-to-inferred for details.",
9
- "presets": [
10
- {
11
- "name": "Starting Dev Server",
12
- "keys": ["cypressConfig", "devServerTarget"]
13
- },
14
- {
15
- "name": "Custom Base Url",
16
- "keys": ["cypressConfig", "baseUrl"]
17
- },
18
- {
19
- "name": "Component Testing",
20
- "keys": ["cypressConfig", "devServerTarget", "testingType", "skipServe"]
21
- }
22
- ],
23
- "properties": {
24
- "cypressConfig": {
25
- "type": "string",
26
- "description": "The path of the Cypress configuration json file.",
27
- "x-completion-type": "file",
28
- "x-completion-glob": "cypress?(*)@(.js|.ts|.json)"
29
- },
30
- "watch": {
31
- "type": "boolean",
32
- "description": "Recompile and run tests when files change.",
33
- "default": false
34
- },
35
- "devServerTarget": {
36
- "type": "string",
37
- "description": "Dev server target to run tests against."
38
- },
39
- "headed": {
40
- "type": "boolean",
41
- "description": "Displays the browser instead of running headlessly. Set this to `true` if your run depends on a Chrome extension being loaded.",
42
- "default": false
43
- },
44
- "headless": {
45
- "type": "boolean",
46
- "description": "Hide the browser instead of running headed.",
47
- "default": false,
48
- "x-deprecated": "Cypress runs headless by default. Use the --watch flag to control head/headless behavior instead. It will be removed in Nx v23."
49
- },
50
- "exit": {
51
- "type": "boolean",
52
- "description": "Whether or not the Cypress Test Runner will stay open after running tests in a spec file.",
53
- "default": true
54
- },
55
- "key": {
56
- "type": "string",
57
- "description": "The key cypress should use to run tests in parallel/record the run (CI only)."
58
- },
59
- "record": {
60
- "type": "boolean",
61
- "description": "Whether or not Cypress should record the results of the tests.",
62
- "default": false
63
- },
64
- "parallel": {
65
- "aliases": ["p"],
66
- "type": "boolean",
67
- "description": "Whether or not Cypress should run its tests in parallel (CI only).",
68
- "default": false
69
- },
70
- "baseUrl": {
71
- "type": "string",
72
- "description": "The address (with the port) which your application is running on.",
73
- "x-priority": "important"
74
- },
75
- "browser": {
76
- "type": "string",
77
- "description": "The browser to run tests in."
78
- },
79
- "env": {
80
- "type": "object",
81
- "description": "A key-value Pair of environment variables to pass to Cypress runner."
82
- },
83
- "spec": {
84
- "type": "string",
85
- "description": "A comma delimited glob string that is provided to the Cypress runner to specify which spec files to run. i.e. `**examples/**,**actions.spec**`."
86
- },
87
- "ciBuildId": {
88
- "oneOf": [
89
- {
90
- "type": "string"
91
- },
92
- {
93
- "type": "number"
94
- }
95
- ],
96
- "description": "A unique identifier for a run to enable grouping or parallelization."
97
- },
98
- "group": {
99
- "type": "string",
100
- "description": "A named group for recorded runs in the Cypress dashboard."
101
- },
102
- "ignoreTestFiles": {
103
- "aliases": ["excludeSpecPattern"],
104
- "oneOf": [
105
- {
106
- "type": "string"
107
- },
108
- {
109
- "type": "array",
110
- "items": {
111
- "type": "string"
112
- }
113
- }
114
- ],
115
- "description": "A String or Array of glob patterns used to ignore test files that would otherwise be shown in your list of tests. Cypress uses minimatch with the options: `{dot: true, matchBase: true}`. We suggest using https://globster.xyz to test what files would match."
116
- },
117
- "reporter": {
118
- "type": "string",
119
- "description": "The reporter used during cypress run."
120
- },
121
- "reporterOptions": {
122
- "oneOf": [
123
- {
124
- "type": "string"
125
- },
126
- {
127
- "type": "object"
128
- }
129
- ],
130
- "description": "The reporter options used. Supported options depend on the reporter. https://docs.cypress.io/guides/tooling/reporters#Reporter-Options"
131
- },
132
- "skipServe": {
133
- "type": "boolean",
134
- "description": "Skip dev-server build.",
135
- "default": false
136
- },
137
- "testingType": {
138
- "type": "string",
139
- "description": "Specify the type of tests to execute.",
140
- "enum": ["component", "e2e"],
141
- "default": "e2e"
142
- },
143
- "tag": {
144
- "type": "string",
145
- "description": "A comma delimited list to identify a run with.",
146
- "aliases": ["t"]
147
- },
148
- "port": {
149
- "oneOf": [
150
- {
151
- "type": "string",
152
- "enum": ["cypress-auto"]
153
- },
154
- {
155
- "type": "number"
156
- }
157
- ],
158
- "description": "Pass a specified port value to the devServerTarget, if the value is 'cypress-auto' a free port will automatically be picked for the devServerTarget."
159
- },
160
- "quiet": {
161
- "aliases": ["q"],
162
- "type": "boolean",
163
- "description": "If passed, Cypress output will not be printed to stdout. Only output from the configured Mocha reporter will print.",
164
- "default": false
165
- },
166
- "runnerUi": {
167
- "type": "boolean",
168
- "description": "Displays the Cypress Runner UI. Useful for when Test Replay is enabled and you would still like the Cypress Runner UI to be displayed for screenshots and video."
169
- },
170
- "autoCancelAfterFailures": {
171
- "oneOf": [
172
- {
173
- "type": "number"
174
- },
175
- {
176
- "type": "boolean",
177
- "const": false
178
- }
179
- ],
180
- "description": "Specify the number of failures to cancel a run being recorded to the Cypress Cloud or `false` to disable auto-cancellation."
181
- }
182
- },
183
- "additionalProperties": true,
184
- "required": ["cypressConfig"],
185
- "examplesFile": "../../../docs/cypress-examples.md"
186
- }
@@ -1,3 +0,0 @@
1
- export declare const CYPRESS_EXECUTOR_DEPRECATION_MESSAGE = "The `@nx/cypress:cypress` executor is deprecated and will be removed in Nx v24. Run `nx g @nx/cypress:convert-to-inferred` to migrate to the `@nx/cypress/plugin` inferred targets. See https://nx.dev/docs/guides/tasks--caching/convert-to-inferred for details.";
2
- export declare function warnCypressExecutorDeprecation(): void;
3
- export declare function warnCypressExecutorGenerating(): void;
@@ -1,20 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CYPRESS_EXECUTOR_DEPRECATION_MESSAGE = void 0;
4
- exports.warnCypressExecutorDeprecation = warnCypressExecutorDeprecation;
5
- exports.warnCypressExecutorGenerating = warnCypressExecutorGenerating;
6
- const devkit_1 = require("@nx/devkit");
7
- // TODO(v24): Remove the @nx/cypress:cypress executor. The inferred plugin
8
- // (@nx/cypress/plugin) and the convert-to-inferred generator stay supported.
9
- exports.CYPRESS_EXECUTOR_DEPRECATION_MESSAGE = 'The `@nx/cypress:cypress` executor is deprecated and will be removed in Nx v24. Run `nx g @nx/cypress:convert-to-inferred` to migrate to the `@nx/cypress/plugin` inferred targets. See https://nx.dev/docs/guides/tasks--caching/convert-to-inferred for details.';
10
- function warnCypressExecutorDeprecation() {
11
- devkit_1.logger.warn(exports.CYPRESS_EXECUTOR_DEPRECATION_MESSAGE);
12
- }
13
- // Fired when the @nx/cypress:configuration or :component-configuration
14
- // generator is about to generate a target that uses the deprecated executor —
15
- // i.e. when @nx/cypress/plugin isn't registered in nx.json. Surfaces the
16
- // deprecation at generation time rather than only when the user later runs the
17
- // generated target.
18
- function warnCypressExecutorGenerating() {
19
- devkit_1.logger.warn('Generating a target that uses the deprecated `@nx/cypress:cypress` executor. The executor will be removed in Nx v24. Run `nx g @nx/cypress:convert-to-inferred` next to migrate this target to the `@nx/cypress/plugin` inferred plugin and prevent future generators from emitting executor targets. See https://nx.dev/docs/guides/tasks--caching/convert-to-inferred for details.');
20
- }
@@ -1,9 +0,0 @@
1
- import { ExecutorContext } from '@nx/devkit';
2
- import { CypressExecutorOptions } from '../executors/cypress/cypress.impl';
3
- export declare function startDevServer(opts: Omit<CypressExecutorOptions, 'cypressConfig'>, context: ExecutorContext): AsyncGenerator<{
4
- baseUrl: any;
5
- portLockFilePath?: undefined;
6
- } | {
7
- baseUrl: any;
8
- portLockFilePath: string;
9
- }, void, unknown>;
@@ -1,165 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.startDevServer = startDevServer;
4
- const tslib_1 = require("tslib");
5
- const devkit_1 = require("@nx/devkit");
6
- const path_1 = require("path");
7
- const detect_port_1 = tslib_1.__importDefault(require("detect-port"));
8
- const fs_1 = require("fs");
9
- const os_1 = require("os");
10
- const internal_1 = require("@nx/devkit/internal");
11
- const PORT_LOCK_DIR = (0, path_1.join)((0, os_1.tmpdir)(), 'nx-cypress-port-locks');
12
- async function* startDevServer(opts, context) {
13
- // no dev server, return the provisioned base url
14
- if (!opts.devServerTarget || opts.skipServe) {
15
- yield { baseUrl: opts.baseUrl };
16
- return;
17
- }
18
- const parsedDevServerTarget = (0, devkit_1.parseTargetString)(opts.devServerTarget, context);
19
- const [targetSupportsWatchOpt] = getValueFromSchema(context, parsedDevServerTarget, 'watch');
20
- const overrides = {
21
- // @NOTE: Do not forward watch option if not supported by the target dev server,
22
- // this is relevant for running Cypress against dev server target that does not support this option,
23
- // for instance @nguniversal/builders:ssr-dev-server.
24
- ...(targetSupportsWatchOpt ? { watch: opts.watch } : {}),
25
- };
26
- if (opts.port === 'cypress-auto') {
27
- const freePort = await getPortForProject(context, parsedDevServerTarget);
28
- overrides['port'] = freePort;
29
- }
30
- else if (opts.port !== undefined) {
31
- overrides['port'] = opts.port;
32
- // zero is a special case that means any valid port so there is no reason to try to 'lock it'
33
- if (opts.port !== 0) {
34
- const didLock = attemptToLockPort(opts.port);
35
- if (!didLock) {
36
- devkit_1.logger.warn((0, devkit_1.stripIndents) `${opts.port} is potentially already in use by another cypress run.
37
- If the port is in use, try using a different port value or passing --port='cypress-auto' to find a free port.`);
38
- }
39
- }
40
- }
41
- for await (const output of await (0, devkit_1.runExecutor)(parsedDevServerTarget, overrides, context)) {
42
- if (!output.success && !opts.watch)
43
- throw new Error('Could not compile application files');
44
- if (!opts.baseUrl &&
45
- !output.baseUrl &&
46
- !output.info?.baseUrl &&
47
- (output.port || output.info?.port)) {
48
- output.baseUrl = `http://localhost:${output.info.port}`;
49
- }
50
- yield {
51
- baseUrl: opts.baseUrl || output.baseUrl || output.info?.baseUrl,
52
- portLockFilePath: overrides.port && (0, path_1.join)(PORT_LOCK_DIR, `${overrides.port}.lock`),
53
- };
54
- }
55
- }
56
- /**
57
- * try to find a free port for the project to run on
58
- * will return undefined if no port is found or the project doesn't have a port option
59
- **/
60
- async function getPortForProject(context, target, defaultPort = 4200) {
61
- const fmtTarget = (0, devkit_1.targetToTargetString)(target);
62
- const [hasPortOpt, schemaPortValue] = getValueFromSchema(context, target, 'port');
63
- let freePort;
64
- if (hasPortOpt) {
65
- let normalizedPortValue;
66
- if (!schemaPortValue) {
67
- devkit_1.logger.info(`NX ${fmtTarget} did not have a defined port value, checking for free port with the default value of ${defaultPort}`);
68
- normalizedPortValue = defaultPort;
69
- }
70
- else {
71
- normalizedPortValue = Number(schemaPortValue);
72
- }
73
- if (isNaN(normalizedPortValue)) {
74
- devkit_1.output.warn({
75
- title: `Port Not a Number`,
76
- bodyLines: [
77
- `The port value found was not a number or can't be parsed to a number`,
78
- `When reading the devServerTarget (${fmtTarget}) schema, expected ${schemaPortValue} to be a number but got NaN.`,
79
- `Nx will use the default value of ${defaultPort} instead.`,
80
- `You can manually specify a port by setting the 'port' option`,
81
- ],
82
- });
83
- normalizedPortValue = defaultPort;
84
- }
85
- try {
86
- let attempts = 0;
87
- // make sure when this check happens in parallel,
88
- // we don't let the same port be used by multiple projects
89
- do {
90
- freePort = await (0, detect_port_1.default)(freePort || normalizedPortValue);
91
- if (attemptToLockPort(freePort)) {
92
- break;
93
- }
94
- attempts++;
95
- // increment port in case the lock file isn't cleaned up
96
- freePort++;
97
- } while (attempts < 20);
98
- devkit_1.logger.info(`NX Using port ${freePort} for ${fmtTarget}`);
99
- }
100
- catch (err) {
101
- throw new Error((0, devkit_1.stripIndents) `Unable to find a free port for the dev server, ${fmtTarget}.
102
- You can disable auto port detection by specifing a port or not passing a value to --port`);
103
- }
104
- }
105
- else {
106
- devkit_1.output.warn({
107
- title: `No Port Option Found`,
108
- bodyLines: [
109
- `The 'port' option is set to 'cypress-auto', but the devServerTarget (${fmtTarget}) does not have a port option.`,
110
- `Because of this, Nx is unable to verify the port is free before starting the dev server.`,
111
- `This might cause issues if the devServerTarget is trying to use a port that is already in use.`,
112
- ],
113
- });
114
- }
115
- return freePort;
116
- }
117
- /**
118
- * Check if the given target has the given property in it's options.
119
- * if the property is does not have a default value or is not in the actual executor options,
120
- * the value will be undefined even if it's in the executor schema.
121
- **/
122
- function getValueFromSchema(context, target, property) {
123
- let targetOpts;
124
- try {
125
- targetOpts = (0, devkit_1.readTargetOptions)(target, context);
126
- }
127
- catch (e) {
128
- throw new Error(`Unable to read the target options for ${(0, devkit_1.targetToTargetString)(target)}.
129
- Are you sure this is a valid target?
130
- Was trying to read the target for the property: '${property}', but got the following error:
131
- ${e.message || e}`);
132
- }
133
- let targetHasOpt = Object.keys(targetOpts).includes(property);
134
- if (!targetHasOpt) {
135
- // NOTE: readTargetOptions doesn't apply non defaulted values, i.e. @nx/vite has a port options but is optional
136
- // so we double check the schema if readTargetOptions didn't return a value for the property
137
- const projectConfig = context.projectsConfigurations?.projects?.[target.project];
138
- const targetConfig = projectConfig.targets[target.target];
139
- const [collection, executor] = (0, internal_1.parseExecutor)(targetConfig.executor);
140
- const { schema } = (0, internal_1.getExecutorInformation)(collection, executor, context.root, context.projectsConfigurations.projects);
141
- // NOTE: schema won't have a default since readTargetOptions would have
142
- // already set that and this check wouldn't need to be made
143
- targetHasOpt = Object.keys(schema.properties).includes(property);
144
- }
145
- return [targetHasOpt, targetOpts[property]];
146
- }
147
- function attemptToLockPort(port) {
148
- try {
149
- (0, fs_1.mkdirSync)(PORT_LOCK_DIR, { recursive: true });
150
- }
151
- catch {
152
- // ignore if already exists
153
- }
154
- const portLockFilePath = (0, path_1.join)(PORT_LOCK_DIR, `${port}.lock`);
155
- try {
156
- if ((0, fs_1.existsSync)(portLockFilePath)) {
157
- return false;
158
- }
159
- (0, fs_1.writeFileSync)(portLockFilePath, 'locked');
160
- return true;
161
- }
162
- catch (err) {
163
- return false;
164
- }
165
- }