@nx/cypress 24.0.0-pr.37405.acdaf31 → 24.0.0-rc.0
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/dist/internal.d.ts +1 -1
- package/dist/plugins/cypress-preset.js +47 -3
- package/dist/src/executors/cypress/{cypress.impl.d.ts → schema.d.ts} +0 -8
- package/dist/src/executors/cypress/schema.js +2 -0
- package/dist/src/generators/base-setup/base-setup.d.ts +5 -1
- package/dist/src/generators/base-setup/files/config-js-cjs/cypress.config.js__ext__ +1 -1
- package/dist/src/generators/base-setup/files/config-js-esm/cypress.config.js__ext__ +1 -1
- package/dist/src/generators/base-setup/files/config-ts-cjs/cypress.config.ts__ext__ +1 -1
- package/dist/src/generators/base-setup/files/config-ts-esm/cypress.config.ts__ext__ +1 -1
- package/dist/src/generators/component-configuration/component-configuration.js +7 -57
- package/dist/src/generators/component-configuration/schema.d.ts +0 -2
- package/dist/src/generators/configuration/configuration.d.ts +0 -5
- package/dist/src/generators/configuration/configuration.js +16 -68
- package/dist/src/generators/configuration/schema.json +0 -6
- package/dist/src/generators/convert-to-inferred/convert-to-inferred.js +26 -0
- package/dist/src/generators/convert-to-inferred/lib/add-build-target-to-ct-preset.d.ts +7 -0
- package/dist/src/generators/convert-to-inferred/lib/add-build-target-to-ct-preset.js +71 -0
- package/dist/src/generators/convert-to-inferred/lib/add-dev-server-target-to-config.js +3 -2
- package/dist/src/generators/convert-to-inferred/lib/add-exclude-spec-pattern.js +2 -1
- package/dist/src/generators/convert-to-inferred/lib/upsert-baseUrl.js +2 -1
- package/dist/src/generators/init/init.js +2 -45
- package/dist/src/generators/init/schema.d.ts +0 -1
- package/dist/src/migrations/update-23-3-0/adapt-query-command-overwrites.md +1 -1
- package/dist/src/migrations/update-23-3-0/ai-instructions-for-cypress-16.md +2 -2
- package/dist/src/migrations/update-23-3-0/upgrade-to-cypress-16.js +3 -2
- package/dist/src/migrations/update-24-0-0/finish-inferred-conversion.md +12 -0
- package/dist/src/migrations/update-24-0-0/remove-deprecated-executors.d.ts +6 -0
- package/dist/src/migrations/update-24-0-0/remove-deprecated-executors.js +10 -0
- package/dist/src/migrations/update-24-0-0/remove-deprecated-executors.md +61 -0
- package/dist/src/plugins/plugin.d.ts +8 -0
- package/dist/src/plugins/plugin.js +23 -5
- package/dist/src/utils/add-linter.d.ts +0 -5
- package/dist/src/utils/add-linter.js +1 -2
- package/dist/src/utils/config.d.ts +4 -0
- package/dist/src/utils/config.js +47 -2
- package/dist/src/utils/find-target-options.js +0 -24
- package/executors.json +1 -7
- package/migrations.json +8 -0
- package/package.json +6 -7
- package/dist/src/executors/cypress/cypress.impl.js +0 -136
- package/dist/src/executors/cypress/schema.json +0 -186
- package/dist/src/utils/deprecation.d.ts +0 -3
- package/dist/src/utils/deprecation.js +0 -20
- package/dist/src/utils/start-dev-server.d.ts +0 -9
- package/dist/src/utils/start-dev-server.js +0 -165
package/dist/internal.d.ts
CHANGED
|
@@ -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/
|
|
5
|
+
export type { CypressExecutorOptions } from './src/executors/cypress/schema';
|
|
@@ -130,6 +130,19 @@ function nxE2EPreset(pathToConfig, options) {
|
|
|
130
130
|
if (options?.bundler === 'vite') {
|
|
131
131
|
on('file:preprocessor', (0, preprocessor_vite_1.default)(options?.viteConfigOverrides));
|
|
132
132
|
}
|
|
133
|
+
// Set by Nx when the web server task already reported it is ready
|
|
134
|
+
const readyUrl = process.env.NX_READY_URL;
|
|
135
|
+
if (readyUrl && isBaseUrlOverridden(config, options)) {
|
|
136
|
+
console.log(`Testing against ${config.baseUrl} from --config or CYPRESS_BASE_URL, not the server Nx started on ${readyUrl}`);
|
|
137
|
+
await waitForServer(config.baseUrl, options?.webServerConfig, (ms) => `${config.baseUrl} did not respond within ${ms}ms. This can be configured in cypress.config.ts.`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (readyUrl) {
|
|
141
|
+
config.baseUrl = (0, devkit_1.getReadyUrl)(config.baseUrl);
|
|
142
|
+
console.log(`Testing against the server Nx started on ${config.baseUrl}`);
|
|
143
|
+
// Cypress only applies config changes returned from setupNodeEvents.
|
|
144
|
+
return config;
|
|
145
|
+
}
|
|
133
146
|
if (!options?.webServerCommands) {
|
|
134
147
|
return;
|
|
135
148
|
}
|
|
@@ -158,23 +171,54 @@ function nxE2EPreset(pathToConfig, options) {
|
|
|
158
171
|
};
|
|
159
172
|
return baseConfig;
|
|
160
173
|
}
|
|
161
|
-
|
|
174
|
+
// Cypress keeps the config file's values in rawJson, without --config, and
|
|
175
|
+
// reports CYPRESS_BASE_URL as from 'env'. A config file that reads
|
|
176
|
+
// CYPRESS_BASE_URL has it in rawJson too. Inferred CI targets pass ciBaseUrl.
|
|
177
|
+
function isBaseUrlOverridden(config, options) {
|
|
178
|
+
if (!config.baseUrl) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
if (config.resolved?.baseUrl?.from === 'env') {
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
const isInferredCiTarget = !!options?.ciWebServerCommand &&
|
|
185
|
+
config.env?.webServerCommand === options.ciWebServerCommand;
|
|
186
|
+
return (!isSameUrl(config.baseUrl, config.rawJson?.e2e?.baseUrl) &&
|
|
187
|
+
!(isInferredCiTarget && isSameUrl(config.baseUrl, options.ciBaseUrl)));
|
|
188
|
+
}
|
|
189
|
+
function isSameUrl(url, other) {
|
|
190
|
+
if (typeof other !== 'string') {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
return new URL(url).href === new URL(other).href;
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return url === other;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function waitForServer(url, webServerConfig, timeoutMessage = (ms) => `Web server failed to start in ${ms}ms. This can be configured in cypress.config.ts.`) {
|
|
162
201
|
return new Promise((resolve, reject) => {
|
|
163
202
|
let pollTimeout;
|
|
164
203
|
const { protocol } = new URL(url);
|
|
204
|
+
let settled = false;
|
|
165
205
|
const timeoutDuration = webServerConfig?.timeout ?? 60 * 1000;
|
|
166
206
|
const timeout = setTimeout(() => {
|
|
207
|
+
settled = true;
|
|
167
208
|
clearTimeout(pollTimeout);
|
|
168
|
-
reject(new Error(
|
|
209
|
+
reject(new Error(timeoutMessage(timeoutDuration)));
|
|
169
210
|
}, timeoutDuration);
|
|
170
211
|
const makeRequest = protocol === 'https:' ? https_1.request : http_1.request;
|
|
171
212
|
function pollForServer() {
|
|
172
213
|
const request = makeRequest(url, () => {
|
|
214
|
+
settled = true;
|
|
173
215
|
clearTimeout(timeout);
|
|
174
216
|
resolve();
|
|
175
217
|
});
|
|
176
218
|
request.on('error', () => {
|
|
177
|
-
|
|
219
|
+
if (!settled) {
|
|
220
|
+
pollTimeout = setTimeout(pollForServer, 100);
|
|
221
|
+
}
|
|
178
222
|
});
|
|
179
223
|
// Don't forget to end the request
|
|
180
224
|
request.end();
|
|
@@ -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
|
-
}>;
|
|
@@ -15,8 +15,12 @@ export interface CypressBaseSetupSchema {
|
|
|
15
15
|
* which merges in its own `component` block afterwards).
|
|
16
16
|
*/
|
|
17
17
|
e2ePreset?: {
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* NxCypressE2EPresetOptions rendered by `renderE2EPresetOptions`: indented
|
|
20
|
+
* JSON, except for values the config picks at runtime from `CI`.
|
|
21
|
+
*/
|
|
19
22
|
presetOptions: string;
|
|
23
|
+
/** A JS expression rendered by `renderE2EPresetOptions`, not a bare URL. */
|
|
20
24
|
baseUrl?: string;
|
|
21
25
|
};
|
|
22
26
|
}
|
|
@@ -7,6 +7,6 @@ const { defineConfig } = require('cypress');
|
|
|
7
7
|
module.exports = defineConfig({<% if (e2ePreset) { %>
|
|
8
8
|
e2e: {
|
|
9
9
|
...nxE2EPreset(__filename, <%- presetOptions %>),<% if (baseUrl) { %>
|
|
10
|
-
baseUrl:
|
|
10
|
+
baseUrl: <%- baseUrl %>,<% } %>
|
|
11
11
|
},
|
|
12
12
|
<% } %>});
|
|
@@ -7,6 +7,6 @@ import { defineConfig } from 'cypress';
|
|
|
7
7
|
export default defineConfig({<% if (e2ePreset) { %>
|
|
8
8
|
e2e: {
|
|
9
9
|
...nxE2EPreset(import.meta.url, <%- presetOptions %>),<% if (baseUrl) { %>
|
|
10
|
-
baseUrl:
|
|
10
|
+
baseUrl: <%- baseUrl %>,<% } %>
|
|
11
11
|
},
|
|
12
12
|
<% } %>});
|
|
@@ -7,6 +7,6 @@ const { defineConfig } = require('cypress');
|
|
|
7
7
|
module.exports = defineConfig({<% if (e2ePreset) { %>
|
|
8
8
|
e2e: {
|
|
9
9
|
...nxE2EPreset(__filename, <%- presetOptions %>),<% if (baseUrl) { %>
|
|
10
|
-
baseUrl:
|
|
10
|
+
baseUrl: <%- baseUrl %>,<% } %>
|
|
11
11
|
},
|
|
12
12
|
<% } %>});
|
|
@@ -7,6 +7,6 @@ import { defineConfig } from 'cypress';
|
|
|
7
7
|
export default defineConfig({<% if (e2ePreset) { %>
|
|
8
8
|
e2e: {
|
|
9
9
|
...nxE2EPreset(import.meta.url, <%- presetOptions %>),<% if (baseUrl) { %>
|
|
10
|
-
baseUrl:
|
|
10
|
+
baseUrl: <%- baseUrl %>,<% } %>
|
|
11
11
|
},
|
|
12
12
|
<% } %>});
|
|
@@ -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.
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
}
|
|
@@ -7,10 +7,6 @@ export interface CypressE2EConfigSchema {
|
|
|
7
7
|
js?: boolean;
|
|
8
8
|
skipFormat?: boolean;
|
|
9
9
|
enableTypedLinting?: boolean;
|
|
10
|
-
/**
|
|
11
|
-
* @deprecated Use `enableTypedLinting` instead. This option will be removed in Nx v24.
|
|
12
|
-
*/
|
|
13
|
-
setParserOptionsProject?: boolean;
|
|
14
10
|
skipPackageJson?: boolean;
|
|
15
11
|
bundler?: 'webpack' | 'vite' | 'none';
|
|
16
12
|
devServerTarget?: string;
|
|
@@ -21,7 +17,6 @@ export interface CypressE2EConfigSchema {
|
|
|
21
17
|
webServerCommands?: Record<string, string>;
|
|
22
18
|
ciWebServerCommand?: string;
|
|
23
19
|
ciBaseUrl?: string;
|
|
24
|
-
addPlugin?: boolean;
|
|
25
20
|
}
|
|
26
21
|
export declare function configurationGenerator(tree: Tree, options: CypressE2EConfigSchema): Promise<GeneratorCallback>;
|
|
27
22
|
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
|
|
32
|
+
else {
|
|
37
33
|
await (0, init_1.addPlugin)(tree, projectGraph, false);
|
|
38
34
|
}
|
|
39
|
-
|
|
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
|
|
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 (
|
|
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 (
|
|
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,
|
|
@@ -221,6 +212,7 @@ async function addFiles(tree, options, projectGraph, hasPlugin) {
|
|
|
221
212
|
ciWebServerCommand,
|
|
222
213
|
ciBaseUrl,
|
|
223
214
|
};
|
|
215
|
+
const renderedPreset = (0, config_1.renderE2EPresetOptions)(e2ePresetOptions, options.baseUrl);
|
|
224
216
|
(0, base_setup_1.addBaseCypressSetup)(tree, {
|
|
225
217
|
project: options.project,
|
|
226
218
|
directory: options.directory,
|
|
@@ -229,10 +221,8 @@ async function addFiles(tree, options, projectGraph, hasPlugin) {
|
|
|
229
221
|
// Generate a fresh config already containing the e2e preset (base-setup
|
|
230
222
|
// no-ops when a config already exists - see the merge below).
|
|
231
223
|
e2ePreset: {
|
|
232
|
-
presetOptions:
|
|
233
|
-
|
|
234
|
-
.join('\n '),
|
|
235
|
-
baseUrl: options.baseUrl,
|
|
224
|
+
presetOptions: renderedPreset.presetOptions.split('\n').join('\n '),
|
|
225
|
+
baseUrl: renderedPreset.baseUrl,
|
|
236
226
|
},
|
|
237
227
|
});
|
|
238
228
|
if (cypressConfigExists) {
|
|
@@ -242,48 +232,6 @@ async function addFiles(tree, options, projectGraph, hasPlugin) {
|
|
|
242
232
|
(0, devkit_1.toJS)(tree);
|
|
243
233
|
}
|
|
244
234
|
}
|
|
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
235
|
function createPackageJson(tree, options) {
|
|
288
236
|
const projectConfig = (0, devkit_1.readProjectConfiguration)(tree, options.project);
|
|
289
237
|
const packageJsonPath = (0, devkit_1.joinPathFragments)(projectConfig.root, 'package.json');
|
|
@@ -65,12 +65,6 @@
|
|
|
65
65
|
"description": "Whether to enable typed linting. For flat configs, this configures the recommended `parserOptions.projectService` and `tsconfigRootDir`. For legacy `.eslintrc` configs, this configures `parserOptions.project`. We do not enable this by default for lint performance reasons.",
|
|
66
66
|
"default": false
|
|
67
67
|
},
|
|
68
|
-
"setParserOptionsProject": {
|
|
69
|
-
"type": "boolean",
|
|
70
|
-
"description": "Deprecated alias for `enableTypedLinting`.",
|
|
71
|
-
"default": false,
|
|
72
|
-
"x-deprecated": "Use `enableTypedLinting` instead. This option will be removed in Nx v24."
|
|
73
|
-
},
|
|
74
68
|
"skipPackageJson": {
|
|
75
69
|
"type": "boolean",
|
|
76
70
|
"default": false,
|
|
@@ -5,6 +5,7 @@ const internal_1 = require("@nx/devkit/internal");
|
|
|
5
5
|
const devkit_1 = require("@nx/devkit");
|
|
6
6
|
const plugin_1 = require("../../plugins/plugin");
|
|
7
7
|
const assert_supported_cypress_version_1 = require("../../utils/assert-supported-cypress-version");
|
|
8
|
+
const add_build_target_to_ct_preset_1 = require("./lib/add-build-target-to-ct-preset");
|
|
8
9
|
const add_dev_server_target_to_config_1 = require("./lib/add-dev-server-target-to-config");
|
|
9
10
|
const add_exclude_spec_pattern_1 = require("./lib/add-exclude-spec-pattern");
|
|
10
11
|
const target_options_map_1 = require("./lib/target-options-map");
|
|
@@ -32,6 +33,9 @@ async function convertToInferred(tree, options) {
|
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
function postTargetTransformer(target, tree, projectDetails, inferredTargetConfiguration) {
|
|
36
|
+
if (target.options?.testingType === 'component') {
|
|
37
|
+
moveBuildTargetToComponentTestingPreset(tree, target, projectDetails.projectName);
|
|
38
|
+
}
|
|
35
39
|
if (target.options) {
|
|
36
40
|
handlePropertiesInOptions(tree, target.options, projectDetails.root, target);
|
|
37
41
|
if (Object.keys(target.options).length === 0) {
|
|
@@ -62,6 +66,28 @@ function postTargetTransformer(target, tree, projectDetails, inferredTargetConfi
|
|
|
62
66
|
}
|
|
63
67
|
return target;
|
|
64
68
|
}
|
|
69
|
+
function moveBuildTargetToComponentTestingPreset(tree, target, projectName) {
|
|
70
|
+
const defaultConfiguration = target.defaultConfiguration
|
|
71
|
+
? target.configurations?.[target.defaultConfiguration]
|
|
72
|
+
: undefined;
|
|
73
|
+
const buildTarget = defaultConfiguration?.devServerTarget ?? target.options.devServerTarget;
|
|
74
|
+
const { cypressConfig } = target.options;
|
|
75
|
+
if (buildTarget &&
|
|
76
|
+
!(cypressConfig &&
|
|
77
|
+
(0, add_build_target_to_ct_preset_1.addBuildTargetToComponentTestingPreset)(tree, cypressConfig, buildTarget))) {
|
|
78
|
+
devkit_1.logger.warn(`Could not set the component testing build target of project "${projectName}" in its Cypress config. Pass it to nxComponentTestingPreset as buildTarget: "${buildTarget}".`);
|
|
79
|
+
}
|
|
80
|
+
const removeDevServerTarget = (options, location) => {
|
|
81
|
+
if (options?.devServerTarget && options.devServerTarget !== buildTarget) {
|
|
82
|
+
devkit_1.logger.warn(`Removed devServerTarget "${options.devServerTarget}" from the ${location} of project "${projectName}". Component testing uses the single buildTarget passed to nxComponentTestingPreset.`);
|
|
83
|
+
}
|
|
84
|
+
delete options?.devServerTarget;
|
|
85
|
+
};
|
|
86
|
+
removeDevServerTarget(target.options, 'options');
|
|
87
|
+
for (const [name, configuration] of Object.entries(target.configurations ?? {})) {
|
|
88
|
+
removeDevServerTarget(configuration, `"${name}" configuration`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
65
91
|
function handlePropertiesInOptions(tree, options, projectRoot, target) {
|
|
66
92
|
let configFilePath;
|
|
67
93
|
if ('cypressConfig' in options) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Tree } from '@nx/devkit';
|
|
2
|
+
/**
|
|
3
|
+
* Sets `buildTarget` in the `nxComponentTestingPreset` options. A non-empty
|
|
4
|
+
* string literal is kept and any other value gets an `||` fallback. Returns
|
|
5
|
+
* false when there is no such call or the value can't be resolved statically.
|
|
6
|
+
*/
|
|
7
|
+
export declare function addBuildTargetToComponentTestingPreset(tree: Tree, configFilePath: string, buildTarget: string): boolean;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.addBuildTargetToComponentTestingPreset = addBuildTargetToComponentTestingPreset;
|
|
4
|
+
const internal_1 = require("@nx/js/internal");
|
|
5
|
+
const tsquery_1 = require("@phenomnomnominal/tsquery");
|
|
6
|
+
const migrations_1 = require("../../../utils/migrations");
|
|
7
|
+
let ts;
|
|
8
|
+
/**
|
|
9
|
+
* Sets `buildTarget` in the `nxComponentTestingPreset` options. A non-empty
|
|
10
|
+
* string literal is kept and any other value gets an `||` fallback. Returns
|
|
11
|
+
* false when there is no such call or the value can't be resolved statically.
|
|
12
|
+
*/
|
|
13
|
+
function addBuildTargetToComponentTestingPreset(tree, configFilePath, buildTarget) {
|
|
14
|
+
ts ??= (0, internal_1.ensureTypescript)();
|
|
15
|
+
const contents = tree.read(configFilePath, 'utf-8');
|
|
16
|
+
if (contents === null) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
const presetCall = (0, tsquery_1.query)((0, migrations_1.parseSourceFile)(configFilePath, contents), 'CallExpression').find((call) => ts.isIdentifier(call.expression) &&
|
|
20
|
+
call.expression.text === 'nxComponentTestingPreset');
|
|
21
|
+
const [pathToConfig, options] = presetCall?.arguments ?? [];
|
|
22
|
+
if (!pathToConfig) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const replace = (start, end, text) => tree.write(configFilePath, `${contents.slice(0, start)}${text}${contents.slice(end)}`);
|
|
26
|
+
const buildTargetLiteral = JSON.stringify(buildTarget);
|
|
27
|
+
if (!options) {
|
|
28
|
+
const insertAt = pathToConfig.getEnd();
|
|
29
|
+
replace(insertAt, insertAt, `, { buildTarget: ${buildTargetLiteral} }`);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
if (!ts.isObjectLiteralExpression(options)) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
const effectiveProperty = [...options.properties]
|
|
36
|
+
.reverse()
|
|
37
|
+
.find((property) => ts.isSpreadAssignment(property) ||
|
|
38
|
+
isUnresolvedComputedName(property.name) ||
|
|
39
|
+
(0, migrations_1.getPropertyName)(property.name) === 'buildTarget');
|
|
40
|
+
if (!effectiveProperty) {
|
|
41
|
+
// `__proto__: value` sets the prototype, which can provide buildTarget
|
|
42
|
+
if (options.properties.some((property) => ts.isPropertyAssignment(property) &&
|
|
43
|
+
!ts.isComputedPropertyName(property.name) &&
|
|
44
|
+
(0, migrations_1.getPropertyName)(property.name) === '__proto__')) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
const insertAt = options.getStart() + 1;
|
|
48
|
+
replace(insertAt, insertAt, ` buildTarget: ${buildTargetLiteral},`);
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
if (ts.isShorthandPropertyAssignment(effectiveProperty)) {
|
|
52
|
+
replace(effectiveProperty.getStart(), effectiveProperty.getEnd(), `buildTarget: buildTarget || ${buildTargetLiteral}`);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
if (!ts.isPropertyAssignment(effectiveProperty) ||
|
|
56
|
+
isUnresolvedComputedName(effectiveProperty.name)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
const value = effectiveProperty.initializer;
|
|
60
|
+
if (ts.isStringLiteralLike(value) && value.text) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
// The factory parenthesizes the left operand when precedence requires it
|
|
64
|
+
const needsParentheses = ts.isParenthesizedExpression(ts.factory.createBinaryExpression(value, ts.SyntaxKind.BarBarToken, ts.factory.createStringLiteral(buildTarget)).left);
|
|
65
|
+
const valueText = value.getText();
|
|
66
|
+
replace(value.getStart(), value.getEnd(), `${needsParentheses ? `(${valueText})` : valueText} || ${buildTargetLiteral}`);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
function isUnresolvedComputedName(name) {
|
|
70
|
+
return ts.isComputedPropertyName(name) && (0, migrations_1.getPropertyName)(name) === null;
|
|
71
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.addDevServerTargetToConfig = addDevServerTargetToConfig;
|
|
4
4
|
const tsquery_1 = require("@phenomnomnominal/tsquery");
|
|
5
|
+
const migrations_1 = require("../../../utils/migrations");
|
|
5
6
|
/**
|
|
6
7
|
* Add or update the webServerCommands and ciWebServerCommand options in the Cypress Config
|
|
7
8
|
* Scenarios Covered:
|
|
@@ -14,7 +15,7 @@ const tsquery_1 = require("@phenomnomnominal/tsquery");
|
|
|
14
15
|
*/
|
|
15
16
|
function addDevServerTargetToConfig(tree, configFilePath, webServerCommands, ciDevServerTarget) {
|
|
16
17
|
let configFileContents = tree.read(configFilePath, 'utf-8');
|
|
17
|
-
let sourceFile = (0,
|
|
18
|
+
let sourceFile = (0, migrations_1.parseSourceFile)(configFilePath, configFileContents);
|
|
18
19
|
const NX_E2E_PRESET_SELECTOR = 'PropertyAssignment:has(Identifier[name=e2e]) CallExpression:has(Identifier[name=nxE2EPreset])';
|
|
19
20
|
const nxE2ePresetOptionsNodes = (0, tsquery_1.query)(sourceFile, NX_E2E_PRESET_SELECTOR);
|
|
20
21
|
if (nxE2ePresetOptionsNodes.length !== 0) {
|
|
@@ -38,7 +39,7 @@ function addDevServerTargetToConfig(tree, configFilePath, webServerCommands, ciD
|
|
|
38
39
|
}
|
|
39
40
|
if (ciDevServerTarget) {
|
|
40
41
|
configFileContents = tree.read(configFilePath, 'utf-8');
|
|
41
|
-
sourceFile = (0,
|
|
42
|
+
sourceFile = (0, migrations_1.parseSourceFile)(configFilePath, configFileContents);
|
|
42
43
|
nxE2ePresetOptionsNode = (0, tsquery_1.query)(sourceFile, NX_E2E_PRESET_SELECTOR)[0];
|
|
43
44
|
const NX_E2E_PRESET_OPTIONS_SELECTOR = 'PropertyAssignment:has(Identifier[name=e2e]) CallExpression:has(Identifier[name=nxE2EPreset]) > ObjectLiteralExpression';
|
|
44
45
|
const optionsObjectNodes = (0, tsquery_1.query)(sourceFile, NX_E2E_PRESET_OPTIONS_SELECTOR);
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.addExcludeSpecPattern = addExcludeSpecPattern;
|
|
4
4
|
const tsquery_1 = require("@phenomnomnominal/tsquery");
|
|
5
|
+
const migrations_1 = require("../../../utils/migrations");
|
|
5
6
|
function addExcludeSpecPattern(tree, configFilePath, excludeSpecPattern) {
|
|
6
7
|
let configFileContents = tree.read(configFilePath, 'utf-8');
|
|
7
|
-
let sourceFile = (0,
|
|
8
|
+
let sourceFile = (0, migrations_1.parseSourceFile)(configFilePath, configFileContents);
|
|
8
9
|
const E2E_CONFIG_SELECTOR = 'PropertyAssignment:has(Identifier[name=e2e]) > ObjectLiteralExpression';
|
|
9
10
|
const e2eConfigNodes = (0, tsquery_1.query)(sourceFile, E2E_CONFIG_SELECTOR);
|
|
10
11
|
if (e2eConfigNodes.length !== 0) {
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.upsertBaseUrl = upsertBaseUrl;
|
|
4
4
|
const tsquery_1 = require("@phenomnomnominal/tsquery");
|
|
5
|
+
const migrations_1 = require("../../../utils/migrations");
|
|
5
6
|
function upsertBaseUrl(tree, configFilePath, baseUrlValueInProject) {
|
|
6
7
|
const configFileContents = tree.read(configFilePath, 'utf-8');
|
|
7
|
-
const sourceFile = (0,
|
|
8
|
+
const sourceFile = (0, migrations_1.parseSourceFile)(configFilePath, configFileContents);
|
|
8
9
|
const BASE_URL_SELECTOR = 'PropertyAssignment:has(Identifier[name=e2e]) PropertyAssignment:has(Identifier[name="baseUrl"])';
|
|
9
10
|
const baseUrlNodes = (0, tsquery_1.query)(sourceFile, BASE_URL_SELECTOR);
|
|
10
11
|
if (baseUrlNodes.length !== 0) {
|