@nx/cypress 23.3.0-beta.7 → 23.3.0-beta.9
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.
- package/README.md +1 -1
- package/dist/src/migrations/update-23-1-0/disable-webpack-ct-just-in-time-compile.js +2 -38
- package/dist/src/migrations/update-23-3-0/ai-instructions-for-cypress-16.md +45 -2
- package/dist/src/migrations/update-23-3-0/update-cypress-16-config-options.js +4 -17
- package/dist/src/migrations/update-23-3-0/upgrade-to-cypress-16.d.ts +8 -0
- package/dist/src/migrations/update-23-3-0/upgrade-to-cypress-16.js +231 -0
- package/dist/src/migrations/update-23-3-0/upgrade-to-cypress-16.md +2 -2
- package/dist/src/plugins/plugin.js +7 -5
- package/dist/src/utils/migrations.d.ts +3 -1
- package/dist/src/utils/migrations.js +98 -24
- package/migrations.json +14 -27
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
[](<>)
|
|
13
13
|
[](http://commitizen.github.io/cz-cli/)
|
|
14
14
|
[](https://go.nx.dev/community)
|
|
15
|
-
[](https://nx.dev/nx-cloud)
|
|
16
16
|
|
|
17
17
|
</div>
|
|
18
18
|
|
|
@@ -46,7 +46,8 @@ function isWebpackComponentTesting(component) {
|
|
|
46
46
|
if (usesNxComponentTestingPreset(component)) {
|
|
47
47
|
// @nx/remix is the only vite-based Nx CT preset; resolve the import bound
|
|
48
48
|
// to the call so an unrelated remix reference elsewhere can't misclassify.
|
|
49
|
-
return getComponentTestingPresetImport(component) !==
|
|
49
|
+
return ((0, migrations_1.getComponentTestingPresetImport)(component.getSourceFile()) !==
|
|
50
|
+
NX_VITE_CT_PRESET);
|
|
50
51
|
}
|
|
51
52
|
// Hand-written config: migrate only when it has an inline devServer framework.
|
|
52
53
|
return hasInlineDevServerFramework(component);
|
|
@@ -71,43 +72,6 @@ function getComponentProperty(component, name) {
|
|
|
71
72
|
ts ??= (0, internal_1.ensureTypescript)();
|
|
72
73
|
return (0, tsquery_1.query)(component, 'PropertyAssignment').filter((property) => ts.isIdentifier(property.name) && property.name.text === name);
|
|
73
74
|
}
|
|
74
|
-
// Resolves the module specifier that binds the `nxComponentTestingPreset`
|
|
75
|
-
// identifier used inside `component`, covering both ESM `import` and CJS
|
|
76
|
-
// `require` cypress configs. Returns null when no such binding is found.
|
|
77
|
-
function getComponentTestingPresetImport(component) {
|
|
78
|
-
ts ??= (0, internal_1.ensureTypescript)();
|
|
79
|
-
for (const statement of component.getSourceFile().statements) {
|
|
80
|
-
if (ts.isImportDeclaration(statement) && statement.importClause) {
|
|
81
|
-
const namedBindings = statement.importClause.namedBindings;
|
|
82
|
-
if (namedBindings &&
|
|
83
|
-
ts.isNamedImports(namedBindings) &&
|
|
84
|
-
ts.isStringLiteral(statement.moduleSpecifier) &&
|
|
85
|
-
namedBindings.elements.some((element) => element.name.text === 'nxComponentTestingPreset')) {
|
|
86
|
-
return statement.moduleSpecifier.text;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
if (ts.isVariableStatement(statement)) {
|
|
90
|
-
for (const declaration of statement.declarationList.declarations) {
|
|
91
|
-
const initializer = declaration.initializer;
|
|
92
|
-
if (!initializer ||
|
|
93
|
-
!ts.isCallExpression(initializer) ||
|
|
94
|
-
!ts.isIdentifier(initializer.expression) ||
|
|
95
|
-
initializer.expression.text !== 'require' ||
|
|
96
|
-
!ts.isObjectBindingPattern(declaration.name)) {
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
const moduleSpecifier = initializer.arguments[0];
|
|
100
|
-
if (moduleSpecifier &&
|
|
101
|
-
ts.isStringLiteral(moduleSpecifier) &&
|
|
102
|
-
declaration.name.elements.some((element) => ts.isIdentifier(element.name) &&
|
|
103
|
-
element.name.text === 'nxComponentTestingPreset')) {
|
|
104
|
-
return moduleSpecifier.text;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
75
|
function setsJustInTimeCompile(component) {
|
|
112
76
|
return getComponentProperty(component, 'justInTimeCompile').length > 0;
|
|
113
77
|
}
|
|
@@ -139,7 +139,50 @@ 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. Adapt each renamed callback
|
|
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:
|
|
143
|
+
|
|
144
|
+
**Before:**
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
Cypress.Commands.overwriteQuery('getCookie', (originalFn, name, options) => {
|
|
148
|
+
return originalFn(name, { ...options, log: false });
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**After:**
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
Cypress.Commands.overwriteQuery(
|
|
156
|
+
'getCookie',
|
|
157
|
+
function (originalFn, name, options) {
|
|
158
|
+
return originalFn.call(this, name, { ...options, log: false });
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Rewrite callbacks that post-process the result. Get the inner function from `originalFn`, then return a function that calls it with the subject and transforms the value:
|
|
164
|
+
|
|
165
|
+
**Before:**
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
Cypress.Commands.overwriteQuery('getCookies', (originalFn, options) => {
|
|
169
|
+
return originalFn(options).then((cookies) =>
|
|
170
|
+
cookies.filter((cookie) => cookie.name.startsWith('app.'))
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**After:**
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
Cypress.Commands.overwriteQuery('getCookies', function (originalFn, options) {
|
|
179
|
+
const innerFn = originalFn.call(this, options);
|
|
180
|
+
return (subject) =>
|
|
181
|
+
innerFn(subject).filter((cookie) => cookie.name.startsWith('app.'));
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Callbacks that call other `cy.*` commands, use `cy.wrap()`, or run asynchronous work cannot become queries. Move that logic into a separate custom command added with `Cypress.Commands.add()` and update the specs that relied on the overwritten behavior.
|
|
143
186
|
|
|
144
187
|
## Step 7: Review behavior changes that need no code by default
|
|
145
188
|
|
|
@@ -155,7 +198,7 @@ Flag these, and change code only when a test fails:
|
|
|
155
198
|
## Step 8: Component testing
|
|
156
199
|
|
|
157
200
|
- Angular: `cypress/angular` mounts with zoneless change detection on Cypress 16, for zone-based apps too. A template no longer re-renders after a plain property mutation on the component instance, so state that tests assert on in the DOM must be signal-based. The `autoSpyOutputs` and `autoDetectChanges` mount options no longer exist: remove `autoDetectChanges`, and replace `autoSpyOutputs: true` with explicit spies passed through `componentProperties` (for example `{ saved: createOutputSpy('savedSpy') }`). Cypress officially supports Angular 21 and later; Angular 20 runs with a warning. The harness bootstraps through `@angular/platform-browser/testing`, so `@angular/platform-browser-dynamic` can be uninstalled when nothing else imports it. The deprecated `@cypress/angular-zoneless` npm package was removed and its imports rewritten to `cypress/angular` by the deterministic migration; remove any leftover reference to it.
|
|
158
|
-
- Vite: Cypress 16 component testing runs on Vite 8 only, and `@cypress/vite-dev-server` 8 declares it as a peer.
|
|
201
|
+
- Vite: Cypress 16 component testing runs on Vite 8 only, and `@cypress/vite-dev-server` 8 declares it as a peer. The deterministic migration kept the workspace on Cypress 15 only when a Vite component testing config, or a config whose bundler could not be determined statically, resolved an installed Vite below 8. It cannot see a `vite` that is declared but not installed. It also does not check a Cypress config with a custom name that no `@nx/cypress:cypress` target references, such as one run through `nx:run-commands`. Bump any `vite` below 8 in the root or a project `package.json`, and any `@vitejs/*` plugin that still pins an older major. A Vite component testing config the migration did not check must resolve Vite 8 from its directory.
|
|
159
202
|
- Next.js: Cypress's `next` dev server framework requires Next.js 15.0.4 or later. The `@nx/next/plugins/component-testing` preset runs the `react` framework over a webpack config Nx builds, so a Cypress config on that preset does not use the dropped adapter. Only a hand-written config with `devServer: { framework: 'next' }` needs the Next.js bump.
|
|
160
203
|
- Electron: the bundled Electron browser is deprecated. Cypress prints a warning; set `defaultBrowser: 'chrome'` (or another installed browser) in the Cypress config, or pass `--browser`, when the warning matters in CI.
|
|
161
204
|
|
|
@@ -53,7 +53,7 @@ async function updateCypress16ConfigOptions(tree) {
|
|
|
53
53
|
if (!isConfigProperty(property)) {
|
|
54
54
|
continue;
|
|
55
55
|
}
|
|
56
|
-
const name = getPropertyName(property.name);
|
|
56
|
+
const name = (0, migrations_1.getPropertyName)(property.name);
|
|
57
57
|
if (name === null) {
|
|
58
58
|
continue;
|
|
59
59
|
}
|
|
@@ -108,7 +108,7 @@ async function updateCypress16ConfigOptions(tree) {
|
|
|
108
108
|
// Removed options whose value carried intent the replacement needs. A
|
|
109
109
|
// shorthand value is unknown, so it gets the follow-up too.
|
|
110
110
|
function getRemovalFollowUp(property) {
|
|
111
|
-
const name = getPropertyName(property.name);
|
|
111
|
+
const name = (0, migrations_1.getPropertyName)(property.name);
|
|
112
112
|
if (name === 'execTimeout') {
|
|
113
113
|
return `removed \`${property.getText()}\`; \`cy.exec()\` is gone, set \`taskTimeout\` if the replacement \`cy.task()\` needs more than the 60000ms default`;
|
|
114
114
|
}
|
|
@@ -156,7 +156,7 @@ function getOptionBlocks(config) {
|
|
|
156
156
|
for (const block of topLevel) {
|
|
157
157
|
for (const property of block.properties) {
|
|
158
158
|
if (!isConfigProperty(property) ||
|
|
159
|
-
!TESTING_TYPE_BLOCKS.includes(getPropertyName(property.name))) {
|
|
159
|
+
!TESTING_TYPE_BLOCKS.includes((0, migrations_1.getPropertyName)(property.name))) {
|
|
160
160
|
continue;
|
|
161
161
|
}
|
|
162
162
|
const nested = (0, config_1.resolveObjectLiteral)(ts.isPropertyAssignment(property)
|
|
@@ -175,24 +175,11 @@ function isNxPresetCall(expression) {
|
|
|
175
175
|
NX_PRESET_CALLS.includes(expression.expression.text));
|
|
176
176
|
}
|
|
177
177
|
function hasProperty(block, name) {
|
|
178
|
-
return block.properties.some((property) => isConfigProperty(property) && getPropertyName(property.name) === name);
|
|
178
|
+
return block.properties.some((property) => isConfigProperty(property) && (0, migrations_1.getPropertyName)(property.name) === name);
|
|
179
179
|
}
|
|
180
180
|
function isConfigProperty(node) {
|
|
181
181
|
return (ts.isPropertyAssignment(node) || ts.isShorthandPropertyAssignment(node));
|
|
182
182
|
}
|
|
183
|
-
// A computed identifier (`[key]: value`) resolves at runtime, so it is skipped.
|
|
184
|
-
function getPropertyName(name) {
|
|
185
|
-
if (ts.isIdentifier(name) || isStringLiteralName(name)) {
|
|
186
|
-
return name.text;
|
|
187
|
-
}
|
|
188
|
-
if (ts.isComputedPropertyName(name) && isStringLiteralName(name.expression)) {
|
|
189
|
-
return name.expression.text;
|
|
190
|
-
}
|
|
191
|
-
return null;
|
|
192
|
-
}
|
|
193
|
-
function isStringLiteralName(node) {
|
|
194
|
-
return ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node);
|
|
195
|
-
}
|
|
196
183
|
function getValueText(property) {
|
|
197
184
|
return ts.isPropertyAssignment(property)
|
|
198
185
|
? property.initializer.getText()
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = upgradeToCypress16;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const devkit_1 = require("@nx/devkit");
|
|
6
|
+
const internal_1 = require("@nx/devkit/internal");
|
|
7
|
+
const internal_2 = require("@nx/js/internal");
|
|
8
|
+
const path_1 = require("path");
|
|
9
|
+
const semver_1 = require("semver");
|
|
10
|
+
const config_1 = require("../../utils/config");
|
|
11
|
+
const migrations_1 = require("../../utils/migrations");
|
|
12
|
+
const versions_1 = require("../../utils/versions");
|
|
13
|
+
const update_angular_zoneless_mount_import_1 = tslib_1.__importDefault(require("./update-angular-zoneless-mount-import"));
|
|
14
|
+
const update_cypress_16_config_options_1 = tslib_1.__importDefault(require("./update-cypress-16-config-options"));
|
|
15
|
+
const update_cypress_16_query_command_overwrites_1 = tslib_1.__importDefault(require("./update-cypress-16-query-command-overwrites"));
|
|
16
|
+
// Keep these versions pinned; versions.ts advances to later majors.
|
|
17
|
+
const CYPRESS_16_VERSIONS = {
|
|
18
|
+
cypress: '^16.0.0',
|
|
19
|
+
'@cypress/vite-dev-server': '^8.0.0',
|
|
20
|
+
'@cypress/webpack-dev-server': '^6.0.0',
|
|
21
|
+
};
|
|
22
|
+
// Cypress 16 component testing throws on a Vite below 8 (getVite.js in its
|
|
23
|
+
// bundled vite-dev-server); the peer `vite ^8.0.0` also excludes 8 prereleases.
|
|
24
|
+
const VITE_FLOOR = '8.0.0';
|
|
25
|
+
const NX_VITE_CT_PRESET = '@nx/remix/plugins/component-testing';
|
|
26
|
+
const NX_WEBPACK_CT_PRESETS = [
|
|
27
|
+
'@nx/angular/plugins/component-testing',
|
|
28
|
+
'@nx/next/plugins/component-testing',
|
|
29
|
+
'@nx/react/plugins/component-testing',
|
|
30
|
+
];
|
|
31
|
+
// Re-collects the Cypress 16 migrations: below their version, above every
|
|
32
|
+
// 23.2.x. `--from` is exclusive.
|
|
33
|
+
const REPLAN_COMMAND = `npx nx migrate @nx/cypress@${versions_1.nxVersion} --from=@nx/cypress@23.3.0-beta.0`;
|
|
34
|
+
const RERUN_COMMAND = `${REPLAN_COMMAND} && npx nx migrate --run-migration=@nx/cypress:upgrade-to-cypress-16`;
|
|
35
|
+
let ts;
|
|
36
|
+
async function upgradeToCypress16(tree) {
|
|
37
|
+
const declared = (0, devkit_1.readJson)(tree, 'package.json');
|
|
38
|
+
const declaredCypress = declared.dependencies?.cypress ?? declared.devDependencies?.cypress;
|
|
39
|
+
// Resolves a `catalog:` reference to its range; `addDependenciesToPackageJson`
|
|
40
|
+
// then writes the bump into the catalog.
|
|
41
|
+
const cypressRange = (0, devkit_1.getDependencyVersionFromPackageJson)(tree, 'cypress');
|
|
42
|
+
if (!cypressRange ||
|
|
43
|
+
!((0, semver_1.validRange)(cypressRange) || ['latest', 'next'].includes(cypressRange))) {
|
|
44
|
+
const reason = declaredCypress
|
|
45
|
+
? `\`cypress\` is declared as \`${declaredCypress}\` in package.json, which this migration cannot bump`
|
|
46
|
+
: '`cypress` is not declared in the root package.json';
|
|
47
|
+
return skipped(`${reason}. Move it to \`${CYPRESS_16_VERSIONS.cypress}\` by hand, install, and run \`${REPLAN_COMMAND} && npx nx migrate --run-migrations\` to apply the Cypress 16 migrations.`);
|
|
48
|
+
}
|
|
49
|
+
const blockers = await findViteBelowFloor(tree);
|
|
50
|
+
if (blockers.length > 0) {
|
|
51
|
+
return skipped(`Kept Cypress 15: Cypress 16 component testing requires Vite 8 and ${blockers.join(', ')}. Update \`vite\` to 8 where each listed config resolves it from, and any \`@vitejs/*\` plugin whose peer range excludes Vite 8. Install, then run \`${RERUN_COMMAND}\` to move to Cypress 16.`);
|
|
52
|
+
}
|
|
53
|
+
const bumped = Object.fromEntries(Object.entries(CYPRESS_16_VERSIONS).filter(([name]) => declared.devDependencies?.[name] || declared.dependencies?.[name]));
|
|
54
|
+
(0, devkit_1.addDependenciesToPackageJson)(tree, {}, bumped);
|
|
55
|
+
const results = [
|
|
56
|
+
await (0, update_cypress_16_config_options_1.default)(tree),
|
|
57
|
+
await (0, update_cypress_16_query_command_overwrites_1.default)(tree),
|
|
58
|
+
await (0, update_angular_zoneless_mount_import_1.default)(tree),
|
|
59
|
+
];
|
|
60
|
+
await (0, devkit_1.formatFiles)(tree);
|
|
61
|
+
return {
|
|
62
|
+
nextSteps: results.flatMap((result) => result?.nextSteps ?? []),
|
|
63
|
+
agentContext: [
|
|
64
|
+
`Bumped ${Object.entries(bumped)
|
|
65
|
+
.map(([name, version]) => `\`${name}\` to \`${version}\``)
|
|
66
|
+
.join(', ')} in package.json`,
|
|
67
|
+
...results.flatMap((result) => result?.agentContext ?? []),
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function skipped(message) {
|
|
72
|
+
return { nextSteps: [message], agentContext: [], skipAgentic: true };
|
|
73
|
+
}
|
|
74
|
+
// The Cypress config files whose component testing would run on a Vite below
|
|
75
|
+
// 8. Vite is resolved from each config's directory, as Cypress does. A config
|
|
76
|
+
// whose bundler cannot be read statically counts when such a Vite resolves.
|
|
77
|
+
async function findViteBelowFloor(tree) {
|
|
78
|
+
const cypressConfigPaths = new Set(await (0, devkit_1.globAsync)(tree, [`**/${config_1.CYPRESS_CONFIG_FILE_NAME_PATTERN}`]));
|
|
79
|
+
for await (const { cypressConfigPath } of (0, migrations_1.cypressProjectConfigs)(tree)) {
|
|
80
|
+
cypressConfigPaths.add(cypressConfigPath);
|
|
81
|
+
}
|
|
82
|
+
const blockers = [];
|
|
83
|
+
for (const cypressConfigPath of cypressConfigPaths) {
|
|
84
|
+
const bundler = tree.exists(cypressConfigPath)
|
|
85
|
+
? getComponentTestingBundler(tree.read(cypressConfigPath, 'utf-8'))
|
|
86
|
+
: 'unknown';
|
|
87
|
+
if (bundler === 'other') {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const viteVersion = (0, internal_1.getInstalledPackageVersion)('vite', [
|
|
91
|
+
(0, path_1.resolve)(tree.root, (0, path_1.dirname)(cypressConfigPath)),
|
|
92
|
+
]);
|
|
93
|
+
if (!viteVersion || !(0, semver_1.lt)(viteVersion, VITE_FLOOR)) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
blockers.push(bundler === 'vite'
|
|
97
|
+
? `${cypressConfigPath} resolves Vite ${viteVersion}`
|
|
98
|
+
: `${cypressConfigPath} resolves Vite ${viteVersion} and its bundler could not be determined statically`);
|
|
99
|
+
}
|
|
100
|
+
return blockers;
|
|
101
|
+
}
|
|
102
|
+
// Reads the `component` block of a config the way Cypress evaluates it:
|
|
103
|
+
// through same-file spreads and variables, last assignment wins. Anything that
|
|
104
|
+
// cannot be resolved statically (an imported spread, a preset options variable
|
|
105
|
+
// from another file, a computed bundler, a preset call not bound to an Nx
|
|
106
|
+
// preset module) is `unknown`.
|
|
107
|
+
function getComponentTestingBundler(contents) {
|
|
108
|
+
ts ??= (0, internal_2.ensureTypescript)();
|
|
109
|
+
const config = (0, config_1.resolveCypressConfigObject)(contents);
|
|
110
|
+
if (!config) {
|
|
111
|
+
return 'unknown';
|
|
112
|
+
}
|
|
113
|
+
const component = findProperty(config, 'component');
|
|
114
|
+
if (component === 'unresolved') {
|
|
115
|
+
return 'unknown';
|
|
116
|
+
}
|
|
117
|
+
if (!component) {
|
|
118
|
+
return 'other';
|
|
119
|
+
}
|
|
120
|
+
return classifyComponent(component, config.getSourceFile());
|
|
121
|
+
}
|
|
122
|
+
// The value assigned to `name` in `block`, following spreads of same-file
|
|
123
|
+
// objects, last member wins. `null` when the block has no such property,
|
|
124
|
+
// `'unresolved'` when the last member that could set it cannot be read: a
|
|
125
|
+
// spread of something unresolvable or a computed key.
|
|
126
|
+
function findProperty(block, name) {
|
|
127
|
+
const sourceFile = block.getSourceFile();
|
|
128
|
+
let value = null;
|
|
129
|
+
for (const property of block.properties) {
|
|
130
|
+
if (ts.isSpreadAssignment(property)) {
|
|
131
|
+
const spread = (0, config_1.resolveObjectLiteral)(property.expression, sourceFile);
|
|
132
|
+
value = spread ? (findProperty(spread, name) ?? value) : 'unresolved';
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const memberName = getMemberName(property);
|
|
136
|
+
if (memberName === 'unresolved') {
|
|
137
|
+
value = 'unresolved';
|
|
138
|
+
}
|
|
139
|
+
else if (memberName === name) {
|
|
140
|
+
value = ts.isPropertyAssignment(property)
|
|
141
|
+
? property.initializer
|
|
142
|
+
: ts.isShorthandPropertyAssignment(property)
|
|
143
|
+
? property.name
|
|
144
|
+
: 'unresolved';
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
// The static name of an object member, `'unresolved'` for a computed key that
|
|
150
|
+
// is not a string literal.
|
|
151
|
+
function getMemberName(property) {
|
|
152
|
+
const name = (0, migrations_1.getPropertyName)(property.name);
|
|
153
|
+
return name === null && ts.isComputedPropertyName(property.name)
|
|
154
|
+
? 'unresolved'
|
|
155
|
+
: name;
|
|
156
|
+
}
|
|
157
|
+
function classifyComponent(value, sourceFile) {
|
|
158
|
+
if (ts.isCallExpression(value)) {
|
|
159
|
+
if (!ts.isIdentifier(value.expression) ||
|
|
160
|
+
value.expression.text !== 'nxComponentTestingPreset') {
|
|
161
|
+
return 'unknown';
|
|
162
|
+
}
|
|
163
|
+
const presetImport = (0, migrations_1.getComponentTestingPresetImport)(sourceFile);
|
|
164
|
+
if (presetImport === NX_VITE_CT_PRESET) {
|
|
165
|
+
return 'vite';
|
|
166
|
+
}
|
|
167
|
+
if (!NX_WEBPACK_CT_PRESETS.includes(presetImport) ||
|
|
168
|
+
value.arguments.some((argument) => ts.isSpreadElement(argument))) {
|
|
169
|
+
return 'unknown';
|
|
170
|
+
}
|
|
171
|
+
// The other Nx presets bundle with webpack unless told otherwise.
|
|
172
|
+
if (!value.arguments[1]) {
|
|
173
|
+
return 'other';
|
|
174
|
+
}
|
|
175
|
+
const options = (0, config_1.resolveObjectLiteral)(value.arguments[1], sourceFile);
|
|
176
|
+
return options ? getBundler(options, 'other') : 'unknown';
|
|
177
|
+
}
|
|
178
|
+
const component = (0, config_1.resolveObjectLiteral)(value, sourceFile);
|
|
179
|
+
if (!component) {
|
|
180
|
+
return 'unknown';
|
|
181
|
+
}
|
|
182
|
+
return classifyComponentObject(component, sourceFile) ?? 'other';
|
|
183
|
+
}
|
|
184
|
+
// The bundler set by the last member of a component object that can set one:
|
|
185
|
+
// a `devServer` property or a spread carrying one. `null` when none does.
|
|
186
|
+
function classifyComponentObject(component, sourceFile) {
|
|
187
|
+
let bundler = null;
|
|
188
|
+
for (const property of component.properties) {
|
|
189
|
+
if (ts.isSpreadAssignment(property)) {
|
|
190
|
+
if (ts.isCallExpression(property.expression)) {
|
|
191
|
+
bundler = classifyComponent(property.expression, sourceFile);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
const spread = (0, config_1.resolveObjectLiteral)(property.expression, sourceFile);
|
|
195
|
+
bundler = spread
|
|
196
|
+
? (classifyComponentObject(spread, sourceFile) ?? bundler)
|
|
197
|
+
: 'unknown';
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const memberName = getMemberName(property);
|
|
201
|
+
if (memberName === 'unresolved') {
|
|
202
|
+
bundler = 'unknown';
|
|
203
|
+
}
|
|
204
|
+
else if (memberName === 'devServer') {
|
|
205
|
+
const devServer = ts.isPropertyAssignment(property)
|
|
206
|
+
? (0, config_1.resolveObjectLiteral)(property.initializer, sourceFile)
|
|
207
|
+
: ts.isShorthandPropertyAssignment(property)
|
|
208
|
+
? (0, config_1.resolveObjectLiteral)(property.name, sourceFile)
|
|
209
|
+
: null;
|
|
210
|
+
// Cypress requires `bundler` in `devServer`, so a block without a
|
|
211
|
+
// readable one holds it somewhere this migration cannot see.
|
|
212
|
+
bundler = devServer ? getBundler(devServer, 'unknown') : 'unknown';
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return bundler;
|
|
216
|
+
}
|
|
217
|
+
// The `bundler` string in an options object, `missing` when it has none.
|
|
218
|
+
function getBundler(options, missing) {
|
|
219
|
+
const bundler = findProperty(options, 'bundler');
|
|
220
|
+
if (bundler === 'unresolved') {
|
|
221
|
+
return 'unknown';
|
|
222
|
+
}
|
|
223
|
+
if (!bundler) {
|
|
224
|
+
return missing;
|
|
225
|
+
}
|
|
226
|
+
if (ts.isStringLiteral(bundler) ||
|
|
227
|
+
ts.isNoSubstitutionTemplateLiteral(bundler)) {
|
|
228
|
+
return bundler.text === 'vite' ? 'vite' : 'other';
|
|
229
|
+
}
|
|
230
|
+
return 'unknown';
|
|
231
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#### Upgrade Cypress 15 to 16
|
|
2
2
|
|
|
3
|
-
Bumps `cypress` to 16 together with `@cypress/vite-dev-server` 8 and `@cypress/webpack-dev-server` 6.
|
|
3
|
+
Bumps `cypress` to 16 together with the declared `@cypress/vite-dev-server` (to 8) and `@cypress/webpack-dev-server` (to 6); a package manager catalog entry is bumped in the catalog. Cypress 16 component testing rejects a Vite below 8, so the migration first reads the bundler of every Cypress config's `component` block, following same-file spreads and variables, and resolves `vite` from that config's directory, as Cypress does. When a Vite component testing project, or a config whose bundler cannot be read statically, resolves a Vite below 8, the workspace stays on Cypress 15 and the migration reports the command to run once Vite is on 8. It does not check a Cypress config with a custom name that no `@nx/cypress:cypress` target references, such as one run through `nx:run-commands`. A Vite that only e2e or webpack component testing projects resolve, such as a transitive Vite hoisted to the workspace root, does not hold the bump. Cypress 16 removes `Cypress.env()` (replaced by the asynchronous `cy.env()` for `env` values and `Cypress.expose()` for the new `expose` values), `cy.exec()` (use `cy.task()`) and `.end()`, drops `env` from test configuration overrides, turns the cookie and storage getters into queries, drops CoffeeScript support, and routes Chrome, Chromium and Edge through the native browser network. It requires Node 22, 24 or 26+, Vite 8 for Vite component testing, Angular 21 for Angular component testing and Next.js 15.0.4 for component testing on Cypress's `next` dev server framework, which the Nx preset does not use. Read more in the [Cypress 16 migration guide](https://docs.cypress.io/app/references/migration-guide#Migrating-to-Cypress-160).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
After the bump, the migration updates the renamed and removed config options, the `cypress/angular-zoneless` import and the `Cypress.Commands.overwrite()` calls for the new queries; a workspace already on Cypress 16 gets the same rewrites from their standalone migrations. The paired AI instructions walk an agent through the source changes that need judgment. The most common one is shown below.
|
|
6
6
|
|
|
7
7
|
#### Examples
|
|
8
8
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createNodesV2 = exports.createNodes = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
4
5
|
const internal_1 = require("@nx/devkit/internal");
|
|
5
6
|
const devkit_1 = require("@nx/devkit");
|
|
6
7
|
const js_1 = require("@nx/js");
|
|
7
8
|
const fs_1 = require("fs");
|
|
8
9
|
const path_1 = require("path");
|
|
10
|
+
const posix = tslib_1.__importStar(require("node:path/posix"));
|
|
9
11
|
const constants_1 = require("../utils/constants");
|
|
10
12
|
const cypressConfigGlob = '**/cypress.config.{js,ts,mjs,cjs}';
|
|
11
13
|
const defaultPatterns = {
|
|
@@ -76,7 +78,7 @@ async function createNodesInternal(configFilePath, options, context, pluginCache
|
|
|
76
78
|
};
|
|
77
79
|
}
|
|
78
80
|
function getTargetOutputs(outputs, subfolder) {
|
|
79
|
-
return outputs.map((output) => subfolder ?
|
|
81
|
+
return outputs.map((output) => subfolder ? posix.join(output, subfolder) : output);
|
|
80
82
|
}
|
|
81
83
|
// Normalize a Cypress config folder (e.g. videosFolder, screenshotsFolder) to a
|
|
82
84
|
// project-root-relative path, then append the per-spec subfolder. Cypress
|
|
@@ -479,13 +481,13 @@ async function getSpecFilesAndPatternsForTestType(cypressConfig, testType, works
|
|
|
479
481
|
const specPattern = cypressConfig[testType].specPattern ??
|
|
480
482
|
defaultPatterns[testType].specPattern;
|
|
481
483
|
const specPatterns = Array.isArray(specPattern)
|
|
482
|
-
? specPattern.map((p) =>
|
|
483
|
-
: [
|
|
484
|
+
? specPattern.map((p) => posix.join(projectRoot, p))
|
|
485
|
+
: [posix.join(projectRoot, specPattern)];
|
|
484
486
|
const excludeSpecPattern = cypressConfig[testType].excludeSpecPattern ??
|
|
485
487
|
defaultPatterns[testType].excludeSpecPattern;
|
|
486
488
|
const excludeSpecPatterns = Array.isArray(excludeSpecPattern)
|
|
487
|
-
? excludeSpecPattern.map((p) =>
|
|
488
|
-
: [
|
|
489
|
+
? excludeSpecPattern.map((p) => posix.join(projectRoot, p))
|
|
490
|
+
: [posix.join(projectRoot, excludeSpecPattern)];
|
|
489
491
|
const specFiles = await (0, internal_1.globWithWorkspaceContext)(workspaceRoot, specPatterns, excludeSpecPatterns);
|
|
490
492
|
return { specFiles, specPatterns, excludeSpecPatterns };
|
|
491
493
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ProjectConfiguration, type Tree } from '@nx/devkit';
|
|
2
|
-
import type { Expression, ObjectLiteralExpression, PropertyAssignment, SourceFile } from 'typescript';
|
|
2
|
+
import type { Expression, ObjectLiteralExpression, PropertyAssignment, PropertyName, SourceFile } from 'typescript';
|
|
3
3
|
export declare function cypressProjectConfigs(tree: Tree): AsyncGenerator<{
|
|
4
4
|
projectName: string;
|
|
5
5
|
projectConfig: ProjectConfiguration;
|
|
@@ -11,6 +11,8 @@ export declare function updateObjectProperty(config: ObjectLiteralExpression, pr
|
|
|
11
11
|
newName?: string;
|
|
12
12
|
newValue?: Expression;
|
|
13
13
|
}): ObjectLiteralExpression;
|
|
14
|
+
export declare function getComponentTestingPresetImport(sourceFile: SourceFile): string | null;
|
|
15
|
+
export declare function getPropertyName(name: PropertyName): string | null;
|
|
14
16
|
export declare function parseSourceFile(filePath: string, content: string): SourceFile;
|
|
15
17
|
/**
|
|
16
18
|
* Whether the file binds `name` as a runtime value anywhere: a variable,
|
|
@@ -4,55 +4,78 @@ exports.cypressProjectConfigs = cypressProjectConfigs;
|
|
|
4
4
|
exports.getObjectProperty = getObjectProperty;
|
|
5
5
|
exports.removeObjectProperty = removeObjectProperty;
|
|
6
6
|
exports.updateObjectProperty = updateObjectProperty;
|
|
7
|
+
exports.getComponentTestingPresetImport = getComponentTestingPresetImport;
|
|
8
|
+
exports.getPropertyName = getPropertyName;
|
|
7
9
|
exports.parseSourceFile = parseSourceFile;
|
|
8
10
|
exports.hasLocalValueBinding = hasLocalValueBinding;
|
|
9
11
|
const devkit_1 = require("@nx/devkit");
|
|
10
|
-
const internal_1 = require("@nx/
|
|
12
|
+
const internal_1 = require("@nx/devkit/internal");
|
|
13
|
+
const internal_2 = require("@nx/js/internal");
|
|
11
14
|
const path_1 = require("path");
|
|
12
15
|
const config_1 = require("./config");
|
|
13
16
|
let ts;
|
|
14
17
|
async function* cypressProjectConfigs(tree) {
|
|
15
18
|
const projects = (0, devkit_1.getProjects)(tree);
|
|
19
|
+
const targetDefaults = (0, devkit_1.readNxJson)(tree)?.targetDefaults;
|
|
16
20
|
for (const [projectName, projectConfig] of projects) {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
const cypressConfigPaths = new Set();
|
|
22
|
+
for (const [targetName, target] of Object.entries(projectConfig.targets ?? {})) {
|
|
23
|
+
if (target.command) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const merged = (0, internal_1.mergeTargetConfigurations)(target, (0, internal_1.readTargetDefaultsForTarget)(targetName, targetDefaults, target.executor, {
|
|
27
|
+
projectName,
|
|
28
|
+
projectNode: {
|
|
29
|
+
name: projectName,
|
|
30
|
+
type: projectConfig.projectType === 'application' ? 'app' : 'lib',
|
|
31
|
+
data: projectConfig,
|
|
32
|
+
},
|
|
33
|
+
}) ?? undefined);
|
|
34
|
+
if (merged.executor !== '@nx/cypress:cypress') {
|
|
35
|
+
continue;
|
|
26
36
|
}
|
|
27
|
-
for (const
|
|
28
|
-
|
|
37
|
+
for (const [, options] of allTargetOptions(merged)) {
|
|
38
|
+
if (options.cypressConfig) {
|
|
39
|
+
cypressConfigPaths.add(toTreePath(tree, (0, internal_1.interpolate)(options.cypressConfig, {
|
|
40
|
+
workspaceRoot: '.',
|
|
41
|
+
projectRoot: projectConfig.root,
|
|
42
|
+
projectName,
|
|
43
|
+
})));
|
|
44
|
+
}
|
|
29
45
|
}
|
|
30
46
|
}
|
|
31
|
-
|
|
32
|
-
// might be using the crystal plugin
|
|
47
|
+
if (cypressConfigPaths.size === 0) {
|
|
33
48
|
const result = await (0, devkit_1.globAsync)(tree, [
|
|
34
49
|
path_1.posix.join(projectConfig.root, config_1.CYPRESS_CONFIG_FILE_NAME_PATTERN),
|
|
35
50
|
]);
|
|
36
51
|
if (result.length > 0) {
|
|
37
|
-
|
|
38
|
-
projectName,
|
|
39
|
-
projectConfig,
|
|
40
|
-
cypressConfigPath: result[0],
|
|
41
|
-
};
|
|
52
|
+
cypressConfigPaths.add(result[0]);
|
|
42
53
|
}
|
|
43
54
|
}
|
|
55
|
+
for (const cypressConfigPath of cypressConfigPaths) {
|
|
56
|
+
yield { projectName, projectConfig, cypressConfigPath };
|
|
57
|
+
}
|
|
44
58
|
}
|
|
45
59
|
}
|
|
60
|
+
function toTreePath(tree, path) {
|
|
61
|
+
if (!(0, path_1.isAbsolute)(path)) {
|
|
62
|
+
return path_1.posix.normalize(path);
|
|
63
|
+
}
|
|
64
|
+
const relativePath = (0, path_1.relative)(tree.root, path);
|
|
65
|
+
return relativePath.startsWith('..') || (0, path_1.isAbsolute)(relativePath)
|
|
66
|
+
? path
|
|
67
|
+
: (0, devkit_1.normalizePath)(relativePath);
|
|
68
|
+
}
|
|
46
69
|
function getObjectProperty(config, name) {
|
|
47
|
-
ts ??= (0,
|
|
70
|
+
ts ??= (0, internal_2.ensureTypescript)();
|
|
48
71
|
return config.properties.find((p) => ts.isPropertyAssignment(p) && p.name.getText() === name);
|
|
49
72
|
}
|
|
50
73
|
function removeObjectProperty(config, property) {
|
|
51
|
-
ts ??= (0,
|
|
74
|
+
ts ??= (0, internal_2.ensureTypescript)();
|
|
52
75
|
return ts.factory.updateObjectLiteralExpression(config, config.properties.filter((p) => p !== property));
|
|
53
76
|
}
|
|
54
77
|
function updateObjectProperty(config, property, { newName, newValue }) {
|
|
55
|
-
ts ??= (0,
|
|
78
|
+
ts ??= (0, internal_2.ensureTypescript)();
|
|
56
79
|
if (!newName && !newValue) {
|
|
57
80
|
throw new Error('newName or newValue must be provided');
|
|
58
81
|
}
|
|
@@ -60,6 +83,57 @@ function updateObjectProperty(config, property, { newName, newValue }) {
|
|
|
60
83
|
? ts.factory.updatePropertyAssignment(p, newName ? ts.factory.createIdentifier(newName) : p.name, newValue ? newValue : p.initializer)
|
|
61
84
|
: p));
|
|
62
85
|
}
|
|
86
|
+
// Resolves the module specifier that binds the `nxComponentTestingPreset`
|
|
87
|
+
// identifier in a cypress config, covering both ESM `import` and CJS
|
|
88
|
+
// `require` forms. Returns null when no such binding is found.
|
|
89
|
+
function getComponentTestingPresetImport(sourceFile) {
|
|
90
|
+
ts ??= (0, internal_2.ensureTypescript)();
|
|
91
|
+
for (const statement of sourceFile.statements) {
|
|
92
|
+
if (ts.isImportDeclaration(statement) && statement.importClause) {
|
|
93
|
+
const namedBindings = statement.importClause.namedBindings;
|
|
94
|
+
if (namedBindings &&
|
|
95
|
+
ts.isNamedImports(namedBindings) &&
|
|
96
|
+
ts.isStringLiteral(statement.moduleSpecifier) &&
|
|
97
|
+
namedBindings.elements.some((element) => element.name.text === 'nxComponentTestingPreset')) {
|
|
98
|
+
return statement.moduleSpecifier.text;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (ts.isVariableStatement(statement)) {
|
|
102
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
103
|
+
const initializer = declaration.initializer;
|
|
104
|
+
if (!initializer ||
|
|
105
|
+
!ts.isCallExpression(initializer) ||
|
|
106
|
+
!ts.isIdentifier(initializer.expression) ||
|
|
107
|
+
initializer.expression.text !== 'require' ||
|
|
108
|
+
!ts.isObjectBindingPattern(declaration.name)) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const moduleSpecifier = initializer.arguments[0];
|
|
112
|
+
if (moduleSpecifier &&
|
|
113
|
+
ts.isStringLiteral(moduleSpecifier) &&
|
|
114
|
+
declaration.name.elements.some((element) => ts.isIdentifier(element.name) &&
|
|
115
|
+
element.name.text === 'nxComponentTestingPreset')) {
|
|
116
|
+
return moduleSpecifier.text;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
// Returns null for a computed name that is not a string literal.
|
|
124
|
+
function getPropertyName(name) {
|
|
125
|
+
ts ??= (0, internal_2.ensureTypescript)();
|
|
126
|
+
if (ts.isIdentifier(name) || isStringLiteralName(name)) {
|
|
127
|
+
return name.text;
|
|
128
|
+
}
|
|
129
|
+
if (ts.isComputedPropertyName(name) && isStringLiteralName(name.expression)) {
|
|
130
|
+
return name.expression.text;
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
function isStringLiteralName(node) {
|
|
135
|
+
return ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node);
|
|
136
|
+
}
|
|
63
137
|
function* allTargetOptions(target) {
|
|
64
138
|
if (target.options) {
|
|
65
139
|
yield [undefined, target.options];
|
|
@@ -76,7 +150,7 @@ function* allTargetOptions(target) {
|
|
|
76
150
|
// The extension picks the script kind, so `<T>x` in a .ts file and JSX in a
|
|
77
151
|
// .tsx file both parse; tsquery's `ast()` parses everything as TSX.
|
|
78
152
|
function parseSourceFile(filePath, content) {
|
|
79
|
-
ts ??= (0,
|
|
153
|
+
ts ??= (0, internal_2.ensureTypescript)();
|
|
80
154
|
return ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest,
|
|
81
155
|
/* setParentNodes */ true);
|
|
82
156
|
}
|
|
@@ -91,7 +165,7 @@ function parseSourceFile(filePath, content) {
|
|
|
91
165
|
* `Cypress` global.
|
|
92
166
|
*/
|
|
93
167
|
function hasLocalValueBinding(sourceFile, name) {
|
|
94
|
-
ts ??= (0,
|
|
168
|
+
ts ??= (0, internal_2.ensureTypescript)();
|
|
95
169
|
if (sourceFile.isDeclarationFile) {
|
|
96
170
|
return false;
|
|
97
171
|
}
|
package/migrations.json
CHANGED
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"documentation": "./dist/src/migrations/update-23-1-0/disable-webpack-ct-just-in-time-compile.md"
|
|
64
64
|
},
|
|
65
65
|
"update-cypress-16-config-options": {
|
|
66
|
-
"version": "23.3.0-beta.
|
|
66
|
+
"version": "23.3.0-beta.8",
|
|
67
67
|
"requires": {
|
|
68
68
|
"cypress": ">=16.0.0"
|
|
69
69
|
},
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"documentation": "./dist/src/migrations/update-23-3-0/update-cypress-16-config-options.md"
|
|
73
73
|
},
|
|
74
74
|
"update-cypress-16-query-command-overwrites": {
|
|
75
|
-
"version": "23.3.0-beta.
|
|
75
|
+
"version": "23.3.0-beta.8",
|
|
76
76
|
"requires": {
|
|
77
77
|
"cypress": ">=16.0.0"
|
|
78
78
|
},
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"documentation": "./dist/src/migrations/update-23-3-0/update-cypress-16-query-command-overwrites.md"
|
|
83
83
|
},
|
|
84
84
|
"update-angular-zoneless-mount-import": {
|
|
85
|
-
"version": "23.3.0-beta.
|
|
85
|
+
"version": "23.3.0-beta.8",
|
|
86
86
|
"requires": {
|
|
87
87
|
"cypress": ">=16.0.0"
|
|
88
88
|
},
|
|
@@ -91,13 +91,23 @@
|
|
|
91
91
|
"documentation": "./dist/src/migrations/update-23-3-0/update-angular-zoneless-mount-import.md"
|
|
92
92
|
},
|
|
93
93
|
"create-ai-instructions-for-cypress-16": {
|
|
94
|
-
"version": "23.3.0-beta.
|
|
94
|
+
"version": "23.3.0-beta.8",
|
|
95
95
|
"requires": {
|
|
96
96
|
"cypress": ">=16.0.0"
|
|
97
97
|
},
|
|
98
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
99
|
"prompt": "./dist/src/migrations/update-23-3-0/ai-instructions-for-cypress-16.md",
|
|
100
100
|
"documentation": "./dist/src/migrations/update-23-3-0/upgrade-to-cypress-16.md"
|
|
101
|
+
},
|
|
102
|
+
"upgrade-to-cypress-16": {
|
|
103
|
+
"version": "23.3.0-beta.8",
|
|
104
|
+
"requires": {
|
|
105
|
+
"cypress": ">=15.0.0 <16.0.0"
|
|
106
|
+
},
|
|
107
|
+
"description": "Moves a Cypress 15 workspace to Cypress 16: bumps `cypress` and the declared `@cypress/*-dev-server` packages unless a Vite component testing config, or a config whose bundler cannot be determined statically, resolves a Vite below 8, applies the Cypress 16 config option, `overwriteQuery` and `cypress/angular` rewrites, and hands the remaining source changes to an AI prompt.",
|
|
108
|
+
"implementation": "./dist/src/migrations/update-23-3-0/upgrade-to-cypress-16",
|
|
109
|
+
"prompt": "./dist/src/migrations/update-23-3-0/ai-instructions-for-cypress-16.md",
|
|
110
|
+
"documentation": "./dist/src/migrations/update-23-3-0/upgrade-to-cypress-16.md"
|
|
101
111
|
}
|
|
102
112
|
},
|
|
103
113
|
"packageJsonUpdates": {
|
|
@@ -197,29 +207,6 @@
|
|
|
197
207
|
"alwaysAddToPackageJson": false
|
|
198
208
|
}
|
|
199
209
|
}
|
|
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
|
-
}
|
|
223
210
|
}
|
|
224
211
|
}
|
|
225
212
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nx/cypress",
|
|
3
|
-
"version": "23.3.0-beta.
|
|
3
|
+
"version": "23.3.0-beta.9",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"files": [
|
|
@@ -84,9 +84,9 @@
|
|
|
84
84
|
"supportsOptionalMigrations": true
|
|
85
85
|
},
|
|
86
86
|
"dependencies": {
|
|
87
|
-
"@nx/devkit": "23.3.0-beta.
|
|
88
|
-
"@nx/eslint": "23.3.0-beta.
|
|
89
|
-
"@nx/js": "23.3.0-beta.
|
|
87
|
+
"@nx/devkit": "23.3.0-beta.9",
|
|
88
|
+
"@nx/eslint": "23.3.0-beta.9",
|
|
89
|
+
"@nx/js": "23.3.0-beta.9",
|
|
90
90
|
"@phenomnomnominal/tsquery": "~6.2.0",
|
|
91
91
|
"detect-port": "^2.1.0",
|
|
92
92
|
"semver": "^7.6.3",
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"tslib": "^2.3.0"
|
|
95
95
|
},
|
|
96
96
|
"devDependencies": {
|
|
97
|
-
"nx": "23.3.0-beta.
|
|
97
|
+
"nx": "23.3.0-beta.9"
|
|
98
98
|
},
|
|
99
99
|
"peerDependencies": {
|
|
100
100
|
"cypress": ">= 13 < 17"
|