@nx/cypress
Version:
157 lines (156 loc) • 7.33 kB
JavaScript
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;
}
}
;