@nx/cypress 23.3.0-beta.0 → 23.3.0-beta.2

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.
@@ -4,6 +4,8 @@ exports.cypressProjectConfigs = cypressProjectConfigs;
4
4
  exports.getObjectProperty = getObjectProperty;
5
5
  exports.removeObjectProperty = removeObjectProperty;
6
6
  exports.updateObjectProperty = updateObjectProperty;
7
+ exports.parseSourceFile = parseSourceFile;
8
+ exports.hasLocalValueBinding = hasLocalValueBinding;
7
9
  const devkit_1 = require("@nx/devkit");
8
10
  const internal_1 = require("@nx/js/internal");
9
11
  const path_1 = require("path");
@@ -12,17 +14,19 @@ let ts;
12
14
  async function* cypressProjectConfigs(tree) {
13
15
  const projects = (0, devkit_1.getProjects)(tree);
14
16
  for (const [projectName, projectConfig] of projects) {
15
- const targetWithExecutor = Object.values(projectConfig.targets ?? {}).find((target) => target.executor === '@nx/cypress:cypress');
16
- if (targetWithExecutor) {
17
- for (const [, options] of allTargetOptions(targetWithExecutor)) {
18
- if (options.cypressConfig) {
19
- yield {
20
- projectName,
21
- projectConfig,
22
- cypressConfigPath: options.cypressConfig,
23
- };
17
+ const targetsWithExecutor = Object.values(projectConfig.targets ?? {}).filter((target) => target.executor === '@nx/cypress:cypress');
18
+ if (targetsWithExecutor.length > 0) {
19
+ const cypressConfigPaths = new Set();
20
+ for (const target of targetsWithExecutor) {
21
+ for (const [, options] of allTargetOptions(target)) {
22
+ if (options.cypressConfig) {
23
+ cypressConfigPaths.add(options.cypressConfig);
24
+ }
24
25
  }
25
26
  }
27
+ for (const cypressConfigPath of cypressConfigPaths) {
28
+ yield { projectName, projectConfig, cypressConfigPath };
29
+ }
26
30
  }
27
31
  else {
28
32
  // might be using the crystal plugin
@@ -69,3 +73,100 @@ function* allTargetOptions(target) {
69
73
  }
70
74
  }
71
75
  }
76
+ // The extension picks the script kind, so `<T>x` in a .ts file and JSX in a
77
+ // .tsx file both parse; tsquery's `ast()` parses everything as TSX.
78
+ function parseSourceFile(filePath, content) {
79
+ ts ??= (0, internal_1.ensureTypescript)();
80
+ return ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest,
81
+ /* setParentNodes */ true);
82
+ }
83
+ /**
84
+ * Whether the file binds `name` as a runtime value anywhere: a variable,
85
+ * parameter, destructured element, function or class (declaration or named
86
+ * expression), enum, namespace holding a value, or a value import (`import x`,
87
+ * `import { x }`, `import * as x`, `import x =`).
88
+ * Type-only imports and ambient declarations (`declare ...`, including
89
+ * everything under `declare global`) do not count, so the usual
90
+ * `declare global { namespace Cypress { ... } }` augmentation never hides the
91
+ * `Cypress` global.
92
+ */
93
+ function hasLocalValueBinding(sourceFile, name) {
94
+ ts ??= (0, internal_1.ensureTypescript)();
95
+ if (sourceFile.isDeclarationFile) {
96
+ return false;
97
+ }
98
+ const bindsName = (node) => {
99
+ if (ts.isVariableDeclaration(node) ||
100
+ ts.isParameter(node) ||
101
+ ts.isBindingElement(node) ||
102
+ ts.isFunctionDeclaration(node) ||
103
+ ts.isFunctionExpression(node) ||
104
+ ts.isClassDeclaration(node) ||
105
+ ts.isClassExpression(node) ||
106
+ ts.isEnumDeclaration(node)) {
107
+ return (!!node.name && ts.isIdentifier(node.name) && node.name.text === name);
108
+ }
109
+ if (ts.isModuleDeclaration(node)) {
110
+ return (ts.isIdentifier(node.name) &&
111
+ node.name.text === name &&
112
+ isInstantiatedNamespace(node));
113
+ }
114
+ if (ts.isImportClause(node)) {
115
+ return !node.isTypeOnly && node.name?.text === name;
116
+ }
117
+ if (ts.isImportSpecifier(node)) {
118
+ return (!node.isTypeOnly &&
119
+ !node.parent.parent.isTypeOnly &&
120
+ node.name.text === name);
121
+ }
122
+ if (ts.isNamespaceImport(node)) {
123
+ return !node.parent.isTypeOnly && node.name.text === name;
124
+ }
125
+ if (ts.isImportEqualsDeclaration(node)) {
126
+ return !node.isTypeOnly && node.name.text === name;
127
+ }
128
+ return false;
129
+ };
130
+ const visit = (node) => {
131
+ if (ts.canHaveModifiers(node) &&
132
+ ts
133
+ .getModifiers(node)
134
+ ?.some((modifier) => modifier.kind === ts.SyntaxKind.DeclareKeyword)) {
135
+ return false;
136
+ }
137
+ return bindsName(node) || (ts.forEachChild(node, visit) ?? false);
138
+ };
139
+ return visit(sourceFile);
140
+ }
141
+ // Mirrors TypeScript's getModuleInstanceState: a namespace emits a value
142
+ // unless every statement is a type declaration, a non-exported import or
143
+ // such a namespace. Anything else, an expression statement included, and
144
+ // `export { x }` lists and const enums, count as a value.
145
+ function isInstantiatedNamespace(node) {
146
+ const body = node.body;
147
+ if (!body) {
148
+ return false;
149
+ }
150
+ if (ts.isModuleDeclaration(body)) {
151
+ return isInstantiatedNamespace(body);
152
+ }
153
+ if (!ts.isModuleBlock(body)) {
154
+ return false;
155
+ }
156
+ return body.statements.some((statement) => {
157
+ if (ts.isInterfaceDeclaration(statement) ||
158
+ ts.isTypeAliasDeclaration(statement)) {
159
+ return false;
160
+ }
161
+ if (ts.isImportDeclaration(statement) ||
162
+ ts.isImportEqualsDeclaration(statement)) {
163
+ return !!ts
164
+ .getModifiers(statement)
165
+ ?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword);
166
+ }
167
+ if (ts.isModuleDeclaration(statement)) {
168
+ return isInstantiatedNamespace(statement);
169
+ }
170
+ return true;
171
+ });
172
+ }
@@ -3,13 +3,15 @@ export declare const nxVersion: any;
3
3
  export declare const minSupportedCypressVersion = "13.0.0";
4
4
  export declare const eslintPluginCypressVersion = "^3.5.0";
5
5
  export declare const typesNodeVersion = "^22.0.0";
6
- export declare const cypressViteDevServerVersion = "^7.3.1";
7
- export declare const cypressVersion = "^15.20.1";
8
- export declare const cypressWebpackVersion = "^5.4.1";
9
- export declare const viteVersion = "^6.0.0";
6
+ export declare const cypressViteDevServerVersion = "^8.0.0";
7
+ export declare const cypressVersion = "^16.0.0";
8
+ export declare const cypressWebpackVersion = "^6.0.0";
9
+ export declare const viteVersion = "^8.0.0";
10
10
  export declare const htmlWebpackPluginVersion = "^5.5.0";
11
- export type CypressVersions = Record<keyof Omit<typeof import('./versions'), 'nxVersion' | 'minSupportedCypressVersion' | 'versions' | 'getInstalledCypressVersion' | 'getInstalledCypressMajorVersion' | 'assertMinimumCypressVersion'>, string>;
11
+ export type CypressVersions = Record<keyof Omit<typeof import('./versions'), 'nxVersion' | 'minSupportedCypressVersion' | 'versions' | 'getInstalledCypressVersion' | 'getInstalledCypressMajorVersion' | 'assertMinimumCypressVersion' | 'assertViteSupportsInstalledCypress' | 'componentTestingVersions'>, string>;
12
12
  export declare function versions(tree: Tree): CypressVersions;
13
+ export declare function componentTestingVersions(tree: Tree, bundler: 'vite' | 'webpack' | undefined): CypressVersions;
13
14
  export declare function getInstalledCypressVersion(tree?: Tree): string | null;
14
15
  export declare function getInstalledCypressMajorVersion(tree?: Tree): number | null;
16
+ export declare function assertViteSupportsInstalledCypress(tree: Tree): void;
15
17
  export declare function assertMinimumCypressVersion(minVersion: number, tree?: Tree): void;
@@ -2,9 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.htmlWebpackPluginVersion = exports.viteVersion = exports.cypressWebpackVersion = exports.cypressVersion = exports.cypressViteDevServerVersion = exports.typesNodeVersion = exports.eslintPluginCypressVersion = exports.minSupportedCypressVersion = exports.nxVersion = void 0;
4
4
  exports.versions = versions;
5
+ exports.componentTestingVersions = componentTestingVersions;
5
6
  exports.getInstalledCypressVersion = getInstalledCypressVersion;
6
7
  exports.getInstalledCypressMajorVersion = getInstalledCypressMajorVersion;
8
+ exports.assertViteSupportsInstalledCypress = assertViteSupportsInstalledCypress;
7
9
  exports.assertMinimumCypressVersion = assertMinimumCypressVersion;
10
+ const devkit_1 = require("@nx/devkit");
8
11
  const internal_1 = require("@nx/devkit/internal");
9
12
  const path_1 = require("path");
10
13
  const semver_1 = require("semver");
@@ -12,10 +15,10 @@ exports.nxVersion = require((0, path_1.join)('@nx/cypress', 'package.json')).ver
12
15
  exports.minSupportedCypressVersion = '13.0.0';
13
16
  exports.eslintPluginCypressVersion = '^3.5.0';
14
17
  exports.typesNodeVersion = '^22.0.0';
15
- exports.cypressViteDevServerVersion = '^7.3.1';
16
- exports.cypressVersion = '^15.20.1';
17
- exports.cypressWebpackVersion = '^5.4.1';
18
- exports.viteVersion = '^6.0.0';
18
+ exports.cypressViteDevServerVersion = '^8.0.0';
19
+ exports.cypressVersion = '^16.0.0';
20
+ exports.cypressWebpackVersion = '^6.0.0';
21
+ exports.viteVersion = '^8.0.0';
19
22
  exports.htmlWebpackPluginVersion = '^5.5.0';
20
23
  const latestVersions = {
21
24
  eslintPluginCypressVersion: exports.eslintPluginCypressVersion,
@@ -45,7 +48,21 @@ const versionMap = {
45
48
  viteVersion: '^6.0.0',
46
49
  htmlWebpackPluginVersion: '^5.5.0',
47
50
  },
51
+ 15: {
52
+ eslintPluginCypressVersion: '^3.5.0',
53
+ typesNodeVersion: '^22.0.0',
54
+ cypressViteDevServerVersion: '^7.3.1',
55
+ cypressVersion: '^15.20.1',
56
+ cypressWebpackVersion: '^5.4.1',
57
+ viteVersion: '^6.0.0',
58
+ htmlWebpackPluginVersion: '^5.5.0',
59
+ },
48
60
  };
61
+ // Highest first, so a range reaching several majors resolves to the top one.
62
+ const supportedMajors = [
63
+ (0, semver_1.major)((0, semver_1.coerce)(exports.cypressVersion)),
64
+ ...Object.keys(versionMap).map(Number),
65
+ ].sort((a, b) => b - a);
49
66
  function versions(tree) {
50
67
  const installedCypressVersion = getInstalledCypressVersion(tree);
51
68
  if (!installedCypressVersion) {
@@ -54,16 +71,52 @@ function versions(tree) {
54
71
  const cypressMajorVersion = (0, semver_1.major)(installedCypressVersion);
55
72
  return versionMap[cypressMajorVersion] ?? latestVersions;
56
73
  }
74
+ // A fresh Vite component testing setup on an older Vite gets the last set
75
+ // that runs on it; Cypress 16 only runs on Vite 8.
76
+ function componentTestingVersions(tree, bundler) {
77
+ if (bundler === 'vite' &&
78
+ !getInstalledCypressVersion(tree) &&
79
+ getViteVersionBelowCypress16Floor(tree)) {
80
+ return versionMap[15];
81
+ }
82
+ return versions(tree);
83
+ }
57
84
  function getInstalledCypressVersion(tree) {
58
85
  if (!tree) {
59
86
  return (0, internal_1.getInstalledPackageVersion)('cypress');
60
87
  }
61
- return (0, internal_1.getDeclaredPackageVersion)(tree, 'cypress');
88
+ const resolved = (0, internal_1.getResolvedPackageVersion)(tree, 'cypress');
89
+ const declared = (0, devkit_1.getDependencyVersionFromPackageJson)(tree, 'cypress');
90
+ if (!resolved ||
91
+ !declared ||
92
+ !(0, semver_1.validRange)(declared) ||
93
+ (0, internal_1.getSatisfyingInstalledPackageVersion)(tree, 'cypress', declared)) {
94
+ return resolved;
95
+ }
96
+ // No installed Cypress satisfies the range. A clean install of a satisfiable
97
+ // range lands on the highest version it admits, so follow the highest
98
+ // supported major it reaches; the version inside that major is unknown.
99
+ const floorMajor = (0, semver_1.major)(resolved);
100
+ const reachedMajor = supportedMajors.find((m) => m > floorMajor && (0, semver_1.intersects)(declared, `${m}.x`));
101
+ return reachedMajor ? `${reachedMajor}.0.0` : resolved;
62
102
  }
63
103
  function getInstalledCypressMajorVersion(tree) {
64
104
  const installedCypressVersion = getInstalledCypressVersion(tree);
65
105
  return installedCypressVersion ? (0, semver_1.major)(installedCypressVersion) : null;
66
106
  }
107
+ function assertViteSupportsInstalledCypress(tree) {
108
+ const cypressMajor = getInstalledCypressMajorVersion(tree);
109
+ const viteVersion = getViteVersionBelowCypress16Floor(tree);
110
+ if (cypressMajor >= 16 && viteVersion) {
111
+ throw new Error(`Cypress ${cypressMajor} component testing requires Vite 8. Found Vite ${viteVersion}. Update Vite to 8 or use Cypress 15.`);
112
+ }
113
+ }
114
+ // Cypress 16 component testing rejects a Vite below 8. Its dev server
115
+ // package peers `vite ^8.0.0`, which also excludes 8 prereleases.
116
+ function getViteVersionBelowCypress16Floor(tree) {
117
+ const viteVersion = (0, internal_1.getResolvedPackageVersion)(tree, 'vite');
118
+ return viteVersion && (0, semver_1.lt)(viteVersion, '8.0.0') ? viteVersion : null;
119
+ }
67
120
  function assertMinimumCypressVersion(minVersion, tree) {
68
121
  const version = getInstalledCypressMajorVersion(tree);
69
122
  if (version && version < minVersion) {
package/migrations.json CHANGED
@@ -61,6 +61,43 @@
61
61
  "description": "Set `justInTimeCompile: false` in webpack component testing Cypress configs on Cypress 14+, where it defaults to true and can intermittently run 0 tests in CI.",
62
62
  "implementation": "./dist/src/migrations/update-23-1-0/disable-webpack-ct-just-in-time-compile",
63
63
  "documentation": "./dist/src/migrations/update-23-1-0/disable-webpack-ct-just-in-time-compile.md"
64
+ },
65
+ "update-cypress-16-config-options": {
66
+ "version": "23.3.0-beta.1",
67
+ "requires": {
68
+ "cypress": ">=16.0.0"
69
+ },
70
+ "description": "Renames `experimentalMemoryManagement` to `manageBrowserMemory`, replaces `experimentalFastVisibility` with `visibilityStrategy`, and removes `experimentalSourceRewriting`, `allowCypressEnv` and `execTimeout` from `cypress.config.{ts,js,mjs,cjs}`, matching the Cypress 16 config changes.",
71
+ "implementation": "./dist/src/migrations/update-23-3-0/update-cypress-16-config-options",
72
+ "documentation": "./dist/src/migrations/update-23-3-0/update-cypress-16-config-options.md"
73
+ },
74
+ "update-cypress-16-query-command-overwrites": {
75
+ "version": "23.3.0-beta.1",
76
+ "requires": {
77
+ "cypress": ">=16.0.0"
78
+ },
79
+ "description": "Renames `Cypress.Commands.overwrite()` to `Cypress.Commands.overwriteQuery()` for `getCookie`, `getCookies`, `getAllCookies`, `getAllLocalStorage` and `getAllSessionStorage`, which are queries in Cypress 16; an AI prompt then adapts the callbacks to the query contract.",
80
+ "implementation": "./dist/src/migrations/update-23-3-0/update-cypress-16-query-command-overwrites",
81
+ "prompt": "./dist/src/migrations/update-23-3-0/adapt-query-command-overwrites.md",
82
+ "documentation": "./dist/src/migrations/update-23-3-0/update-cypress-16-query-command-overwrites.md"
83
+ },
84
+ "update-angular-zoneless-mount-import": {
85
+ "version": "23.3.0-beta.1",
86
+ "requires": {
87
+ "cypress": ">=16.0.0"
88
+ },
89
+ "description": "Rewrites `cypress/angular-zoneless` and `@cypress/angular-zoneless` imports to `cypress/angular`, which is zoneless in Cypress 16 and the only Angular mount entry point it ships, and removes the deprecated `@cypress/angular-zoneless` package.",
90
+ "implementation": "./dist/src/migrations/update-23-3-0/update-angular-zoneless-mount-import",
91
+ "documentation": "./dist/src/migrations/update-23-3-0/update-angular-zoneless-mount-import.md"
92
+ },
93
+ "create-ai-instructions-for-cypress-16": {
94
+ "version": "23.3.0-beta.1",
95
+ "requires": {
96
+ "cypress": ">=16.0.0"
97
+ },
98
+ "description": "AI-assisted migration: replaces `Cypress.env()`, `cy.exec()`, `.end()`, `env` test-configuration overrides and runtime `Cypress.config()` writes removed in Cypress 16, whose replacements depend on where each value comes from, so it is driven by an AI prompt rather than a deterministic generator.",
99
+ "prompt": "./dist/src/migrations/update-23-3-0/ai-instructions-for-cypress-16.md",
100
+ "documentation": "./dist/src/migrations/update-23-3-0/upgrade-to-cypress-16.md"
64
101
  }
65
102
  },
66
103
  "packageJsonUpdates": {
@@ -160,6 +197,29 @@
160
197
  "alwaysAddToPackageJson": false
161
198
  }
162
199
  }
200
+ },
201
+ "23.3.0-cypress-16": {
202
+ "version": "23.3.0-beta.1",
203
+ "requires": {
204
+ "cypress": ">=15.0.0 <16.0.0"
205
+ },
206
+ "incompatibleWith": {
207
+ "vite": "<8.0.0"
208
+ },
209
+ "packages": {
210
+ "cypress": {
211
+ "version": "^16.0.0",
212
+ "alwaysAddToPackageJson": false
213
+ },
214
+ "@cypress/vite-dev-server": {
215
+ "version": "^8.0.0",
216
+ "alwaysAddToPackageJson": false
217
+ },
218
+ "@cypress/webpack-dev-server": {
219
+ "version": "^6.0.0",
220
+ "alwaysAddToPackageJson": false
221
+ }
222
+ }
163
223
  }
164
224
  }
165
225
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/cypress",
3
- "version": "23.3.0-beta.0",
3
+ "version": "23.3.0-beta.2",
4
4
  "private": false,
5
5
  "type": "commonjs",
6
6
  "files": [
@@ -89,15 +89,15 @@
89
89
  "semver": "^7.6.3",
90
90
  "tree-kill": "^1.2.2",
91
91
  "tslib": "^2.3.0",
92
- "@nx/devkit": "23.3.0-beta.0",
93
- "@nx/eslint": "23.3.0-beta.0",
94
- "@nx/js": "23.3.0-beta.0"
92
+ "@nx/devkit": "23.3.0-beta.2",
93
+ "@nx/eslint": "23.3.0-beta.2",
94
+ "@nx/js": "23.3.0-beta.2"
95
95
  },
96
96
  "devDependencies": {
97
- "nx": "23.3.0-beta.0"
97
+ "nx": "23.3.0-beta.2"
98
98
  },
99
99
  "peerDependencies": {
100
- "cypress": ">= 13 < 16"
100
+ "cypress": ">= 13 < 17"
101
101
  },
102
102
  "peerDependenciesMeta": {
103
103
  "cypress": {