UNPKG

@nx/cypress

Version:

The Nx Plugin for Cypress contains executors and generators allowing your workspace to use the powerful Cypress integration testing capabilities.

157 lines (156 loc) 7.33 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.startDevServer = startDevServer; const devkit_1 = require("@nx/devkit"); const path_1 = require("path"); const detectPort = require("detect-port"); const executor_utils_1 = require("nx/src/command-line/run/executor-utils"); const fs_1 = require("fs"); async function* startDevServer(opts, context) { // no dev server, return the provisioned base url if (!opts.devServerTarget || opts.skipServe) { yield { baseUrl: opts.baseUrl }; return; } const parsedDevServerTarget = (0, devkit_1.parseTargetString)(opts.devServerTarget, context); const [targetSupportsWatchOpt] = getValueFromSchema(context, parsedDevServerTarget, 'watch'); const overrides = { // @NOTE: Do not forward watch option if not supported by the target dev server, // this is relevant for running Cypress against dev server target that does not support this option, // for instance @nguniversal/builders:ssr-dev-server. ...(targetSupportsWatchOpt ? { watch: opts.watch } : {}), }; if (opts.port === 'cypress-auto') { const freePort = await getPortForProject(context, parsedDevServerTarget); overrides['port'] = freePort; } else if (opts.port !== undefined) { overrides['port'] = opts.port; // zero is a special case that means any valid port so there is no reason to try to 'lock it' if (opts.port !== 0) { const didLock = attemptToLockPort(opts.port); if (!didLock) { devkit_1.logger.warn((0, devkit_1.stripIndents) `${opts.port} is potentially already in use by another cypress run. If the port is in use, try using a different port value or passing --port='cypress-auto' to find a free port.`); } } } for await (const output of await (0, devkit_1.runExecutor)(parsedDevServerTarget, overrides, context)) { if (!output.success && !opts.watch) throw new Error('Could not compile application files'); if (!opts.baseUrl && !output.baseUrl && !output.info?.baseUrl && (output.port || output.info?.port)) { output.baseUrl = `http://localhost:${output.info.port}`; } yield { baseUrl: opts.baseUrl || output.baseUrl || output.info?.baseUrl, portLockFilePath: overrides.port && (0, path_1.join)(__dirname, `${overrides.port}.txt`), }; } } /** * try to find a free port for the project to run on * will return undefined if no port is found or the project doesn't have a port option **/ async function getPortForProject(context, target, defaultPort = 4200) { const fmtTarget = (0, devkit_1.targetToTargetString)(target); const [hasPortOpt, schemaPortValue] = getValueFromSchema(context, target, 'port'); let freePort; if (hasPortOpt) { let normalizedPortValue; if (!schemaPortValue) { devkit_1.logger.info(`NX ${fmtTarget} did not have a defined port value, checking for free port with the default value of ${defaultPort}`); normalizedPortValue = defaultPort; } else { normalizedPortValue = Number(schemaPortValue); } if (isNaN(normalizedPortValue)) { devkit_1.output.warn({ title: `Port Not a Number`, bodyLines: [ `The port value found was not a number or can't be parsed to a number`, `When reading the devServerTarget (${fmtTarget}) schema, expected ${schemaPortValue} to be a number but got NaN.`, `Nx will use the default value of ${defaultPort} instead.`, `You can manually specify a port by setting the 'port' option`, ], }); normalizedPortValue = defaultPort; } try { let attempts = 0; // make sure when this check happens in parallel, // we don't let the same port be used by multiple projects do { freePort = await detectPort(freePort || normalizedPortValue); if (attemptToLockPort(freePort)) { break; } attempts++; // increment port in case the lock file isn't cleaned up freePort++; } while (attempts < 20); devkit_1.logger.info(`NX Using port ${freePort} for ${fmtTarget}`); } catch (err) { throw new Error((0, devkit_1.stripIndents) `Unable to find a free port for the dev server, ${fmtTarget}. You can disable auto port detection by specifing a port or not passing a value to --port`); } } else { devkit_1.output.warn({ title: `No Port Option Found`, bodyLines: [ `The 'port' option is set to 'cypress-auto', but the devServerTarget (${fmtTarget}) does not have a port option.`, `Because of this, Nx is unable to verify the port is free before starting the dev server.`, `This might cause issues if the devServerTarget is trying to use a port that is already in use.`, ], }); } return freePort; } /** * Check if the given target has the given property in it's options. * if the property is does not have a default value or is not in the actual executor options, * the value will be undefined even if it's in the executor schema. **/ function getValueFromSchema(context, target, property) { let targetOpts; try { targetOpts = (0, devkit_1.readTargetOptions)(target, context); } catch (e) { throw new Error(`Unable to read the target options for ${(0, devkit_1.targetToTargetString)(target)}. Are you sure this is a valid target? Was trying to read the target for the property: '${property}', but got the following error: ${e.message || e}`); } let targetHasOpt = Object.keys(targetOpts).includes(property); if (!targetHasOpt) { // NOTE: readTargetOptions doesn't apply non defaulted values, i.e. @nx/vite has a port options but is optional // so we double check the schema if readTargetOptions didn't return a value for the property const projectConfig = context.projectsConfigurations?.projects?.[target.project]; const targetConfig = projectConfig.targets[target.target]; const [collection, executor] = (0, executor_utils_1.parseExecutor)(targetConfig.executor); const { schema } = (0, executor_utils_1.getExecutorInformation)(collection, executor, context.root, context.projectsConfigurations.projects); // NOTE: schema won't have a default since readTargetOptions would have // already set that and this check wouldn't need to be made targetHasOpt = Object.keys(schema.properties).includes(property); } return [targetHasOpt, targetOpts[property]]; } function attemptToLockPort(port) { const portLockFilePath = (0, path_1.join)(__dirname, `${port}.txt`); try { if ((0, fs_1.existsSync)(portLockFilePath)) { return false; } (0, fs_1.writeFileSync)(portLockFilePath, 'locked'); return true; } catch (err) { return false; } }