@nx/cypress 17.1.2 → 17.2.0-beta.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/migrations.json +10 -0
- package/package.json +5 -5
- package/plugin.d.ts +1 -0
- package/plugin.js +6 -0
- package/plugins/cypress-preset.d.ts +3 -19
- package/plugins/cypress-preset.js +37 -10
- package/src/executors/cypress/cypress.impl.js +9 -152
- package/src/generators/component-configuration/component-configuration.js +25 -18
- package/src/generators/configuration/configuration.js +38 -11
- package/src/generators/init/init.js +41 -1
- package/src/generators/init/schema.d.ts +2 -0
- package/src/migrations/update-17-2-0/add-dev-server-targets-to-cypress-configs.d.ts +2 -0
- package/src/migrations/update-17-2-0/add-dev-server-targets-to-cypress-configs.js +125 -0
- package/src/migrations/update-17-2-0/add-nx-cypress-plugin.d.ts +2 -0
- package/src/migrations/update-17-2-0/add-nx-cypress-plugin.js +16 -0
- package/src/plugins/plugin.d.ts +8 -0
- package/src/plugins/plugin.js +243 -0
- package/src/utils/config.d.ts +2 -4
- package/src/utils/config.js +8 -7
- package/src/utils/start-dev-server.d.ts +9 -0
- package/src/utils/start-dev-server.js +157 -0
package/migrations.json
CHANGED
|
@@ -47,6 +47,16 @@
|
|
|
47
47
|
"version": "16.8.0-beta.4",
|
|
48
48
|
"description": "Update to Cypress v13. Most noteable change is video recording is off by default. This migration will only update if the workspace is already on Cypress v12. https://docs.cypress.io/guides/references/migration-guide#Migrating-to-Cypress-130",
|
|
49
49
|
"implementation": "./src/migrations/update-16-8-0/cypress-13"
|
|
50
|
+
},
|
|
51
|
+
"add-nx-metadata": {
|
|
52
|
+
"version": "17.2.0-beta.0",
|
|
53
|
+
"description": "Add devServerTargets into cypress.config.ts files for @nx/cypress/plugin",
|
|
54
|
+
"implementation": "./src/migrations/update-17-2-0/add-dev-server-targets-to-cypress-configs"
|
|
55
|
+
},
|
|
56
|
+
"add-nx-cypress-plugin": {
|
|
57
|
+
"version": "17.2.0-beta.0",
|
|
58
|
+
"description": "Add the @nx/cypress/plugin to nx.json plugins",
|
|
59
|
+
"implementation": "./src/migrations/update-17-2-0/add-nx-cypress-plugin"
|
|
50
60
|
}
|
|
51
61
|
},
|
|
52
62
|
"packageJsonUpdates": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nx/cypress",
|
|
3
|
-
"version": "17.
|
|
3
|
+
"version": "17.2.0-beta.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The Nx Plugin for Cypress contains executors and generators allowing your workspace to use the powerful Cypress integration testing capabilities.",
|
|
6
6
|
"repository": {
|
|
@@ -34,14 +34,14 @@
|
|
|
34
34
|
"migrations": "./migrations.json"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
+
"@nx/devkit": "17.2.0-beta.0",
|
|
38
|
+
"@nx/eslint": "17.2.0-beta.0",
|
|
39
|
+
"@nx/js": "17.2.0-beta.0",
|
|
37
40
|
"@phenomnomnominal/tsquery": "~5.0.1",
|
|
38
41
|
"detect-port": "^1.5.1",
|
|
39
42
|
"semver": "7.5.3",
|
|
40
43
|
"tslib": "^2.3.0",
|
|
41
|
-
"@
|
|
42
|
-
"@nx/js": "17.1.2",
|
|
43
|
-
"@nx/eslint": "17.1.2",
|
|
44
|
-
"@nrwl/cypress": "17.1.2"
|
|
44
|
+
"@nrwl/cypress": "17.2.0-beta.0"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"cypress": ">= 3 < 14"
|
package/plugin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createNodes, createDependencies } from './src/plugins/plugin';
|
package/plugin.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createDependencies = exports.createNodes = void 0;
|
|
4
|
+
var plugin_1 = require("./src/plugins/plugin");
|
|
5
|
+
Object.defineProperty(exports, "createNodes", { enumerable: true, get: function () { return plugin_1.createNodes; } });
|
|
6
|
+
Object.defineProperty(exports, "createDependencies", { enumerable: true, get: function () { return plugin_1.createDependencies; } });
|
|
@@ -30,26 +30,8 @@ export declare function nxBaseCypressPreset(pathToConfig: string, options?: {
|
|
|
30
30
|
* }
|
|
31
31
|
* })
|
|
32
32
|
*
|
|
33
|
-
* @param pathToConfig will be used to construct the output paths for videos and screenshots
|
|
34
33
|
*/
|
|
35
|
-
export declare function nxE2EPreset(pathToConfig: string, options?: NxCypressE2EPresetOptions):
|
|
36
|
-
fileServerFolder: string;
|
|
37
|
-
supportFile: string;
|
|
38
|
-
specPattern: string;
|
|
39
|
-
fixturesFolder: string;
|
|
40
|
-
videosFolder: string;
|
|
41
|
-
screenshotsFolder: string;
|
|
42
|
-
chromeWebSecurity: boolean;
|
|
43
|
-
} | {
|
|
44
|
-
setupNodeEvents(on: any): void;
|
|
45
|
-
fileServerFolder: string;
|
|
46
|
-
supportFile: string;
|
|
47
|
-
specPattern: string;
|
|
48
|
-
fixturesFolder: string;
|
|
49
|
-
videosFolder: string;
|
|
50
|
-
screenshotsFolder: string;
|
|
51
|
-
chromeWebSecurity: boolean;
|
|
52
|
-
};
|
|
34
|
+
export declare function nxE2EPreset(pathToConfig: string, options?: NxCypressE2EPresetOptions): any;
|
|
53
35
|
export type NxCypressE2EPresetOptions = {
|
|
54
36
|
bundler?: string;
|
|
55
37
|
/**
|
|
@@ -58,5 +40,7 @@ export type NxCypressE2EPresetOptions = {
|
|
|
58
40
|
* default is 'src'
|
|
59
41
|
**/
|
|
60
42
|
cypressDir?: string;
|
|
43
|
+
devServerTargets?: Record<string, string>;
|
|
44
|
+
ciDevServerTarget?: string;
|
|
61
45
|
};
|
|
62
46
|
export {};
|
|
@@ -5,6 +5,8 @@ const devkit_1 = require("@nx/devkit");
|
|
|
5
5
|
const path_1 = require("path");
|
|
6
6
|
const fs_1 = require("fs");
|
|
7
7
|
const preprocessor_vite_1 = require("../src/plugins/preprocessor-vite");
|
|
8
|
+
const ct_helpers_1 = require("../src/utils/ct-helpers");
|
|
9
|
+
const start_dev_server_1 = require("../src/utils/start-dev-server");
|
|
8
10
|
function nxBaseCypressPreset(pathToConfig, options) {
|
|
9
11
|
// used to set babel settings for react CT.
|
|
10
12
|
process.env.NX_CYPRESS_COMPONENT_TEST =
|
|
@@ -39,25 +41,50 @@ exports.nxBaseCypressPreset = nxBaseCypressPreset;
|
|
|
39
41
|
* }
|
|
40
42
|
* })
|
|
41
43
|
*
|
|
42
|
-
* @param pathToConfig will be used to construct the output paths for videos and screenshots
|
|
43
44
|
*/
|
|
44
45
|
function nxE2EPreset(pathToConfig, options) {
|
|
45
46
|
const basePath = options?.cypressDir || 'src';
|
|
46
|
-
const baseConfig = {
|
|
47
|
+
const baseConfig /** Cypress.EndToEndConfigOptions */ = {
|
|
47
48
|
...nxBaseCypressPreset(pathToConfig),
|
|
48
49
|
fileServerFolder: '.',
|
|
49
50
|
supportFile: `${basePath}/support/e2e.ts`,
|
|
50
51
|
specPattern: `${basePath}/**/*.cy.{js,jsx,ts,tsx}`,
|
|
51
52
|
fixturesFolder: `${basePath}/fixtures`,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
env: {
|
|
54
|
+
devServerTargets: options?.devServerTargets,
|
|
55
|
+
devServerTargetOptions: {},
|
|
56
|
+
ciDevServerTarget: options?.ciDevServerTarget,
|
|
57
|
+
},
|
|
58
|
+
async setupNodeEvents(on, config) {
|
|
59
|
+
if (options?.bundler === 'vite') {
|
|
57
60
|
on('file:preprocessor', (0, preprocessor_vite_1.default)());
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
}
|
|
62
|
+
if (!config.env.devServerTargets) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const devServerTarget = config.env.devServerTarget ?? config.env.devServerTargets['default'];
|
|
66
|
+
if (!devServerTarget) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (!config.baseUrl && devServerTarget) {
|
|
70
|
+
const graph = await (0, devkit_1.createProjectGraphAsync)();
|
|
71
|
+
const target = (0, devkit_1.parseTargetString)(devServerTarget, graph);
|
|
72
|
+
const context = (0, ct_helpers_1.createExecutorContext)(graph, graph.nodes[target.project].data?.targets, target.project, target.target, target.configuration);
|
|
73
|
+
const devServer = (0, start_dev_server_1.startDevServer)({
|
|
74
|
+
devServerTarget,
|
|
75
|
+
...config.env.devServerTargetOptions,
|
|
76
|
+
}, context);
|
|
77
|
+
on('after:run', () => {
|
|
78
|
+
devServer.return();
|
|
79
|
+
});
|
|
80
|
+
const devServerValue = (await devServer.next()).value;
|
|
81
|
+
if (!devServerValue) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
return { ...config, baseUrl: devServerValue.baseUrl };
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
};
|
|
61
88
|
return baseConfig;
|
|
62
89
|
}
|
|
63
90
|
exports.nxE2EPreset = nxE2EPreset;
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const devkit_1 = require("@nx/devkit");
|
|
4
|
-
const executor_utils_1 = require("nx/src/command-line/run/executor-utils");
|
|
5
4
|
const fs_1 = require("fs");
|
|
6
5
|
const path_1 = require("path");
|
|
7
6
|
const ct_helpers_1 = require("../../utils/ct-helpers");
|
|
8
7
|
const cypress_version_1 = require("../../utils/cypress-version");
|
|
9
|
-
const
|
|
8
|
+
const start_dev_server_1 = require("../../utils/start-dev-server");
|
|
10
9
|
const Cypress = require('cypress'); // @NOTE: Importing via ES6 messes the whole test dependencies.
|
|
11
10
|
async function cypressExecutor(options, context) {
|
|
12
11
|
options = normalizeOptions(options, context);
|
|
13
12
|
// this is used by cypress component testing presets to build the executor contexts with the correct configuration options.
|
|
14
13
|
process.env.NX_CYPRESS_TARGET_CONFIGURATION = context.configurationName;
|
|
15
14
|
let success;
|
|
16
|
-
for await (const devServerValues of startDevServer(options, context)) {
|
|
15
|
+
for await (const devServerValues of (0, start_dev_server_1.startDevServer)(options, context)) {
|
|
17
16
|
try {
|
|
18
17
|
success = await runCypress(devServerValues.baseUrl, {
|
|
19
18
|
...options,
|
|
@@ -90,50 +89,6 @@ A generator to migrate from v8 to v10 is provided. See https://nx.dev/cypress/v1
|
|
|
90
89
|
`);
|
|
91
90
|
}
|
|
92
91
|
}
|
|
93
|
-
async function* startDevServer(opts, context) {
|
|
94
|
-
// no dev server, return the provisioned base url
|
|
95
|
-
if (!opts.devServerTarget || opts.skipServe) {
|
|
96
|
-
yield { baseUrl: opts.baseUrl };
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
const parsedDevServerTarget = (0, devkit_1.parseTargetString)(opts.devServerTarget, context);
|
|
100
|
-
const [targetSupportsWatchOpt] = getValueFromSchema(context, parsedDevServerTarget, 'watch');
|
|
101
|
-
const overrides = {
|
|
102
|
-
// @NOTE: Do not forward watch option if not supported by the target dev server,
|
|
103
|
-
// this is relevant for running Cypress against dev server target that does not support this option,
|
|
104
|
-
// for instance @nguniversal/builders:ssr-dev-server.
|
|
105
|
-
...(targetSupportsWatchOpt ? { watch: opts.watch } : {}),
|
|
106
|
-
};
|
|
107
|
-
if (opts.port === 'cypress-auto') {
|
|
108
|
-
const freePort = await getPortForProject(context, parsedDevServerTarget);
|
|
109
|
-
overrides['port'] = freePort;
|
|
110
|
-
}
|
|
111
|
-
else if (opts.port !== undefined) {
|
|
112
|
-
overrides['port'] = opts.port;
|
|
113
|
-
// zero is a special case that means any valid port so there is no reason to try to 'lock it'
|
|
114
|
-
if (opts.port !== 0) {
|
|
115
|
-
const didLock = attemptToLockPort(opts.port);
|
|
116
|
-
if (!didLock) {
|
|
117
|
-
devkit_1.logger.warn((0, devkit_1.stripIndents) `${opts.port} is potentially already in use by another cypress run.
|
|
118
|
-
If the port is in use, try using a different port value or passing --port='cypress-auto' to find a free port.`);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
for await (const output of await (0, devkit_1.runExecutor)(parsedDevServerTarget, overrides, context)) {
|
|
123
|
-
if (!output.success && !opts.watch)
|
|
124
|
-
throw new Error('Could not compile application files');
|
|
125
|
-
if (!opts.baseUrl &&
|
|
126
|
-
!output.baseUrl &&
|
|
127
|
-
!output.info?.baseUrl &&
|
|
128
|
-
(output.port || output.info?.port)) {
|
|
129
|
-
output.baseUrl = `http://localhost:${output.port ?? output.info?.port}`;
|
|
130
|
-
}
|
|
131
|
-
yield {
|
|
132
|
-
baseUrl: opts.baseUrl || output.baseUrl || output.info?.baseUrl,
|
|
133
|
-
portLockFilePath: overrides.port && (0, path_1.join)(__dirname, `${overrides.port}.txt`),
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
92
|
/**
|
|
138
93
|
* @whatItDoes Initialize the Cypress test runner with the provided project configuration.
|
|
139
94
|
* By default, Cypress will run tests from the CLI without the GUI and provide directly the results in the console output.
|
|
@@ -154,8 +109,14 @@ async function runCypress(baseUrl, opts) {
|
|
|
154
109
|
if (opts.browser) {
|
|
155
110
|
options.browser = opts.browser;
|
|
156
111
|
}
|
|
112
|
+
options.env = {
|
|
113
|
+
devServerTarget: opts.devServerTarget,
|
|
114
|
+
};
|
|
157
115
|
if (opts.env) {
|
|
158
|
-
options.env =
|
|
116
|
+
options.env = {
|
|
117
|
+
...options.env,
|
|
118
|
+
...opts.env,
|
|
119
|
+
};
|
|
159
120
|
}
|
|
160
121
|
if (opts.spec) {
|
|
161
122
|
options.spec = opts.spec;
|
|
@@ -211,110 +172,6 @@ async function runCypress(baseUrl, opts) {
|
|
|
211
172
|
*/
|
|
212
173
|
return !result.totalFailed && !result.failures;
|
|
213
174
|
}
|
|
214
|
-
/**
|
|
215
|
-
* try to find a free port for the project to run on
|
|
216
|
-
* will return undefined if no port is found or the project doesn't have a port option
|
|
217
|
-
**/
|
|
218
|
-
async function getPortForProject(context, target, defaultPort = 4200) {
|
|
219
|
-
const fmtTarget = (0, devkit_1.targetToTargetString)(target);
|
|
220
|
-
const [hasPortOpt, schemaPortValue] = getValueFromSchema(context, target, 'port');
|
|
221
|
-
let freePort;
|
|
222
|
-
if (hasPortOpt) {
|
|
223
|
-
let normalizedPortValue;
|
|
224
|
-
if (!schemaPortValue) {
|
|
225
|
-
devkit_1.logger.info(`NX ${fmtTarget} did not have a defined port value, checking for free port with the default value of ${defaultPort}`);
|
|
226
|
-
normalizedPortValue = defaultPort;
|
|
227
|
-
}
|
|
228
|
-
else {
|
|
229
|
-
normalizedPortValue = Number(schemaPortValue);
|
|
230
|
-
}
|
|
231
|
-
if (isNaN(normalizedPortValue)) {
|
|
232
|
-
devkit_1.output.warn({
|
|
233
|
-
title: `Port Not a Number`,
|
|
234
|
-
bodyLines: [
|
|
235
|
-
`The port value found was not a number or can't be parsed to a number`,
|
|
236
|
-
`When reading the devServerTarget (${fmtTarget}) schema, expected ${schemaPortValue} to be a number but got NaN.`,
|
|
237
|
-
`Nx will use the default value of ${defaultPort} instead.`,
|
|
238
|
-
`You can manually specify a port by setting the 'port' option`,
|
|
239
|
-
],
|
|
240
|
-
});
|
|
241
|
-
normalizedPortValue = defaultPort;
|
|
242
|
-
}
|
|
243
|
-
try {
|
|
244
|
-
let attempts = 0;
|
|
245
|
-
// make sure when this check happens in parallel,
|
|
246
|
-
// we don't let the same port be used by multiple projects
|
|
247
|
-
do {
|
|
248
|
-
freePort = await detectPort(freePort || normalizedPortValue);
|
|
249
|
-
if (attemptToLockPort(freePort)) {
|
|
250
|
-
break;
|
|
251
|
-
}
|
|
252
|
-
attempts++;
|
|
253
|
-
// increment port in case the lock file isn't cleaned up
|
|
254
|
-
freePort++;
|
|
255
|
-
} while (attempts < 20);
|
|
256
|
-
devkit_1.logger.info(`NX Using port ${freePort} for ${fmtTarget}`);
|
|
257
|
-
}
|
|
258
|
-
catch (err) {
|
|
259
|
-
throw new Error((0, devkit_1.stripIndents) `Unable to find a free port for the dev server, ${fmtTarget}.
|
|
260
|
-
You can disable auto port detection by specifing a port or not passing a value to --port`);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
else {
|
|
264
|
-
devkit_1.output.warn({
|
|
265
|
-
title: `No Port Option Found`,
|
|
266
|
-
bodyLines: [
|
|
267
|
-
`The 'port' option is set to 'cypress-auto', but the devServerTarget (${fmtTarget}) does not have a port option.`,
|
|
268
|
-
`Because of this, Nx is unable to verify the port is free before starting the dev server.`,
|
|
269
|
-
`This might cause issues if the devServerTarget is trying to use a port that is already in use.`,
|
|
270
|
-
],
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
return freePort;
|
|
274
|
-
}
|
|
275
|
-
/**
|
|
276
|
-
* Check if the given target has the given property in it's options.
|
|
277
|
-
* if the property is does not have a default value or is not in the actual executor options,
|
|
278
|
-
* the value will be undefined even if it's in the executor schema.
|
|
279
|
-
**/
|
|
280
|
-
function getValueFromSchema(context, target, property) {
|
|
281
|
-
let targetOpts;
|
|
282
|
-
try {
|
|
283
|
-
targetOpts = (0, devkit_1.readTargetOptions)(target, context);
|
|
284
|
-
}
|
|
285
|
-
catch (e) {
|
|
286
|
-
throw new Error(`Unable to read the target options for ${(0, devkit_1.targetToTargetString)(target)}.
|
|
287
|
-
Are you sure this is a valid target?
|
|
288
|
-
Was trying to read the target for the property: '${property}', but got the following error:
|
|
289
|
-
${e.message || e}`);
|
|
290
|
-
}
|
|
291
|
-
let targetHasOpt = Object.keys(targetOpts).includes(property);
|
|
292
|
-
if (!targetHasOpt) {
|
|
293
|
-
// NOTE: readTargetOptions doesn't apply non defaulted values, i.e. @nx/vite has a port options but is optional
|
|
294
|
-
// so we double check the schema if readTargetOptions didn't return a value for the property
|
|
295
|
-
const projectConfig = context.projectsConfigurations?.projects?.[target.project];
|
|
296
|
-
const targetConfig = projectConfig.targets[target.target];
|
|
297
|
-
const [collection, executor] = targetConfig.executor.split(':');
|
|
298
|
-
const { schema } = (0, executor_utils_1.getExecutorInformation)(collection, executor, context.root);
|
|
299
|
-
// NOTE: schema won't have a default since readTargetOptions would have
|
|
300
|
-
// already set that and this check wouldn't need to be made
|
|
301
|
-
targetHasOpt = Object.keys(schema.properties).includes(property);
|
|
302
|
-
}
|
|
303
|
-
return [targetHasOpt, targetOpts[property]];
|
|
304
|
-
}
|
|
305
|
-
function attemptToLockPort(port) {
|
|
306
|
-
const portLockFilePath = (0, path_1.join)(__dirname, `${port}.txt`);
|
|
307
|
-
try {
|
|
308
|
-
if ((0, fs_1.existsSync)(portLockFilePath)) {
|
|
309
|
-
return false;
|
|
310
|
-
}
|
|
311
|
-
(0, fs_1.writeFileSync)(portLockFilePath, 'locked');
|
|
312
|
-
return true;
|
|
313
|
-
}
|
|
314
|
-
catch (err) {
|
|
315
|
-
return false;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
175
|
function cleanupTmpFile(path) {
|
|
319
176
|
try {
|
|
320
177
|
if (path && (0, fs_1.existsSync)(path)) {
|
|
@@ -7,11 +7,17 @@ const versions_1 = require("../../utils/versions");
|
|
|
7
7
|
const base_setup_1 = require("../base-setup/base-setup");
|
|
8
8
|
async function componentConfigurationGenerator(tree, options) {
|
|
9
9
|
const opts = normalizeOptions(options);
|
|
10
|
+
const nxJson = (0, devkit_1.readNxJson)(tree);
|
|
11
|
+
const hasPlugin = nxJson.plugins?.some((p) => typeof p === 'string'
|
|
12
|
+
? p === '@nx/cypress/plugin'
|
|
13
|
+
: p.plugin === '@nx/cypress/plugin');
|
|
10
14
|
const projectConfig = (0, devkit_1.readProjectConfiguration)(tree, opts.project);
|
|
11
15
|
const installDepsTask = updateDeps(tree, opts);
|
|
12
16
|
addProjectFiles(tree, projectConfig, opts);
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
if (!hasPlugin) {
|
|
18
|
+
addTargetToProject(tree, projectConfig, opts);
|
|
19
|
+
}
|
|
20
|
+
updateNxJsonConfiguration(tree, hasPlugin);
|
|
15
21
|
updateTsConfigForComponentTesting(tree, projectConfig);
|
|
16
22
|
if (!opts.skipFormat) {
|
|
17
23
|
await (0, devkit_1.formatFiles)(tree);
|
|
@@ -67,25 +73,26 @@ function addTargetToProject(tree, projectConfig, opts) {
|
|
|
67
73
|
};
|
|
68
74
|
(0, devkit_1.updateProjectConfiguration)(tree, opts.project, projectConfig);
|
|
69
75
|
}
|
|
70
|
-
function updateNxJsonConfiguration(tree) {
|
|
76
|
+
function updateNxJsonConfiguration(tree, hasPlugin) {
|
|
71
77
|
const nxJson = (0, devkit_1.readNxJson)(tree);
|
|
72
|
-
const
|
|
73
|
-
if (
|
|
74
|
-
|
|
78
|
+
const productionFileSet = nxJson.namedInputs?.production;
|
|
79
|
+
if (productionFileSet) {
|
|
80
|
+
nxJson.namedInputs.production = Array.from(new Set([
|
|
81
|
+
...productionFileSet,
|
|
82
|
+
'!{projectRoot}/cypress/**/*',
|
|
83
|
+
'!{projectRoot}/**/*.cy.[jt]s?(x)',
|
|
84
|
+
'!{projectRoot}/cypress.config.[jt]s',
|
|
85
|
+
]));
|
|
75
86
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (productionFileSet) {
|
|
82
|
-
nxJson.namedInputs.production = Array.from(new Set([
|
|
83
|
-
...productionFileSet,
|
|
84
|
-
'!{projectRoot}/cypress/**/*',
|
|
85
|
-
'!{projectRoot}/**/*.cy.[jt]s?(x)',
|
|
86
|
-
'!{projectRoot}/cypress.config.[jt]s',
|
|
87
|
-
]));
|
|
87
|
+
if (!hasPlugin) {
|
|
88
|
+
const cacheableOperations = nxJson.tasksRunnerOptions?.default?.options?.cacheableOperations;
|
|
89
|
+
if (cacheableOperations &&
|
|
90
|
+
!cacheableOperations.includes('component-test')) {
|
|
91
|
+
cacheableOperations.push('component-test');
|
|
88
92
|
}
|
|
93
|
+
nxJson.targetDefaults ??= {};
|
|
94
|
+
nxJson.targetDefaults['component-test'] ??= {};
|
|
95
|
+
nxJson.targetDefaults['component-test'].cache ??= true;
|
|
89
96
|
nxJson.targetDefaults['component-test'] ??= {};
|
|
90
97
|
nxJson.targetDefaults['component-test'].inputs ??= [
|
|
91
98
|
'default',
|
|
@@ -17,11 +17,18 @@ async function configurationGenerator(tree, options) {
|
|
|
17
17
|
if (!(0, cypress_version_1.installedCypressVersion)()) {
|
|
18
18
|
tasks.push(await (0, init_1.default)(tree, opts));
|
|
19
19
|
}
|
|
20
|
+
const projectGraph = await (0, devkit_1.createProjectGraphAsync)();
|
|
21
|
+
const nxJson = (0, devkit_1.readNxJson)(tree);
|
|
22
|
+
const hasPlugin = nxJson.plugins?.some((p) => typeof p === 'string'
|
|
23
|
+
? p === '@nx/cypress/plugin'
|
|
24
|
+
: p.plugin === '@nx/cypress/plugin');
|
|
20
25
|
if (opts.bundler === 'vite') {
|
|
21
26
|
tasks.push((0, devkit_1.addDependenciesToPackageJson)(tree, {}, { vite: versions_1.viteVersion }));
|
|
22
27
|
}
|
|
23
|
-
await addFiles(tree, opts);
|
|
24
|
-
|
|
28
|
+
await addFiles(tree, opts, projectGraph, hasPlugin);
|
|
29
|
+
if (!hasPlugin) {
|
|
30
|
+
addTarget(tree, opts);
|
|
31
|
+
}
|
|
25
32
|
const linterTask = await (0, add_linter_1.addLinterToCyProject)(tree, {
|
|
26
33
|
...opts,
|
|
27
34
|
cypressDir: opts.directory,
|
|
@@ -46,16 +53,20 @@ Rename or remove the existing e2e target.`);
|
|
|
46
53
|
In this case you need to provide a devServerTarget,'<projectName>:<targetName>[:<configName>]', or a baseUrl option`);
|
|
47
54
|
}
|
|
48
55
|
options.directory ??= 'src';
|
|
56
|
+
const devServerTarget = options.devServerTarget ??
|
|
57
|
+
(projectConfig.targets.serve ? `${options.project}:serve` : undefined);
|
|
58
|
+
if (!options.baseUrl && !devServerTarget) {
|
|
59
|
+
throw new Error('Either baseUrl or devServerTarget must be provided');
|
|
60
|
+
}
|
|
49
61
|
return {
|
|
50
62
|
...options,
|
|
51
63
|
bundler: options.bundler ?? 'webpack',
|
|
52
64
|
rootProject: options.rootProject ?? projectConfig.root === '.',
|
|
53
65
|
linter: options.linter ?? eslint_1.Linter.EsLint,
|
|
54
|
-
devServerTarget
|
|
55
|
-
(projectConfig.targets.serve ? `${options.project}:serve` : undefined),
|
|
66
|
+
devServerTarget,
|
|
56
67
|
};
|
|
57
68
|
}
|
|
58
|
-
async function addFiles(tree, options) {
|
|
69
|
+
async function addFiles(tree, options, projectGraph, hasPlugin) {
|
|
59
70
|
const projectConfig = (0, devkit_1.readProjectConfiguration)(tree, options.project);
|
|
60
71
|
const cyVersion = (0, cypress_version_1.installedCypressVersion)();
|
|
61
72
|
const filesToUse = cyVersion && cyVersion < 10 ? 'v9' : 'v10';
|
|
@@ -84,10 +95,29 @@ async function addFiles(tree, options) {
|
|
|
84
95
|
jsx: options.jsx,
|
|
85
96
|
});
|
|
86
97
|
const cyFile = (0, devkit_1.joinPathFragments)(projectConfig.root, 'cypress.config.ts');
|
|
98
|
+
let devServerTargets;
|
|
99
|
+
let ciDevServerTarget;
|
|
100
|
+
if (hasPlugin && !options.baseUrl && options.devServerTarget) {
|
|
101
|
+
devServerTargets = {};
|
|
102
|
+
devServerTargets.default = options.devServerTarget;
|
|
103
|
+
const parsedTarget = (0, devkit_1.parseTargetString)(options.devServerTarget, projectGraph);
|
|
104
|
+
const devServerProjectConfig = (0, devkit_1.readProjectConfiguration)(tree, parsedTarget.project);
|
|
105
|
+
// Add production e2e target if serve target is found
|
|
106
|
+
if (parsedTarget.configuration !== 'production' &&
|
|
107
|
+
devServerProjectConfig.targets[parsedTarget.target]?.configurations?.['production']) {
|
|
108
|
+
devServerTargets.production = `${parsedTarget.project}:${parsedTarget.target}:production`;
|
|
109
|
+
}
|
|
110
|
+
// Add ci/static e2e target if serve target is found
|
|
111
|
+
if (devServerProjectConfig.targets?.['serve-static']) {
|
|
112
|
+
ciDevServerTarget = `${parsedTarget.project}:serve-static`;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
87
115
|
const updatedCyConfig = await (0, config_1.addDefaultE2EConfig)(tree.read(cyFile, 'utf-8'), {
|
|
88
|
-
|
|
89
|
-
bundler: options.bundler,
|
|
90
|
-
|
|
116
|
+
cypressDir: options.directory,
|
|
117
|
+
bundler: options.bundler === 'vite' ? 'vite' : undefined,
|
|
118
|
+
devServerTargets,
|
|
119
|
+
ciDevServerTarget: ciDevServerTarget,
|
|
120
|
+
}, options.baseUrl);
|
|
91
121
|
tree.write(cyFile, updatedCyConfig);
|
|
92
122
|
}
|
|
93
123
|
if (cyVersion &&
|
|
@@ -148,9 +178,6 @@ function addTarget(tree, opts) {
|
|
|
148
178
|
};
|
|
149
179
|
}
|
|
150
180
|
}
|
|
151
|
-
else {
|
|
152
|
-
throw new Error('Either baseUrl or devServerTarget must be provided');
|
|
153
|
-
}
|
|
154
181
|
(0, devkit_1.updateProjectConfiguration)(tree, opts.project, projectConfig);
|
|
155
182
|
}
|
|
156
183
|
exports.default = configurationGenerator;
|
|
@@ -13,6 +13,7 @@ function setupE2ETargetDefaults(tree) {
|
|
|
13
13
|
nxJson.targetDefaults ??= {};
|
|
14
14
|
const productionFileSet = !!nxJson.namedInputs?.production;
|
|
15
15
|
nxJson.targetDefaults.e2e ??= {};
|
|
16
|
+
nxJson.targetDefaults.e2e.cache ??= true;
|
|
16
17
|
nxJson.targetDefaults.e2e.inputs ??= [
|
|
17
18
|
'default',
|
|
18
19
|
productionFileSet ? '^production' : '^default',
|
|
@@ -27,13 +28,52 @@ function updateDependencies(tree) {
|
|
|
27
28
|
'@types/node': versions_1.typesNodeVersion,
|
|
28
29
|
});
|
|
29
30
|
}
|
|
31
|
+
function addPlugin(tree) {
|
|
32
|
+
const nxJson = (0, devkit_1.readNxJson)(tree);
|
|
33
|
+
nxJson.plugins ??= [];
|
|
34
|
+
for (const plugin of nxJson.plugins) {
|
|
35
|
+
if (typeof plugin === 'string'
|
|
36
|
+
? plugin === '@nx/cypress/plugin'
|
|
37
|
+
: plugin.plugin === '@nx/cypress/plugin') {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
nxJson.plugins.push({
|
|
42
|
+
plugin: '@nx/cypress/plugin',
|
|
43
|
+
options: {
|
|
44
|
+
targetName: 'e2e',
|
|
45
|
+
componentTestingTargetName: 'component-test',
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
(0, devkit_1.updateNxJson)(tree, nxJson);
|
|
49
|
+
}
|
|
50
|
+
function updateProductionFileset(tree) {
|
|
51
|
+
const nxJson = (0, devkit_1.readNxJson)(tree);
|
|
52
|
+
const productionFileset = nxJson.namedInputs?.production;
|
|
53
|
+
if (productionFileset) {
|
|
54
|
+
nxJson.namedInputs.production = Array.from(new Set([
|
|
55
|
+
...productionFileset,
|
|
56
|
+
'!{projectRoot}/cypress/**/*',
|
|
57
|
+
'!{projectRoot}/**/*.cy.[jt]s?(x)',
|
|
58
|
+
'!{projectRoot}/cypress.config.[jt]s',
|
|
59
|
+
]));
|
|
60
|
+
}
|
|
61
|
+
(0, devkit_1.updateNxJson)(tree, nxJson);
|
|
62
|
+
}
|
|
30
63
|
async function cypressInitGenerator(tree, options) {
|
|
31
|
-
|
|
64
|
+
const addPlugins = process.env.NX_PCV3 === 'true';
|
|
65
|
+
updateProductionFileset(tree);
|
|
66
|
+
if (!addPlugins) {
|
|
67
|
+
setupE2ETargetDefaults(tree);
|
|
68
|
+
}
|
|
32
69
|
const tasks = [];
|
|
33
70
|
tasks.push(await (0, js_1.initGenerator)(tree, {
|
|
34
71
|
...options,
|
|
35
72
|
skipFormat: true,
|
|
36
73
|
}));
|
|
74
|
+
if (addPlugins) {
|
|
75
|
+
addPlugin(tree);
|
|
76
|
+
}
|
|
37
77
|
if (!options.skipPackageJson) {
|
|
38
78
|
tasks.push(updateDependencies(tree));
|
|
39
79
|
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const devkit_1 = require("@nx/devkit");
|
|
4
|
+
const executor_options_utils_1 = require("@nx/devkit/src/generators/executor-options-utils");
|
|
5
|
+
const typescript_1 = require("typescript");
|
|
6
|
+
function addDevServerTargets(tree, cypressConfig, devServerTargets) {
|
|
7
|
+
const contents = tree.read(cypressConfig, 'utf-8');
|
|
8
|
+
const sourceFile = (0, typescript_1.createSourceFile)(cypressConfig, contents, typescript_1.ScriptTarget.ESNext);
|
|
9
|
+
const ciDevServerTarget = devServerTargets.ci;
|
|
10
|
+
delete devServerTargets.ci;
|
|
11
|
+
const changes = [];
|
|
12
|
+
const visit = (node) => {
|
|
13
|
+
if ((0, typescript_1.isCallExpression)(node) &&
|
|
14
|
+
(0, typescript_1.isIdentifier)(node.expression) &&
|
|
15
|
+
node.expression.text === 'nxE2EPreset') {
|
|
16
|
+
const argumentContents = ', ' +
|
|
17
|
+
JSON.stringify({
|
|
18
|
+
devServerTargets,
|
|
19
|
+
ciDevServerTarget,
|
|
20
|
+
});
|
|
21
|
+
if (node.arguments.length === 1) {
|
|
22
|
+
changes.push({
|
|
23
|
+
type: devkit_1.ChangeType.Insert,
|
|
24
|
+
index: node.arguments[0].getEnd(),
|
|
25
|
+
text: argumentContents,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
const lastArgument = node.arguments[node.arguments.length - 1];
|
|
30
|
+
if ((0, typescript_1.isObjectLiteralExpression)(lastArgument)) {
|
|
31
|
+
const lastProperty = lastArgument.properties[lastArgument.properties.length - 1];
|
|
32
|
+
const trailingComma = lastArgument.properties.hasTrailingComma;
|
|
33
|
+
changes.push({
|
|
34
|
+
type: devkit_1.ChangeType.Insert,
|
|
35
|
+
index: lastProperty.getEnd(),
|
|
36
|
+
text: (trailingComma ? '' : ', ') +
|
|
37
|
+
'devServerTargets: ' +
|
|
38
|
+
JSON.stringify(devServerTargets) +
|
|
39
|
+
(ciDevServerTarget
|
|
40
|
+
? `, ciDevServerTarget: '${ciDevServerTarget}'`
|
|
41
|
+
: ''),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
(0, typescript_1.forEachChild)(node, visit);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
(0, typescript_1.forEachChild)(sourceFile, visit);
|
|
51
|
+
tree.write(cypressConfig, (0, devkit_1.applyChangesToString)(contents, changes));
|
|
52
|
+
}
|
|
53
|
+
async function update(tree) {
|
|
54
|
+
const projects = (0, devkit_1.getProjects)(tree);
|
|
55
|
+
const graph = await (0, devkit_1.createProjectGraphAsync)();
|
|
56
|
+
const devServerTargetsMap = new Map();
|
|
57
|
+
(0, executor_options_utils_1.forEachExecutorOptionsInGraph)(graph, '@nx/cypress:cypress', (options, project, target, configuration) => {
|
|
58
|
+
const targetConfig = projects.get(project).targets?.[target];
|
|
59
|
+
if (!targetConfig) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const cypressConfig = options.cypressConfig ?? targetConfig.options?.cypressConfig;
|
|
63
|
+
if (!cypressConfig) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const devServerTargets = {};
|
|
67
|
+
devServerTargets.default = targetConfig.options?.devServerTarget;
|
|
68
|
+
for (const [configuration, configurationOptions] of Object.entries(targetConfig.configurations ?? {})) {
|
|
69
|
+
devServerTargets[configuration] = configurationOptions.devServerTarget;
|
|
70
|
+
}
|
|
71
|
+
devServerTargetsMap.set(cypressConfig, devServerTargets);
|
|
72
|
+
});
|
|
73
|
+
for (const [cypressConfig, devServerTargets] of devServerTargetsMap) {
|
|
74
|
+
addDevServerTargets(tree, cypressConfig, devServerTargets);
|
|
75
|
+
}
|
|
76
|
+
// const configFiles = glob(tree, [createNodes[0]]);
|
|
77
|
+
//
|
|
78
|
+
// const proj = Object.fromEntries(getProjects(tree).entries());
|
|
79
|
+
//
|
|
80
|
+
// const rootMappings = createProjectRootMappingsFromProjectConfigurations(proj);
|
|
81
|
+
//
|
|
82
|
+
// for (const configFile of configFiles) {
|
|
83
|
+
// const siblings = tree.children(dirname(configFile));
|
|
84
|
+
// if (!siblings.includes('project.json')) {
|
|
85
|
+
// continue;
|
|
86
|
+
// }
|
|
87
|
+
//
|
|
88
|
+
// const projectName = findProjectForPath(configFile, rootMappings);
|
|
89
|
+
// const projectConfig = readProjectConfiguration(tree, projectName);
|
|
90
|
+
// const e2eTarget: TargetConfiguration<CypressExecutorOptions> =
|
|
91
|
+
// projectConfig.targets?.e2e;
|
|
92
|
+
//
|
|
93
|
+
// if (!e2eTarget || e2eTarget.executor !== '@nx/cypress:cypress') {
|
|
94
|
+
// continue;
|
|
95
|
+
// }
|
|
96
|
+
//
|
|
97
|
+
// nxMetadata.devServerTarget = e2eTarget.options?.devServerTarget;
|
|
98
|
+
// nxMetadata.productionDevServerTarget =
|
|
99
|
+
// e2eTarget.configurations?.production?.devServerTarget;
|
|
100
|
+
// nxMetadata.ciDevServerTarget =
|
|
101
|
+
// e2eTarget.configurations?.ci?.devServerTarget;
|
|
102
|
+
//
|
|
103
|
+
// if (Object.values(nxMetadata).filter((v) => !!v).length > 0) {
|
|
104
|
+
// let contents = tree.read(configFile, 'utf-8');
|
|
105
|
+
//
|
|
106
|
+
// contents =
|
|
107
|
+
// `import type { NxCypressMetadata } from '@nx/cypress/plugin';\n` +
|
|
108
|
+
// contents +
|
|
109
|
+
// `
|
|
110
|
+
//
|
|
111
|
+
// /**
|
|
112
|
+
// * This is metadata for the @nx/cypress/plugin
|
|
113
|
+
// */
|
|
114
|
+
// export const nx: NxCypressMetadata = ${JSON.stringify(
|
|
115
|
+
// nxMetadata,
|
|
116
|
+
// null,
|
|
117
|
+
// 2
|
|
118
|
+
// )};`;
|
|
119
|
+
//
|
|
120
|
+
// tree.write(configFile, contents);
|
|
121
|
+
// }
|
|
122
|
+
// }
|
|
123
|
+
await (0, devkit_1.formatFiles)(tree);
|
|
124
|
+
}
|
|
125
|
+
exports.default = update;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const devkit_1 = require("@nx/devkit");
|
|
4
|
+
const plugin_1 = require("../../plugins/plugin");
|
|
5
|
+
const find_project_for_path_1 = require("nx/src/project-graph/utils/find-project-for-path");
|
|
6
|
+
const replace_project_configuration_with_plugin_1 = require("@nx/devkit/src/utils/replace-project-configuration-with-plugin");
|
|
7
|
+
async function update(tree) {
|
|
8
|
+
const proj = Object.fromEntries((0, devkit_1.getProjects)(tree).entries());
|
|
9
|
+
const rootMappings = (0, find_project_for_path_1.createProjectRootMappingsFromProjectConfigurations)(proj);
|
|
10
|
+
await (0, replace_project_configuration_with_plugin_1.replaceProjectConfigurationsWithPlugin)(tree, rootMappings, '@nx/cypress/plugin', plugin_1.createNodes, {
|
|
11
|
+
targetName: 'e2e',
|
|
12
|
+
componentTestingTargetName: 'component-test',
|
|
13
|
+
});
|
|
14
|
+
await (0, devkit_1.formatFiles)(tree);
|
|
15
|
+
}
|
|
16
|
+
exports.default = update;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { CreateDependencies, CreateNodes } from '@nx/devkit';
|
|
2
|
+
export interface CypressPluginOptions {
|
|
3
|
+
ciTargetName?: string;
|
|
4
|
+
targetName?: string;
|
|
5
|
+
componentTestingTargetName?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const createDependencies: CreateDependencies;
|
|
8
|
+
export declare const createNodes: CreateNodes<CypressPluginOptions>;
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createNodes = exports.createDependencies = void 0;
|
|
4
|
+
const devkit_1 = require("@nx/devkit");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const internal_1 = require("@nx/js/src/internal");
|
|
7
|
+
const js_1 = require("@nx/js");
|
|
8
|
+
const project_configuration_utils_1 = require("nx/src/project-graph/utils/project-configuration-utils");
|
|
9
|
+
const get_named_inputs_1 = require("@nx/devkit/src/utils/get-named-inputs");
|
|
10
|
+
const fs_1 = require("fs");
|
|
11
|
+
const workspace_context_1 = require("nx/src/utils/workspace-context");
|
|
12
|
+
const calculate_hash_for_create_nodes_1 = require("@nx/devkit/src/utils/calculate-hash-for-create-nodes");
|
|
13
|
+
const cache_directory_1 = require("nx/src/utils/cache-directory");
|
|
14
|
+
const cachePath = (0, path_1.join)(cache_directory_1.projectGraphCacheDirectory, 'cypress.hash');
|
|
15
|
+
const targetsCache = (0, fs_1.existsSync)(cachePath) ? readTargetsCache() : {};
|
|
16
|
+
const calculatedTargets = {};
|
|
17
|
+
function readTargetsCache() {
|
|
18
|
+
return (0, devkit_1.readJsonFile)(cachePath);
|
|
19
|
+
}
|
|
20
|
+
function writeTargetsToCache(targets) {
|
|
21
|
+
(0, devkit_1.writeJsonFile)(cachePath, targets);
|
|
22
|
+
}
|
|
23
|
+
const createDependencies = () => {
|
|
24
|
+
writeTargetsToCache(calculatedTargets);
|
|
25
|
+
return [];
|
|
26
|
+
};
|
|
27
|
+
exports.createDependencies = createDependencies;
|
|
28
|
+
exports.createNodes = [
|
|
29
|
+
'**/cypress.config.{js,ts,mjs,mts,cjs,cts}',
|
|
30
|
+
(configFilePath, options, context) => {
|
|
31
|
+
options = normalizeOptions(options);
|
|
32
|
+
const projectRoot = (0, path_1.dirname)(configFilePath);
|
|
33
|
+
// Do not create a project if package.json and project.json isn't there.
|
|
34
|
+
const siblingFiles = (0, fs_1.readdirSync)((0, path_1.join)(context.workspaceRoot, projectRoot));
|
|
35
|
+
if (!siblingFiles.includes('package.json') &&
|
|
36
|
+
!siblingFiles.includes('project.json')) {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
const hash = (0, calculate_hash_for_create_nodes_1.calculateHashForCreateNodes)(projectRoot, options, context);
|
|
40
|
+
const targets = targetsCache[hash]
|
|
41
|
+
? targetsCache[hash]
|
|
42
|
+
: buildCypressTargets(configFilePath, projectRoot, options, context);
|
|
43
|
+
calculatedTargets[hash] = targets;
|
|
44
|
+
return {
|
|
45
|
+
projects: {
|
|
46
|
+
[projectRoot]: {
|
|
47
|
+
projectType: 'application',
|
|
48
|
+
targets,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
function getOutputs(projectRoot, cypressConfig, testingType) {
|
|
55
|
+
function getOutput(path) {
|
|
56
|
+
if (path.startsWith('..')) {
|
|
57
|
+
return (0, path_1.join)('{workspaceRoot}', (0, path_1.join)(projectRoot, path));
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
return (0, path_1.join)('{projectRoot}', path);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const { screenshotsFolder, videosFolder, e2e, component } = cypressConfig;
|
|
64
|
+
const outputs = ['{options.videosFolder}', '{options.screenshotsFolder}'];
|
|
65
|
+
if (videosFolder) {
|
|
66
|
+
outputs.push(getOutput(videosFolder));
|
|
67
|
+
}
|
|
68
|
+
if (screenshotsFolder) {
|
|
69
|
+
outputs.push(getOutput(screenshotsFolder));
|
|
70
|
+
}
|
|
71
|
+
switch (testingType) {
|
|
72
|
+
case 'e2e': {
|
|
73
|
+
if (e2e.videosFolder) {
|
|
74
|
+
outputs.push(getOutput(e2e.videosFolder));
|
|
75
|
+
}
|
|
76
|
+
if (e2e.screenshotsFolder) {
|
|
77
|
+
outputs.push(getOutput(e2e.screenshotsFolder));
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
case 'component': {
|
|
82
|
+
if (component.videosFolder) {
|
|
83
|
+
outputs.push(getOutput(component.videosFolder));
|
|
84
|
+
}
|
|
85
|
+
if (component.screenshotsFolder) {
|
|
86
|
+
outputs.push(getOutput(component.screenshotsFolder));
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return outputs;
|
|
92
|
+
}
|
|
93
|
+
function buildCypressTargets(configFilePath, projectRoot, options, context) {
|
|
94
|
+
const cypressConfig = getCypressConfig(configFilePath, context);
|
|
95
|
+
const namedInputs = (0, get_named_inputs_1.getNamedInputs)(projectRoot, context);
|
|
96
|
+
const baseTargetConfig = {
|
|
97
|
+
executor: '@nx/cypress:cypress',
|
|
98
|
+
options: {
|
|
99
|
+
cypressConfig: configFilePath,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
const targets = {};
|
|
103
|
+
if ('e2e' in cypressConfig) {
|
|
104
|
+
const e2eTargetDefaults = (0, project_configuration_utils_1.readTargetDefaultsForTarget)(options.targetName, context.nxJsonConfiguration.targetDefaults, '@nx/cypress:cypress');
|
|
105
|
+
targets[options.targetName] = {
|
|
106
|
+
...baseTargetConfig,
|
|
107
|
+
options: {
|
|
108
|
+
...baseTargetConfig.options,
|
|
109
|
+
testingType: 'e2e',
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
if (e2eTargetDefaults?.cache === undefined) {
|
|
113
|
+
targets[options.targetName].cache = true;
|
|
114
|
+
}
|
|
115
|
+
if (e2eTargetDefaults?.inputs === undefined) {
|
|
116
|
+
targets[options.targetName].inputs =
|
|
117
|
+
'production' in namedInputs
|
|
118
|
+
? ['default', '^production']
|
|
119
|
+
: ['default', '^default'];
|
|
120
|
+
}
|
|
121
|
+
if (e2eTargetDefaults?.outputs === undefined) {
|
|
122
|
+
targets[options.targetName].outputs = getOutputs(projectRoot, cypressConfig, 'e2e');
|
|
123
|
+
}
|
|
124
|
+
const cypressEnv = {
|
|
125
|
+
...cypressConfig.env,
|
|
126
|
+
...cypressConfig.e2e?.env,
|
|
127
|
+
};
|
|
128
|
+
const devServerTargets = cypressEnv?.devServerTargets;
|
|
129
|
+
if (devServerTargets?.default) {
|
|
130
|
+
targets[options.targetName].options.devServerTarget =
|
|
131
|
+
devServerTargets.default;
|
|
132
|
+
delete devServerTargets.default;
|
|
133
|
+
}
|
|
134
|
+
if (Object.keys(devServerTargets ?? {}).length > 0) {
|
|
135
|
+
targets[options.targetName].configurations ??= {};
|
|
136
|
+
for (const [configuration, devServerTarget] of Object.entries(devServerTargets ?? {})) {
|
|
137
|
+
targets[options.targetName].configurations[configuration] = {
|
|
138
|
+
devServerTarget,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const ciDevServerTarget = cypressEnv?.ciDevServerTarget;
|
|
143
|
+
if (ciDevServerTarget) {
|
|
144
|
+
const specPatterns = Array.isArray(cypressConfig.e2e.specPattern)
|
|
145
|
+
? cypressConfig.e2e.specPattern.map((p) => (0, path_1.join)(projectRoot, p))
|
|
146
|
+
: [(0, path_1.join)(projectRoot, cypressConfig.e2e.specPattern)];
|
|
147
|
+
const excludeSpecPatterns = !cypressConfig.e2e
|
|
148
|
+
.excludeSpecPattern
|
|
149
|
+
? cypressConfig.e2e.excludeSpecPattern
|
|
150
|
+
: Array.isArray(cypressConfig.e2e.excludeSpecPattern)
|
|
151
|
+
? cypressConfig.e2e.excludeSpecPattern.map((p) => (0, path_1.join)(projectRoot, p))
|
|
152
|
+
: [(0, path_1.join)(projectRoot, cypressConfig.e2e.excludeSpecPattern)];
|
|
153
|
+
const specFiles = (0, workspace_context_1.globWithWorkspaceContext)(context.workspaceRoot, specPatterns, excludeSpecPatterns);
|
|
154
|
+
const dependsOn = [];
|
|
155
|
+
const outputs = getOutputs(projectRoot, cypressConfig, 'e2e');
|
|
156
|
+
const inputs = 'production' in namedInputs
|
|
157
|
+
? ['default', '^production']
|
|
158
|
+
: ['default', '^default'];
|
|
159
|
+
for (const file of specFiles) {
|
|
160
|
+
const targetName = options.ciTargetName + '--' + file;
|
|
161
|
+
targets[targetName] = {
|
|
162
|
+
...targets[options.targetName],
|
|
163
|
+
outputs,
|
|
164
|
+
inputs,
|
|
165
|
+
cache: true,
|
|
166
|
+
configurations: undefined,
|
|
167
|
+
options: {
|
|
168
|
+
...targets[options.targetName].options,
|
|
169
|
+
devServerTarget: ciDevServerTarget,
|
|
170
|
+
spec: file,
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
dependsOn.push({
|
|
174
|
+
target: targetName,
|
|
175
|
+
projects: 'self',
|
|
176
|
+
params: 'forward',
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
targets[options.ciTargetName] ??= {};
|
|
180
|
+
targets[options.ciTargetName] = {
|
|
181
|
+
executor: 'nx:noop',
|
|
182
|
+
cache: true,
|
|
183
|
+
inputs,
|
|
184
|
+
outputs,
|
|
185
|
+
dependsOn,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if ('component' in cypressConfig) {
|
|
190
|
+
const componentTestingTargetDefaults = (0, project_configuration_utils_1.readTargetDefaultsForTarget)(options.componentTestingTargetName, context.nxJsonConfiguration.targetDefaults, '@nx/cypress:cypress');
|
|
191
|
+
// This will not override the e2e target if it is the same
|
|
192
|
+
targets[options.componentTestingTargetName] ??= {
|
|
193
|
+
...baseTargetConfig,
|
|
194
|
+
options: {
|
|
195
|
+
...baseTargetConfig.options,
|
|
196
|
+
testingType: 'component',
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
if (componentTestingTargetDefaults?.cache === undefined) {
|
|
200
|
+
targets[options.componentTestingTargetName].cache = true;
|
|
201
|
+
}
|
|
202
|
+
if (componentTestingTargetDefaults?.inputs === undefined) {
|
|
203
|
+
targets[options.componentTestingTargetName].inputs =
|
|
204
|
+
'production' in namedInputs
|
|
205
|
+
? ['default', '^production']
|
|
206
|
+
: ['default', '^default'];
|
|
207
|
+
}
|
|
208
|
+
if (componentTestingTargetDefaults?.outputs === undefined) {
|
|
209
|
+
targets[options.componentTestingTargetName].outputs = getOutputs(projectRoot, cypressConfig, 'component');
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return targets;
|
|
213
|
+
}
|
|
214
|
+
function getCypressConfig(configFilePath, context) {
|
|
215
|
+
const resolvedPath = (0, path_1.join)(context.workspaceRoot, configFilePath);
|
|
216
|
+
let module;
|
|
217
|
+
if (['.ts', '.mts', '.cts'].includes((0, path_1.extname)(configFilePath))) {
|
|
218
|
+
const tsConfigPath = (0, js_1.getRootTsConfigPath)();
|
|
219
|
+
if (tsConfigPath) {
|
|
220
|
+
const unregisterTsProject = (0, internal_1.registerTsProject)(tsConfigPath);
|
|
221
|
+
try {
|
|
222
|
+
module = require(resolvedPath);
|
|
223
|
+
}
|
|
224
|
+
finally {
|
|
225
|
+
unregisterTsProject();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
module = require(resolvedPath);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
module = require(resolvedPath);
|
|
234
|
+
}
|
|
235
|
+
return module.default ?? module;
|
|
236
|
+
}
|
|
237
|
+
function normalizeOptions(options) {
|
|
238
|
+
options ??= {};
|
|
239
|
+
options.targetName ??= 'e2e';
|
|
240
|
+
options.componentTestingTargetName ??= 'component-test';
|
|
241
|
+
options.ciTargetName ??= 'e2e-ci';
|
|
242
|
+
return options;
|
|
243
|
+
}
|
package/src/utils/config.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
bundler?: string;
|
|
4
|
-
}): Promise<string>;
|
|
1
|
+
import { NxCypressE2EPresetOptions } from '../../plugins/cypress-preset';
|
|
2
|
+
export declare function addDefaultE2EConfig(cyConfigContents: string, options: NxCypressE2EPresetOptions, baseUrl: string): Promise<string>;
|
|
5
3
|
/**
|
|
6
4
|
* Adds the nxComponentTestingPreset to the cypress config file
|
|
7
5
|
* Make sure after calling this the correct import statement is addeda
|
package/src/utils/config.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.addMountDefinition = exports.addDefaultCTConfig = exports.addDefaultE2EConfig = void 0;
|
|
4
4
|
const TS_QUERY_EXPORT_CONFIG_PREFIX = ':matches(ExportAssignment, BinaryExpression:has(Identifier[name="module"]):has(Identifier[name="exports"]))';
|
|
5
|
-
async function addDefaultE2EConfig(cyConfigContents, options) {
|
|
5
|
+
async function addDefaultE2EConfig(cyConfigContents, options, baseUrl) {
|
|
6
6
|
if (!cyConfigContents) {
|
|
7
7
|
throw new Error('The passed in cypress config file is empty!');
|
|
8
8
|
}
|
|
@@ -10,21 +10,22 @@ async function addDefaultE2EConfig(cyConfigContents, options) {
|
|
|
10
10
|
const testingTypeConfig = tsquery.query(cyConfigContents, `${TS_QUERY_EXPORT_CONFIG_PREFIX} PropertyAssignment:has(Identifier[name="e2e"])`);
|
|
11
11
|
let updatedConfigContents = cyConfigContents;
|
|
12
12
|
if (testingTypeConfig.length === 0) {
|
|
13
|
-
const configValue = options
|
|
14
|
-
? `nxE2EPreset(__filename, { cypressDir: '${options.directory}', bundler: 'vite' })`
|
|
15
|
-
: `nxE2EPreset(__filename, { cypressDir: '${options.directory}' })`;
|
|
13
|
+
const configValue = `nxE2EPreset(__filename, ${JSON.stringify(options)})`;
|
|
16
14
|
updatedConfigContents = tsquery.replace(cyConfigContents, `${TS_QUERY_EXPORT_CONFIG_PREFIX} ObjectLiteralExpression:first-child`, (node) => {
|
|
15
|
+
let baseUrlContents = baseUrl ? `,\nbaseUrl: '${baseUrl}'` : '';
|
|
17
16
|
if (node.properties.length > 0) {
|
|
18
17
|
return `{
|
|
19
18
|
${node.properties.map((p) => p.getText()).join(',\n')},
|
|
20
|
-
e2e:
|
|
19
|
+
e2e: { ...${configValue}${baseUrlContents} }
|
|
21
20
|
}`;
|
|
22
21
|
}
|
|
23
22
|
return `{
|
|
24
|
-
e2e:
|
|
23
|
+
e2e: { ...${configValue}${baseUrlContents} }
|
|
25
24
|
}`;
|
|
26
25
|
});
|
|
27
|
-
|
|
26
|
+
return `import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
|
27
|
+
|
|
28
|
+
${updatedConfigContents}`;
|
|
28
29
|
}
|
|
29
30
|
return updatedConfigContents;
|
|
30
31
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ExecutorContext } from '@nx/devkit';
|
|
2
|
+
import { CypressExecutorOptions } from '../executors/cypress/cypress.impl';
|
|
3
|
+
export declare function startDevServer(opts: Omit<CypressExecutorOptions, 'cypressConfig'>, context: ExecutorContext): AsyncGenerator<{
|
|
4
|
+
baseUrl: any;
|
|
5
|
+
portLockFilePath?: undefined;
|
|
6
|
+
} | {
|
|
7
|
+
baseUrl: any;
|
|
8
|
+
portLockFilePath: string;
|
|
9
|
+
}, void, unknown>;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.startDevServer = void 0;
|
|
4
|
+
const devkit_1 = require("@nx/devkit");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const detectPort = require("detect-port");
|
|
7
|
+
const executor_utils_1 = require("nx/src/command-line/run/executor-utils");
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
async function* startDevServer(opts, context) {
|
|
10
|
+
// no dev server, return the provisioned base url
|
|
11
|
+
if (!opts.devServerTarget || opts.skipServe) {
|
|
12
|
+
yield { baseUrl: opts.baseUrl };
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const parsedDevServerTarget = (0, devkit_1.parseTargetString)(opts.devServerTarget, context);
|
|
16
|
+
const [targetSupportsWatchOpt] = getValueFromSchema(context, parsedDevServerTarget, 'watch');
|
|
17
|
+
const overrides = {
|
|
18
|
+
// @NOTE: Do not forward watch option if not supported by the target dev server,
|
|
19
|
+
// this is relevant for running Cypress against dev server target that does not support this option,
|
|
20
|
+
// for instance @nguniversal/builders:ssr-dev-server.
|
|
21
|
+
...(targetSupportsWatchOpt ? { watch: opts.watch } : {}),
|
|
22
|
+
};
|
|
23
|
+
if (opts.port === 'cypress-auto') {
|
|
24
|
+
const freePort = await getPortForProject(context, parsedDevServerTarget);
|
|
25
|
+
overrides['port'] = freePort;
|
|
26
|
+
}
|
|
27
|
+
else if (opts.port !== undefined) {
|
|
28
|
+
overrides['port'] = opts.port;
|
|
29
|
+
// zero is a special case that means any valid port so there is no reason to try to 'lock it'
|
|
30
|
+
if (opts.port !== 0) {
|
|
31
|
+
const didLock = attemptToLockPort(opts.port);
|
|
32
|
+
if (!didLock) {
|
|
33
|
+
devkit_1.logger.warn((0, devkit_1.stripIndents) `${opts.port} is potentially already in use by another cypress run.
|
|
34
|
+
If the port is in use, try using a different port value or passing --port='cypress-auto' to find a free port.`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
for await (const output of await (0, devkit_1.runExecutor)(parsedDevServerTarget, overrides, context)) {
|
|
39
|
+
if (!output.success && !opts.watch)
|
|
40
|
+
throw new Error('Could not compile application files');
|
|
41
|
+
if (!opts.baseUrl &&
|
|
42
|
+
!output.baseUrl &&
|
|
43
|
+
!output.info?.baseUrl &&
|
|
44
|
+
(output.port || output.info?.port)) {
|
|
45
|
+
output.baseUrl = `http://localhost:${output.info.port}`;
|
|
46
|
+
}
|
|
47
|
+
yield {
|
|
48
|
+
baseUrl: opts.baseUrl || output.baseUrl || output.info?.baseUrl,
|
|
49
|
+
portLockFilePath: overrides.port && (0, path_1.join)(__dirname, `${overrides.port}.txt`),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.startDevServer = startDevServer;
|
|
54
|
+
/**
|
|
55
|
+
* try to find a free port for the project to run on
|
|
56
|
+
* will return undefined if no port is found or the project doesn't have a port option
|
|
57
|
+
**/
|
|
58
|
+
async function getPortForProject(context, target, defaultPort = 4200) {
|
|
59
|
+
const fmtTarget = (0, devkit_1.targetToTargetString)(target);
|
|
60
|
+
const [hasPortOpt, schemaPortValue] = getValueFromSchema(context, target, 'port');
|
|
61
|
+
let freePort;
|
|
62
|
+
if (hasPortOpt) {
|
|
63
|
+
let normalizedPortValue;
|
|
64
|
+
if (!schemaPortValue) {
|
|
65
|
+
devkit_1.logger.info(`NX ${fmtTarget} did not have a defined port value, checking for free port with the default value of ${defaultPort}`);
|
|
66
|
+
normalizedPortValue = defaultPort;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
normalizedPortValue = Number(schemaPortValue);
|
|
70
|
+
}
|
|
71
|
+
if (isNaN(normalizedPortValue)) {
|
|
72
|
+
devkit_1.output.warn({
|
|
73
|
+
title: `Port Not a Number`,
|
|
74
|
+
bodyLines: [
|
|
75
|
+
`The port value found was not a number or can't be parsed to a number`,
|
|
76
|
+
`When reading the devServerTarget (${fmtTarget}) schema, expected ${schemaPortValue} to be a number but got NaN.`,
|
|
77
|
+
`Nx will use the default value of ${defaultPort} instead.`,
|
|
78
|
+
`You can manually specify a port by setting the 'port' option`,
|
|
79
|
+
],
|
|
80
|
+
});
|
|
81
|
+
normalizedPortValue = defaultPort;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
let attempts = 0;
|
|
85
|
+
// make sure when this check happens in parallel,
|
|
86
|
+
// we don't let the same port be used by multiple projects
|
|
87
|
+
do {
|
|
88
|
+
freePort = await detectPort(freePort || normalizedPortValue);
|
|
89
|
+
if (attemptToLockPort(freePort)) {
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
attempts++;
|
|
93
|
+
// increment port in case the lock file isn't cleaned up
|
|
94
|
+
freePort++;
|
|
95
|
+
} while (attempts < 20);
|
|
96
|
+
devkit_1.logger.info(`NX Using port ${freePort} for ${fmtTarget}`);
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
throw new Error((0, devkit_1.stripIndents) `Unable to find a free port for the dev server, ${fmtTarget}.
|
|
100
|
+
You can disable auto port detection by specifing a port or not passing a value to --port`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
devkit_1.output.warn({
|
|
105
|
+
title: `No Port Option Found`,
|
|
106
|
+
bodyLines: [
|
|
107
|
+
`The 'port' option is set to 'cypress-auto', but the devServerTarget (${fmtTarget}) does not have a port option.`,
|
|
108
|
+
`Because of this, Nx is unable to verify the port is free before starting the dev server.`,
|
|
109
|
+
`This might cause issues if the devServerTarget is trying to use a port that is already in use.`,
|
|
110
|
+
],
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return freePort;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Check if the given target has the given property in it's options.
|
|
117
|
+
* if the property is does not have a default value or is not in the actual executor options,
|
|
118
|
+
* the value will be undefined even if it's in the executor schema.
|
|
119
|
+
**/
|
|
120
|
+
function getValueFromSchema(context, target, property) {
|
|
121
|
+
let targetOpts;
|
|
122
|
+
try {
|
|
123
|
+
targetOpts = (0, devkit_1.readTargetOptions)(target, context);
|
|
124
|
+
}
|
|
125
|
+
catch (e) {
|
|
126
|
+
throw new Error(`Unable to read the target options for ${(0, devkit_1.targetToTargetString)(target)}.
|
|
127
|
+
Are you sure this is a valid target?
|
|
128
|
+
Was trying to read the target for the property: '${property}', but got the following error:
|
|
129
|
+
${e.message || e}`);
|
|
130
|
+
}
|
|
131
|
+
let targetHasOpt = Object.keys(targetOpts).includes(property);
|
|
132
|
+
if (!targetHasOpt) {
|
|
133
|
+
// NOTE: readTargetOptions doesn't apply non defaulted values, i.e. @nx/vite has a port options but is optional
|
|
134
|
+
// so we double check the schema if readTargetOptions didn't return a value for the property
|
|
135
|
+
const projectConfig = context.projectsConfigurations?.projects?.[target.project];
|
|
136
|
+
const targetConfig = projectConfig.targets[target.target];
|
|
137
|
+
const [collection, executor] = targetConfig.executor.split(':');
|
|
138
|
+
const { schema } = (0, executor_utils_1.getExecutorInformation)(collection, executor, context.root, context.projectsConfigurations.projects);
|
|
139
|
+
// NOTE: schema won't have a default since readTargetOptions would have
|
|
140
|
+
// already set that and this check wouldn't need to be made
|
|
141
|
+
targetHasOpt = Object.keys(schema.properties).includes(property);
|
|
142
|
+
}
|
|
143
|
+
return [targetHasOpt, targetOpts[property]];
|
|
144
|
+
}
|
|
145
|
+
function attemptToLockPort(port) {
|
|
146
|
+
const portLockFilePath = (0, path_1.join)(__dirname, `${port}.txt`);
|
|
147
|
+
try {
|
|
148
|
+
if ((0, fs_1.existsSync)(portLockFilePath)) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
(0, fs_1.writeFileSync)(portLockFilePath, 'locked');
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|