init
This commit is contained in:
156
node_modules/cypress/lib/tasks/cache.js
generated
vendored
Normal file
156
node_modules/cypress/lib/tasks/cache.js
generated
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
"use strict";
|
||||
|
||||
const state = require('./state');
|
||||
|
||||
const logger = require('../logger');
|
||||
|
||||
const fs = require('../fs');
|
||||
|
||||
const util = require('../util');
|
||||
|
||||
const {
|
||||
join
|
||||
} = require('path');
|
||||
|
||||
const Table = require('cli-table3');
|
||||
|
||||
const dayjs = require('dayjs');
|
||||
|
||||
const relativeTime = require('dayjs/plugin/relativeTime');
|
||||
|
||||
const chalk = require('chalk');
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
const getFolderSize = require('./get-folder-size');
|
||||
|
||||
const Bluebird = require('bluebird');
|
||||
|
||||
dayjs.extend(relativeTime); // output colors for the table
|
||||
|
||||
const colors = {
|
||||
titles: chalk.white,
|
||||
dates: chalk.cyan,
|
||||
values: chalk.green,
|
||||
size: chalk.gray
|
||||
};
|
||||
|
||||
const logCachePath = () => {
|
||||
logger.always(state.getCacheDir());
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const clear = () => {
|
||||
return fs.removeAsync(state.getCacheDir());
|
||||
};
|
||||
|
||||
const prune = () => {
|
||||
const cacheDir = state.getCacheDir();
|
||||
const currentVersion = util.pkgVersion();
|
||||
let deletedBinary = false;
|
||||
return fs.readdirAsync(cacheDir).then(versions => {
|
||||
return Bluebird.all(versions.map(version => {
|
||||
if (version !== currentVersion) {
|
||||
deletedBinary = true;
|
||||
const versionDir = join(cacheDir, version);
|
||||
return fs.removeAsync(versionDir);
|
||||
}
|
||||
}));
|
||||
}).then(() => {
|
||||
if (deletedBinary) {
|
||||
logger.always(`Deleted all binary caches except for the ${currentVersion} binary cache.`);
|
||||
} else {
|
||||
logger.always(`No binary caches found to prune.`);
|
||||
}
|
||||
}).catch({
|
||||
code: 'ENOENT'
|
||||
}, () => {
|
||||
logger.always(`No Cypress cache was found at ${cacheDir}. Nothing to prune.`);
|
||||
});
|
||||
};
|
||||
|
||||
const fileSizeInMB = size => {
|
||||
return `${(size / 1024 / 1024).toFixed(1)}MB`;
|
||||
};
|
||||
/**
|
||||
* Collects all cached versions, finds when each was used
|
||||
* and prints a table with results to the terminal
|
||||
*/
|
||||
|
||||
|
||||
const list = showSize => {
|
||||
return getCachedVersions(showSize).then(binaries => {
|
||||
const head = [colors.titles('version'), colors.titles('last used')];
|
||||
|
||||
if (showSize) {
|
||||
head.push(colors.titles('size'));
|
||||
}
|
||||
|
||||
const table = new Table({
|
||||
head
|
||||
});
|
||||
binaries.forEach(binary => {
|
||||
const versionString = colors.values(binary.version);
|
||||
const lastUsed = binary.accessed ? colors.dates(binary.accessed) : 'unknown';
|
||||
const row = [versionString, lastUsed];
|
||||
|
||||
if (showSize) {
|
||||
const size = colors.size(fileSizeInMB(binary.size));
|
||||
row.push(size);
|
||||
}
|
||||
|
||||
return table.push(row);
|
||||
});
|
||||
logger.always(table.toString());
|
||||
});
|
||||
};
|
||||
|
||||
const getCachedVersions = showSize => {
|
||||
const cacheDir = state.getCacheDir();
|
||||
return fs.readdirAsync(cacheDir).filter(util.isSemver).map(version => {
|
||||
return {
|
||||
version,
|
||||
folderPath: join(cacheDir, version)
|
||||
};
|
||||
}).mapSeries(binary => {
|
||||
// last access time on the folder is different from last access time
|
||||
// on the Cypress binary
|
||||
const binaryDir = state.getBinaryDir(binary.version);
|
||||
const executable = state.getPathToExecutable(binaryDir);
|
||||
return fs.statAsync(executable).then(stat => {
|
||||
const lastAccessedTime = _.get(stat, 'atime');
|
||||
|
||||
if (!lastAccessedTime) {
|
||||
// the test runner has never been opened
|
||||
// or could be a test simulating missing timestamp
|
||||
return binary;
|
||||
}
|
||||
|
||||
const accessed = dayjs(lastAccessedTime).fromNow();
|
||||
binary.accessed = accessed;
|
||||
return binary;
|
||||
}, e => {
|
||||
// could not find the binary or gets its stats
|
||||
return binary;
|
||||
});
|
||||
}).mapSeries(binary => {
|
||||
if (showSize) {
|
||||
const binaryDir = state.getBinaryDir(binary.version);
|
||||
return getFolderSize(binaryDir).then(size => {
|
||||
return { ...binary,
|
||||
size
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return binary;
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
path: logCachePath,
|
||||
clear,
|
||||
prune,
|
||||
list,
|
||||
getCachedVersions
|
||||
};
|
343
node_modules/cypress/lib/tasks/download.js
generated
vendored
Normal file
343
node_modules/cypress/lib/tasks/download.js
generated
vendored
Normal file
@@ -0,0 +1,343 @@
|
||||
"use strict";
|
||||
|
||||
const arch = require('arch');
|
||||
|
||||
const la = require('lazy-ass');
|
||||
|
||||
const is = require('check-more-types');
|
||||
|
||||
const os = require('os');
|
||||
|
||||
const url = require('url');
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const debug = require('debug')('cypress:cli');
|
||||
|
||||
const request = require('@cypress/request');
|
||||
|
||||
const Promise = require('bluebird');
|
||||
|
||||
const requestProgress = require('request-progress');
|
||||
|
||||
const {
|
||||
stripIndent
|
||||
} = require('common-tags');
|
||||
|
||||
const {
|
||||
throwFormErrorText,
|
||||
errors
|
||||
} = require('../errors');
|
||||
|
||||
const fs = require('../fs');
|
||||
|
||||
const util = require('../util');
|
||||
|
||||
const defaultBaseUrl = 'https://download.cypress.io/';
|
||||
|
||||
const getProxyUrl = () => {
|
||||
return process.env.HTTPS_PROXY || process.env.https_proxy || process.env.npm_config_https_proxy || process.env.HTTP_PROXY || process.env.http_proxy || process.env.npm_config_proxy || null;
|
||||
};
|
||||
|
||||
const getRealOsArch = () => {
|
||||
// os.arch() returns the arch for which this node was compiled
|
||||
// we want the operating system's arch instead: x64 or x86
|
||||
const osArch = arch();
|
||||
|
||||
if (osArch === 'x86') {
|
||||
// match process.platform output
|
||||
return 'ia32';
|
||||
}
|
||||
|
||||
return osArch;
|
||||
};
|
||||
|
||||
const getBaseUrl = () => {
|
||||
if (util.getEnv('CYPRESS_DOWNLOAD_MIRROR')) {
|
||||
let baseUrl = util.getEnv('CYPRESS_DOWNLOAD_MIRROR');
|
||||
|
||||
if (!baseUrl.endsWith('/')) {
|
||||
baseUrl += '/';
|
||||
}
|
||||
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
return defaultBaseUrl;
|
||||
};
|
||||
|
||||
const getCA = () => {
|
||||
return new Promise(resolve => {
|
||||
if (!util.getEnv('CYPRESS_DOWNLOAD_USE_CA')) {
|
||||
resolve();
|
||||
}
|
||||
|
||||
if (process.env.npm_config_ca) {
|
||||
resolve(process.env.npm_config_ca);
|
||||
} else if (process.env.npm_config_cafile) {
|
||||
fs.readFile(process.env.npm_config_cafile, 'utf8').then(cafileContent => {
|
||||
resolve(cafileContent);
|
||||
}).catch(() => {
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const prepend = urlPath => {
|
||||
const endpoint = url.resolve(getBaseUrl(), urlPath);
|
||||
const platform = os.platform();
|
||||
const arch = getRealOsArch();
|
||||
return `${endpoint}?platform=${platform}&arch=${arch}`;
|
||||
};
|
||||
|
||||
const getUrl = version => {
|
||||
if (is.url(version)) {
|
||||
debug('version is already an url', version);
|
||||
return version;
|
||||
}
|
||||
|
||||
return version ? prepend(`desktop/${version}`) : prepend('desktop');
|
||||
};
|
||||
|
||||
const statusMessage = err => {
|
||||
return err.statusCode ? [err.statusCode, err.statusMessage].join(' - ') : err.toString();
|
||||
};
|
||||
|
||||
const prettyDownloadErr = (err, version) => {
|
||||
const msg = stripIndent`
|
||||
URL: ${getUrl(version)}
|
||||
${statusMessage(err)}
|
||||
`;
|
||||
debug(msg);
|
||||
return throwFormErrorText(errors.failedDownload)(msg);
|
||||
};
|
||||
/**
|
||||
* Checks checksum and file size for the given file. Allows both
|
||||
* values or just one of them to be checked.
|
||||
*/
|
||||
|
||||
|
||||
const verifyDownloadedFile = (filename, expectedSize, expectedChecksum) => {
|
||||
if (expectedSize && expectedChecksum) {
|
||||
debug('verifying checksum and file size');
|
||||
return Promise.join(util.getFileChecksum(filename), util.getFileSize(filename), (checksum, filesize) => {
|
||||
if (checksum === expectedChecksum && filesize === expectedSize) {
|
||||
debug('downloaded file has the expected checksum and size ✅');
|
||||
return;
|
||||
}
|
||||
|
||||
debug('raising error: checksum or file size mismatch');
|
||||
const text = stripIndent`
|
||||
Corrupted download
|
||||
|
||||
Expected downloaded file to have checksum: ${expectedChecksum}
|
||||
Computed checksum: ${checksum}
|
||||
|
||||
Expected downloaded file to have size: ${expectedSize}
|
||||
Computed size: ${filesize}
|
||||
`;
|
||||
debug(text);
|
||||
throw new Error(text);
|
||||
});
|
||||
}
|
||||
|
||||
if (expectedChecksum) {
|
||||
debug('only checking expected file checksum %d', expectedChecksum);
|
||||
return util.getFileChecksum(filename).then(checksum => {
|
||||
if (checksum === expectedChecksum) {
|
||||
debug('downloaded file has the expected checksum ✅');
|
||||
return;
|
||||
}
|
||||
|
||||
debug('raising error: file checksum mismatch');
|
||||
const text = stripIndent`
|
||||
Corrupted download
|
||||
|
||||
Expected downloaded file to have checksum: ${expectedChecksum}
|
||||
Computed checksum: ${checksum}
|
||||
`;
|
||||
throw new Error(text);
|
||||
});
|
||||
}
|
||||
|
||||
if (expectedSize) {
|
||||
// maybe we don't have a checksum, but at least CDN returns content length
|
||||
// which we can check against the file size
|
||||
debug('only checking expected file size %d', expectedSize);
|
||||
return util.getFileSize(filename).then(filesize => {
|
||||
if (filesize === expectedSize) {
|
||||
debug('downloaded file has the expected size ✅');
|
||||
return;
|
||||
}
|
||||
|
||||
debug('raising error: file size mismatch');
|
||||
const text = stripIndent`
|
||||
Corrupted download
|
||||
|
||||
Expected downloaded file to have size: ${expectedSize}
|
||||
Computed size: ${filesize}
|
||||
`;
|
||||
throw new Error(text);
|
||||
});
|
||||
}
|
||||
|
||||
debug('downloaded file lacks checksum or size to verify');
|
||||
return Promise.resolve();
|
||||
}; // downloads from given url
|
||||
// return an object with
|
||||
// {filename: ..., downloaded: true}
|
||||
|
||||
|
||||
const downloadFromUrl = ({
|
||||
url,
|
||||
downloadDestination,
|
||||
progress,
|
||||
ca
|
||||
}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const proxy = getProxyUrl();
|
||||
debug('Downloading package', {
|
||||
url,
|
||||
proxy,
|
||||
downloadDestination
|
||||
});
|
||||
let redirectVersion;
|
||||
const reqOptions = {
|
||||
url,
|
||||
proxy,
|
||||
|
||||
followRedirect(response) {
|
||||
const version = response.headers['x-version'];
|
||||
debug('redirect version:', version);
|
||||
|
||||
if (version) {
|
||||
// set the version in options if we have one.
|
||||
// this insulates us from potential redirect
|
||||
// problems where version would be set to undefined.
|
||||
redirectVersion = version;
|
||||
} // yes redirect
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
if (ca) {
|
||||
debug('using custom CA details from npm config');
|
||||
reqOptions.agentOptions = {
|
||||
ca
|
||||
};
|
||||
}
|
||||
|
||||
const req = request(reqOptions); // closure
|
||||
|
||||
let started = null;
|
||||
let expectedSize;
|
||||
let expectedChecksum;
|
||||
requestProgress(req, {
|
||||
throttle: progress.throttle
|
||||
}).on('response', response => {
|
||||
// we have computed checksum and filesize during test runner binary build
|
||||
// and have set it on the S3 object as user meta data, available via
|
||||
// these custom headers "x-amz-meta-..."
|
||||
// see https://github.com/cypress-io/cypress/pull/4092
|
||||
expectedSize = response.headers['x-amz-meta-size'] || response.headers['content-length'];
|
||||
expectedChecksum = response.headers['x-amz-meta-checksum'];
|
||||
|
||||
if (expectedChecksum) {
|
||||
debug('expected checksum %s', expectedChecksum);
|
||||
}
|
||||
|
||||
if (expectedSize) {
|
||||
// convert from string (all Amazon custom headers are strings)
|
||||
expectedSize = Number(expectedSize);
|
||||
debug('expected file size %d', expectedSize);
|
||||
} // start counting now once we've gotten
|
||||
// response headers
|
||||
|
||||
|
||||
started = new Date(); // if our status code does not start with 200
|
||||
|
||||
if (!/^2/.test(response.statusCode)) {
|
||||
debug('response code %d', response.statusCode);
|
||||
const err = new Error(stripIndent`
|
||||
Failed downloading the Cypress binary.
|
||||
Response code: ${response.statusCode}
|
||||
Response message: ${response.statusMessage}
|
||||
`);
|
||||
reject(err);
|
||||
}
|
||||
}).on('error', reject).on('progress', state => {
|
||||
// total time we've elapsed
|
||||
// starting on our first progress notification
|
||||
const elapsed = new Date() - started; // request-progress sends a value between 0 and 1
|
||||
|
||||
const percentage = util.convertPercentToPercentage(state.percent);
|
||||
const eta = util.calculateEta(percentage, elapsed); // send up our percent and seconds remaining
|
||||
|
||||
progress.onProgress(percentage, util.secsRemaining(eta));
|
||||
}) // save this download here
|
||||
.pipe(fs.createWriteStream(downloadDestination)).on('finish', () => {
|
||||
debug('downloading finished');
|
||||
verifyDownloadedFile(downloadDestination, expectedSize, expectedChecksum).then(() => {
|
||||
return resolve(redirectVersion);
|
||||
}, reject);
|
||||
});
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Download Cypress.zip from external url to local file.
|
||||
* @param [string] version Could be "3.3.0" or full URL
|
||||
* @param [string] downloadDestination Local filename to save as
|
||||
*/
|
||||
|
||||
|
||||
const start = opts => {
|
||||
let {
|
||||
version,
|
||||
downloadDestination,
|
||||
progress
|
||||
} = opts;
|
||||
|
||||
if (!downloadDestination) {
|
||||
la(is.unemptyString(downloadDestination), 'missing download dir', opts);
|
||||
}
|
||||
|
||||
if (!progress) {
|
||||
progress = {
|
||||
onProgress: () => {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const url = getUrl(version);
|
||||
progress.throttle = 100;
|
||||
debug('needed Cypress version: %s', version);
|
||||
debug('source url %s', url);
|
||||
debug(`downloading cypress.zip to "${downloadDestination}"`); // ensure download dir exists
|
||||
|
||||
return fs.ensureDirAsync(path.dirname(downloadDestination)).then(() => {
|
||||
return getCA();
|
||||
}).then(ca => {
|
||||
return downloadFromUrl({
|
||||
url,
|
||||
downloadDestination,
|
||||
progress,
|
||||
ca
|
||||
});
|
||||
}).catch(err => {
|
||||
return prettyDownloadErr(err, version);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
start,
|
||||
getUrl,
|
||||
getProxyUrl,
|
||||
getCA
|
||||
};
|
41
node_modules/cypress/lib/tasks/get-folder-size.js
generated
vendored
Normal file
41
node_modules/cypress/lib/tasks/get-folder-size.js
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
"use strict";
|
||||
|
||||
const fs = require('../fs');
|
||||
|
||||
const {
|
||||
join
|
||||
} = require('path');
|
||||
|
||||
const Bluebird = require('bluebird');
|
||||
/**
|
||||
* Get the size of a folder or a file.
|
||||
*
|
||||
* This function returns the actual file size of the folder (size), not the allocated space on disk (size on disk).
|
||||
* For more details between the difference, check this link:
|
||||
* https://www.howtogeek.com/180369/why-is-there-a-big-difference-between-size-and-size-on-disk/
|
||||
*
|
||||
* @param {string} path path to the file or the folder.
|
||||
*/
|
||||
|
||||
|
||||
async function getSize(path) {
|
||||
const stat = await fs.lstat(path);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
const list = await fs.readdir(path);
|
||||
return Bluebird.resolve(list).reduce(async (prev, curr) => {
|
||||
const currPath = join(path, curr);
|
||||
const s = await fs.lstat(currPath);
|
||||
|
||||
if (s.isDirectory()) {
|
||||
return prev + (await getSize(currPath));
|
||||
}
|
||||
|
||||
return prev + s.size;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
return stat.size;
|
||||
}
|
||||
|
||||
module.exports = getSize;
|
449
node_modules/cypress/lib/tasks/install.js
generated
vendored
Normal file
449
node_modules/cypress/lib/tasks/install.js
generated
vendored
Normal file
@@ -0,0 +1,449 @@
|
||||
"use strict";
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
const os = require('os');
|
||||
|
||||
const url = require('url');
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const chalk = require('chalk');
|
||||
|
||||
const debug = require('debug')('cypress:cli');
|
||||
|
||||
const {
|
||||
Listr
|
||||
} = require('listr2');
|
||||
|
||||
const Promise = require('bluebird');
|
||||
|
||||
const logSymbols = require('log-symbols');
|
||||
|
||||
const {
|
||||
stripIndent
|
||||
} = require('common-tags');
|
||||
|
||||
const fs = require('../fs');
|
||||
|
||||
const download = require('./download');
|
||||
|
||||
const util = require('../util');
|
||||
|
||||
const state = require('./state');
|
||||
|
||||
const unzip = require('./unzip');
|
||||
|
||||
const logger = require('../logger');
|
||||
|
||||
const {
|
||||
throwFormErrorText,
|
||||
errors
|
||||
} = require('../errors');
|
||||
|
||||
const verbose = require('../VerboseRenderer');
|
||||
|
||||
const getNpmArgv = () => {
|
||||
const json = process.env.npm_config_argv;
|
||||
|
||||
if (!json) {
|
||||
return;
|
||||
}
|
||||
|
||||
debug('found npm argv json %o', json);
|
||||
|
||||
try {
|
||||
return JSON.parse(json).original || [];
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}; // attempt to discover the version specifier used to install Cypress
|
||||
// for example: "^5.0.0", "https://cdn.cypress.io/...", ...
|
||||
|
||||
|
||||
const getVersionSpecifier = (startDir = path.resolve(__dirname, '../..')) => {
|
||||
const argv = getNpmArgv();
|
||||
|
||||
if (argv) {
|
||||
const tgz = _.find(argv, t => t.endsWith('cypress.tgz'));
|
||||
|
||||
if (tgz) {
|
||||
return tgz;
|
||||
}
|
||||
}
|
||||
|
||||
const getVersionSpecifierFromPkg = dir => {
|
||||
debug('looking for versionSpecifier %o', {
|
||||
dir
|
||||
});
|
||||
|
||||
const tryParent = () => {
|
||||
const parentPath = path.resolve(dir, '..');
|
||||
|
||||
if (parentPath === dir) {
|
||||
debug('reached FS root with no versionSpecifier found');
|
||||
return;
|
||||
}
|
||||
|
||||
return getVersionSpecifierFromPkg(parentPath);
|
||||
};
|
||||
|
||||
return fs.readJSON(path.join(dir, 'package.json')).catch(() => ({})).then(pkg => {
|
||||
const specifier = _.chain(['dependencies', 'devDependencies', 'optionalDependencies']).map(prop => _.get(pkg, `${prop}.cypress`)).compact().first().value();
|
||||
|
||||
return specifier || tryParent();
|
||||
});
|
||||
}; // recurse through parent directories until package.json with `cypress` is found
|
||||
|
||||
|
||||
return getVersionSpecifierFromPkg(startDir).then(versionSpecifier => {
|
||||
debug('finished looking for versionSpecifier', {
|
||||
versionSpecifier
|
||||
});
|
||||
return versionSpecifier;
|
||||
});
|
||||
};
|
||||
|
||||
const betaNpmUrlRe = /^\/beta\/npm\/(?<version>[0-9.]+)\/(?<artifactSlug>[^/]+)\/cypress\.tgz$/; // convert a prerelease NPM package .tgz URL to the corresponding binary .zip URL
|
||||
|
||||
const getBinaryUrlFromPrereleaseNpmUrl = npmUrl => {
|
||||
let parsed;
|
||||
|
||||
try {
|
||||
parsed = url.parse(npmUrl);
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
|
||||
const matches = betaNpmUrlRe.exec(parsed.pathname);
|
||||
|
||||
if (parsed.hostname !== 'cdn.cypress.io' || !matches) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
version,
|
||||
artifactSlug
|
||||
} = matches.groups;
|
||||
parsed.pathname = `/beta/binary/${version}/${os.platform()}-${os.arch()}/${artifactSlug}/cypress.zip`;
|
||||
return parsed.format();
|
||||
};
|
||||
|
||||
const alreadyInstalledMsg = () => {
|
||||
if (!util.isPostInstall()) {
|
||||
logger.log(stripIndent`
|
||||
Skipping installation:
|
||||
|
||||
Pass the ${chalk.yellow('--force')} option if you'd like to reinstall anyway.
|
||||
`);
|
||||
}
|
||||
};
|
||||
|
||||
const displayCompletionMsg = () => {
|
||||
// check here to see if we are globally installed
|
||||
if (util.isInstalledGlobally()) {
|
||||
// if we are display a warning
|
||||
logger.log();
|
||||
logger.warn(stripIndent`
|
||||
${logSymbols.warning} Warning: It looks like you\'ve installed Cypress globally.
|
||||
|
||||
This will work, but it'\s not recommended.
|
||||
|
||||
The recommended way to install Cypress is as a devDependency per project.
|
||||
|
||||
You should probably run these commands:
|
||||
|
||||
- ${chalk.cyan('npm uninstall -g cypress')}
|
||||
- ${chalk.cyan('npm install --save-dev cypress')}
|
||||
`);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log();
|
||||
logger.log('You can now open Cypress by running:', chalk.cyan(path.join('node_modules', '.bin', 'cypress'), 'open'));
|
||||
logger.log();
|
||||
logger.log(chalk.grey('https://on.cypress.io/installing-cypress'));
|
||||
logger.log();
|
||||
};
|
||||
|
||||
const downloadAndUnzip = ({
|
||||
version,
|
||||
installDir,
|
||||
downloadDir
|
||||
}) => {
|
||||
const progress = {
|
||||
throttle: 100,
|
||||
onProgress: null
|
||||
};
|
||||
const downloadDestination = path.join(downloadDir, `cypress-${process.pid}.zip`);
|
||||
const rendererOptions = getRendererOptions(); // let the user know what version of cypress we're downloading!
|
||||
|
||||
logger.log(`Installing Cypress ${chalk.gray(`(version: ${version})`)}`);
|
||||
logger.log();
|
||||
const tasks = new Listr([{
|
||||
options: {
|
||||
title: util.titleize('Downloading Cypress')
|
||||
},
|
||||
task: (ctx, task) => {
|
||||
// as our download progresses indicate the status
|
||||
progress.onProgress = progessify(task, 'Downloading Cypress');
|
||||
return download.start({
|
||||
version,
|
||||
downloadDestination,
|
||||
progress
|
||||
}).then(redirectVersion => {
|
||||
if (redirectVersion) version = redirectVersion;
|
||||
debug(`finished downloading file: ${downloadDestination}`);
|
||||
}).then(() => {
|
||||
// save the download destination for unzipping
|
||||
util.setTaskTitle(task, util.titleize(chalk.green('Downloaded Cypress')), rendererOptions.renderer);
|
||||
});
|
||||
}
|
||||
}, unzipTask({
|
||||
progress,
|
||||
zipFilePath: downloadDestination,
|
||||
installDir,
|
||||
rendererOptions
|
||||
}), {
|
||||
options: {
|
||||
title: util.titleize('Finishing Installation')
|
||||
},
|
||||
task: (ctx, task) => {
|
||||
const cleanup = () => {
|
||||
debug('removing zip file %s', downloadDestination);
|
||||
return fs.removeAsync(downloadDestination);
|
||||
};
|
||||
|
||||
return cleanup().then(() => {
|
||||
debug('finished installation in', installDir);
|
||||
util.setTaskTitle(task, util.titleize(chalk.green('Finished Installation'), chalk.gray(installDir)), rendererOptions.renderer);
|
||||
});
|
||||
}
|
||||
}], {
|
||||
rendererOptions
|
||||
}); // start the tasks!
|
||||
|
||||
return Promise.resolve(tasks.run());
|
||||
};
|
||||
|
||||
const start = (options = {}) => {
|
||||
debug('installing with options %j', options);
|
||||
|
||||
_.defaults(options, {
|
||||
force: false
|
||||
});
|
||||
|
||||
const pkgVersion = util.pkgVersion();
|
||||
let needVersion = pkgVersion;
|
||||
let binaryUrlOverride;
|
||||
debug('version in package.json is', needVersion); // let this environment variable reset the binary version we need
|
||||
|
||||
if (util.getEnv('CYPRESS_INSTALL_BINARY')) {
|
||||
// because passed file paths are often double quoted
|
||||
// and might have extra whitespace around, be robust and trim the string
|
||||
const trimAndRemoveDoubleQuotes = true;
|
||||
const envVarVersion = util.getEnv('CYPRESS_INSTALL_BINARY', trimAndRemoveDoubleQuotes);
|
||||
debug('using environment variable CYPRESS_INSTALL_BINARY "%s"', envVarVersion);
|
||||
|
||||
if (envVarVersion === '0') {
|
||||
debug('environment variable CYPRESS_INSTALL_BINARY = 0, skipping install');
|
||||
logger.log(stripIndent`
|
||||
${chalk.yellow('Note:')} Skipping binary installation: Environment variable CYPRESS_INSTALL_BINARY = 0.`);
|
||||
logger.log();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
binaryUrlOverride = envVarVersion;
|
||||
}
|
||||
|
||||
if (util.getEnv('CYPRESS_CACHE_FOLDER')) {
|
||||
const envCache = util.getEnv('CYPRESS_CACHE_FOLDER');
|
||||
logger.log(stripIndent`
|
||||
${chalk.yellow('Note:')} Overriding Cypress cache directory to: ${chalk.cyan(envCache)}
|
||||
|
||||
Previous installs of Cypress may not be found.
|
||||
`);
|
||||
logger.log();
|
||||
}
|
||||
|
||||
const installDir = state.getVersionDir(pkgVersion);
|
||||
const cacheDir = state.getCacheDir();
|
||||
const binaryDir = state.getBinaryDir(pkgVersion);
|
||||
return fs.ensureDirAsync(cacheDir).catch({
|
||||
code: 'EACCES'
|
||||
}, err => {
|
||||
return throwFormErrorText(errors.invalidCacheDirectory)(stripIndent`
|
||||
Failed to access ${chalk.cyan(cacheDir)}:
|
||||
|
||||
${err.message}
|
||||
`);
|
||||
}).then(() => {
|
||||
return Promise.all([state.getBinaryPkgAsync(binaryDir).then(state.getBinaryPkgVersion), getVersionSpecifier()]);
|
||||
}).then(([binaryVersion, versionSpecifier]) => {
|
||||
if (!binaryUrlOverride && versionSpecifier) {
|
||||
const computedBinaryUrl = getBinaryUrlFromPrereleaseNpmUrl(versionSpecifier);
|
||||
|
||||
if (computedBinaryUrl) {
|
||||
debug('computed binary url from version specifier %o', {
|
||||
computedBinaryUrl,
|
||||
needVersion
|
||||
});
|
||||
binaryUrlOverride = computedBinaryUrl;
|
||||
}
|
||||
}
|
||||
|
||||
needVersion = binaryUrlOverride || needVersion;
|
||||
debug('installed version is', binaryVersion, 'version needed is', needVersion);
|
||||
|
||||
if (!binaryVersion) {
|
||||
debug('no binary installed under cli version');
|
||||
return true;
|
||||
}
|
||||
|
||||
logger.log();
|
||||
logger.log(stripIndent`
|
||||
Cypress ${chalk.green(binaryVersion)} is installed in ${chalk.cyan(installDir)}
|
||||
`);
|
||||
logger.log();
|
||||
|
||||
if (options.force) {
|
||||
debug('performing force install over existing binary');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (binaryVersion === needVersion || !util.isSemver(needVersion)) {
|
||||
// our version matches, tell the user this is a noop
|
||||
alreadyInstalledMsg();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}).then(shouldInstall => {
|
||||
// noop if we've been told not to download
|
||||
if (!shouldInstall) {
|
||||
debug('Not downloading or installing binary');
|
||||
return;
|
||||
}
|
||||
|
||||
if (needVersion !== pkgVersion) {
|
||||
logger.log(chalk.yellow(stripIndent`
|
||||
${logSymbols.warning} Warning: Forcing a binary version different than the default.
|
||||
|
||||
The CLI expected to install version: ${chalk.green(pkgVersion)}
|
||||
|
||||
Instead we will install version: ${chalk.green(needVersion)}
|
||||
|
||||
These versions may not work properly together.
|
||||
`));
|
||||
logger.log();
|
||||
} // see if version supplied is a path to a binary
|
||||
|
||||
|
||||
return fs.pathExistsAsync(needVersion).then(exists => {
|
||||
if (exists) {
|
||||
return path.extname(needVersion) === '.zip' ? needVersion : false;
|
||||
}
|
||||
|
||||
const possibleFile = util.formAbsolutePath(needVersion);
|
||||
debug('checking local file', possibleFile, 'cwd', process.cwd());
|
||||
return fs.pathExistsAsync(possibleFile).then(exists => {
|
||||
// if this exists return the path to it
|
||||
// else false
|
||||
if (exists && path.extname(possibleFile) === '.zip') {
|
||||
return possibleFile;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}).then(pathToLocalFile => {
|
||||
if (pathToLocalFile) {
|
||||
const absolutePath = path.resolve(needVersion);
|
||||
debug('found local file at', absolutePath);
|
||||
debug('skipping download');
|
||||
const rendererOptions = getRendererOptions();
|
||||
return new Listr([unzipTask({
|
||||
progress: {
|
||||
throttle: 100,
|
||||
onProgress: null
|
||||
},
|
||||
zipFilePath: absolutePath,
|
||||
installDir,
|
||||
rendererOptions
|
||||
})], {
|
||||
rendererOptions
|
||||
}).run();
|
||||
}
|
||||
|
||||
if (options.force) {
|
||||
debug('Cypress already installed at', installDir);
|
||||
debug('but the installation was forced');
|
||||
}
|
||||
|
||||
debug('preparing to download and unzip version ', needVersion, 'to path', installDir);
|
||||
const downloadDir = os.tmpdir();
|
||||
return downloadAndUnzip({
|
||||
version: needVersion,
|
||||
installDir,
|
||||
downloadDir
|
||||
});
|
||||
}) // delay 1 sec for UX, unless we are testing
|
||||
.then(() => {
|
||||
return Promise.delay(1000);
|
||||
}).then(displayCompletionMsg);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
start,
|
||||
_getVersionSpecifier: getVersionSpecifier,
|
||||
_getBinaryUrlFromPrereleaseNpmUrl: getBinaryUrlFromPrereleaseNpmUrl
|
||||
};
|
||||
|
||||
const unzipTask = ({
|
||||
zipFilePath,
|
||||
installDir,
|
||||
progress,
|
||||
rendererOptions
|
||||
}) => {
|
||||
return {
|
||||
options: {
|
||||
title: util.titleize('Unzipping Cypress')
|
||||
},
|
||||
task: (ctx, task) => {
|
||||
// as our unzip progresses indicate the status
|
||||
progress.onProgress = progessify(task, 'Unzipping Cypress');
|
||||
return unzip.start({
|
||||
zipFilePath,
|
||||
installDir,
|
||||
progress
|
||||
}).then(() => {
|
||||
util.setTaskTitle(task, util.titleize(chalk.green('Unzipped Cypress')), rendererOptions.renderer);
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const progessify = (task, title) => {
|
||||
// return higher order function
|
||||
return (percentComplete, remaining) => {
|
||||
percentComplete = chalk.white(` ${percentComplete}%`); // pluralize seconds remaining
|
||||
|
||||
remaining = chalk.gray(`${remaining}s`);
|
||||
util.setTaskTitle(task, util.titleize(title, percentComplete, remaining), getRendererOptions().renderer);
|
||||
};
|
||||
}; // if we are running in CI then use
|
||||
// the verbose renderer else use
|
||||
// the default
|
||||
|
||||
|
||||
const getRendererOptions = () => {
|
||||
let renderer = util.isCi() ? verbose : 'default';
|
||||
|
||||
if (logger.logLevel() === 'silent') {
|
||||
renderer = 'silent';
|
||||
}
|
||||
|
||||
return {
|
||||
renderer
|
||||
};
|
||||
};
|
226
node_modules/cypress/lib/tasks/state.js
generated
vendored
Normal file
226
node_modules/cypress/lib/tasks/state.js
generated
vendored
Normal file
@@ -0,0 +1,226 @@
|
||||
"use strict";
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
const os = require('os');
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const untildify = require('untildify');
|
||||
|
||||
const R = require('ramda');
|
||||
|
||||
const debug = require('debug')('cypress:cli');
|
||||
|
||||
const fs = require('../fs');
|
||||
|
||||
const util = require('../util');
|
||||
|
||||
const getPlatformExecutable = () => {
|
||||
const platform = os.platform();
|
||||
|
||||
switch (platform) {
|
||||
case 'darwin':
|
||||
return 'Contents/MacOS/Cypress';
|
||||
|
||||
case 'linux':
|
||||
return 'Cypress';
|
||||
|
||||
case 'win32':
|
||||
return 'Cypress.exe';
|
||||
// TODO handle this error using our standard
|
||||
|
||||
default:
|
||||
throw new Error(`Platform: "${platform}" is not supported.`);
|
||||
}
|
||||
};
|
||||
|
||||
const getPlatFormBinaryFolder = () => {
|
||||
const platform = os.platform();
|
||||
|
||||
switch (platform) {
|
||||
case 'darwin':
|
||||
return 'Cypress.app';
|
||||
|
||||
case 'linux':
|
||||
return 'Cypress';
|
||||
|
||||
case 'win32':
|
||||
return 'Cypress';
|
||||
// TODO handle this error using our standard
|
||||
|
||||
default:
|
||||
throw new Error(`Platform: "${platform}" is not supported.`);
|
||||
}
|
||||
};
|
||||
|
||||
const getBinaryPkgPath = binaryDir => {
|
||||
const platform = os.platform();
|
||||
|
||||
switch (platform) {
|
||||
case 'darwin':
|
||||
return path.join(binaryDir, 'Contents', 'Resources', 'app', 'package.json');
|
||||
|
||||
case 'linux':
|
||||
return path.join(binaryDir, 'resources', 'app', 'package.json');
|
||||
|
||||
case 'win32':
|
||||
return path.join(binaryDir, 'resources', 'app', 'package.json');
|
||||
// TODO handle this error using our standard
|
||||
|
||||
default:
|
||||
throw new Error(`Platform: "${platform}" is not supported.`);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Get path to binary directory
|
||||
*/
|
||||
|
||||
|
||||
const getBinaryDir = (version = util.pkgVersion()) => {
|
||||
return path.join(getVersionDir(version), getPlatFormBinaryFolder());
|
||||
};
|
||||
|
||||
const getVersionDir = (version = util.pkgVersion()) => {
|
||||
return path.join(getCacheDir(), version);
|
||||
};
|
||||
/**
|
||||
* When executing "npm postinstall" hook, the working directory is set to
|
||||
* "<current folder>/node_modules/cypress", which can be surprising when using relative paths.
|
||||
*/
|
||||
|
||||
|
||||
const isInstallingFromPostinstallHook = () => {
|
||||
// individual folders
|
||||
const cwdFolders = process.cwd().split(path.sep);
|
||||
const length = cwdFolders.length;
|
||||
return cwdFolders[length - 2] === 'node_modules' && cwdFolders[length - 1] === 'cypress';
|
||||
};
|
||||
|
||||
const getCacheDir = () => {
|
||||
let cache_directory = util.getCacheDir();
|
||||
|
||||
if (util.getEnv('CYPRESS_CACHE_FOLDER')) {
|
||||
const envVarCacheDir = untildify(util.getEnv('CYPRESS_CACHE_FOLDER'));
|
||||
debug('using environment variable CYPRESS_CACHE_FOLDER %s', envVarCacheDir);
|
||||
|
||||
if (!path.isAbsolute(envVarCacheDir) && isInstallingFromPostinstallHook()) {
|
||||
const packageRootFolder = path.join('..', '..', envVarCacheDir);
|
||||
cache_directory = path.resolve(packageRootFolder);
|
||||
debug('installing from postinstall hook, original root folder is %s', packageRootFolder);
|
||||
debug('and resolved cache directory is %s', cache_directory);
|
||||
} else {
|
||||
cache_directory = path.resolve(envVarCacheDir);
|
||||
}
|
||||
}
|
||||
|
||||
return cache_directory;
|
||||
};
|
||||
|
||||
const parseRealPlatformBinaryFolderAsync = binaryPath => {
|
||||
return fs.realpathAsync(binaryPath).then(realPath => {
|
||||
debug('CYPRESS_RUN_BINARY has realpath:', realPath);
|
||||
|
||||
if (!realPath.toString().endsWith(getPlatformExecutable())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (os.platform() === 'darwin') {
|
||||
return path.resolve(realPath, '..', '..', '..');
|
||||
}
|
||||
|
||||
return path.resolve(realPath, '..');
|
||||
});
|
||||
};
|
||||
|
||||
const getDistDir = () => {
|
||||
return path.join(__dirname, '..', '..', 'dist');
|
||||
};
|
||||
/**
|
||||
* Returns full filename to the file that keeps the Test Runner verification state as JSON text.
|
||||
* Note: the binary state file will be stored one level up from the given binary folder.
|
||||
* @param {string} binaryDir - full path to the folder holding the binary.
|
||||
*/
|
||||
|
||||
|
||||
const getBinaryStatePath = binaryDir => {
|
||||
return path.join(binaryDir, '..', 'binary_state.json');
|
||||
};
|
||||
|
||||
const getBinaryStateContentsAsync = binaryDir => {
|
||||
const fullPath = getBinaryStatePath(binaryDir);
|
||||
return fs.readJsonAsync(fullPath).catch({
|
||||
code: 'ENOENT'
|
||||
}, SyntaxError, () => {
|
||||
debug('could not read binary_state.json file at "%s"', fullPath);
|
||||
return {};
|
||||
});
|
||||
};
|
||||
|
||||
const getBinaryVerifiedAsync = binaryDir => {
|
||||
return getBinaryStateContentsAsync(binaryDir).tap(debug).get('verified');
|
||||
};
|
||||
|
||||
const clearBinaryStateAsync = binaryDir => {
|
||||
return fs.removeAsync(getBinaryStatePath(binaryDir));
|
||||
};
|
||||
/**
|
||||
* Writes the new binary status.
|
||||
* @param {boolean} verified The new test runner state after smoke test
|
||||
* @param {string} binaryDir Folder holding the binary
|
||||
* @returns {Promise<void>} returns a promise
|
||||
*/
|
||||
|
||||
|
||||
const writeBinaryVerifiedAsync = (verified, binaryDir) => {
|
||||
return getBinaryStateContentsAsync(binaryDir).then(contents => {
|
||||
return fs.outputJsonAsync(getBinaryStatePath(binaryDir), _.extend(contents, {
|
||||
verified
|
||||
}), {
|
||||
spaces: 2
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const getPathToExecutable = binaryDir => {
|
||||
return path.join(binaryDir, getPlatformExecutable());
|
||||
};
|
||||
/**
|
||||
* Resolves with an object read from the binary app package.json file.
|
||||
* If the file does not exist resolves with null
|
||||
*/
|
||||
|
||||
|
||||
const getBinaryPkgAsync = binaryDir => {
|
||||
const pathToPackageJson = getBinaryPkgPath(binaryDir);
|
||||
debug('Reading binary package.json from:', pathToPackageJson);
|
||||
return fs.pathExistsAsync(pathToPackageJson).then(exists => {
|
||||
if (!exists) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fs.readJsonAsync(pathToPackageJson);
|
||||
});
|
||||
};
|
||||
|
||||
const getBinaryPkgVersion = R.propOr(null, 'version');
|
||||
const getBinaryElectronVersion = R.propOr(null, 'electronVersion');
|
||||
const getBinaryElectronNodeVersion = R.propOr(null, 'electronNodeVersion');
|
||||
module.exports = {
|
||||
getPathToExecutable,
|
||||
getPlatformExecutable,
|
||||
// those names start to sound like Java
|
||||
getBinaryElectronNodeVersion,
|
||||
getBinaryElectronVersion,
|
||||
getBinaryPkgVersion,
|
||||
getBinaryVerifiedAsync,
|
||||
getBinaryPkgAsync,
|
||||
getBinaryPkgPath,
|
||||
getBinaryDir,
|
||||
getCacheDir,
|
||||
clearBinaryStateAsync,
|
||||
writeBinaryVerifiedAsync,
|
||||
parseRealPlatformBinaryFolderAsync,
|
||||
getDistDir,
|
||||
getVersionDir
|
||||
};
|
226
node_modules/cypress/lib/tasks/unzip.js
generated
vendored
Normal file
226
node_modules/cypress/lib/tasks/unzip.js
generated
vendored
Normal file
@@ -0,0 +1,226 @@
|
||||
"use strict";
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
const la = require('lazy-ass');
|
||||
|
||||
const is = require('check-more-types');
|
||||
|
||||
const cp = require('child_process');
|
||||
|
||||
const os = require('os');
|
||||
|
||||
const yauzl = require('yauzl');
|
||||
|
||||
const debug = require('debug')('cypress:cli:unzip');
|
||||
|
||||
const extract = require('extract-zip');
|
||||
|
||||
const Promise = require('bluebird');
|
||||
|
||||
const readline = require('readline');
|
||||
|
||||
const {
|
||||
throwFormErrorText,
|
||||
errors
|
||||
} = require('../errors');
|
||||
|
||||
const fs = require('../fs');
|
||||
|
||||
const util = require('../util');
|
||||
|
||||
const unzipTools = {
|
||||
extract
|
||||
}; // expose this function for simple testing
|
||||
|
||||
const unzip = ({
|
||||
zipFilePath,
|
||||
installDir,
|
||||
progress
|
||||
}) => {
|
||||
debug('unzipping from %s', zipFilePath);
|
||||
debug('into', installDir);
|
||||
|
||||
if (!zipFilePath) {
|
||||
throw new Error('Missing zip filename');
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
let yauzlDoneTime = 0;
|
||||
return fs.ensureDirAsync(installDir).then(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return yauzl.open(zipFilePath, (err, zipFile) => {
|
||||
yauzlDoneTime = Date.now();
|
||||
|
||||
if (err) {
|
||||
debug('error using yauzl %s', err.message);
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
const total = zipFile.entryCount;
|
||||
debug('zipFile entries count', total);
|
||||
const started = new Date();
|
||||
let percent = 0;
|
||||
let count = 0;
|
||||
|
||||
const notify = percent => {
|
||||
const elapsed = +new Date() - +started;
|
||||
const eta = util.calculateEta(percent, elapsed);
|
||||
progress.onProgress(percent, util.secsRemaining(eta));
|
||||
};
|
||||
|
||||
const tick = () => {
|
||||
count += 1;
|
||||
percent = count / total * 100;
|
||||
const displayPercent = percent.toFixed(0);
|
||||
return notify(displayPercent);
|
||||
};
|
||||
|
||||
const unzipWithNode = () => {
|
||||
debug('unzipping with node.js (slow)');
|
||||
const opts = {
|
||||
dir: installDir,
|
||||
onEntry: tick
|
||||
};
|
||||
debug('calling Node extract tool %s %o', zipFilePath, opts);
|
||||
return unzipTools.extract(zipFilePath, opts).then(() => {
|
||||
debug('node unzip finished');
|
||||
return resolve();
|
||||
}).catch(err => {
|
||||
if (err) {
|
||||
debug('error %s', err.message);
|
||||
return reject(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const unzipFallback = _.once(unzipWithNode);
|
||||
|
||||
const unzipWithUnzipTool = () => {
|
||||
debug('unzipping via `unzip`');
|
||||
const inflatingRe = /inflating:/;
|
||||
const sp = cp.spawn('unzip', ['-o', zipFilePath, '-d', installDir]);
|
||||
sp.on('error', err => {
|
||||
debug('unzip tool error: %s', err.message);
|
||||
unzipFallback();
|
||||
});
|
||||
sp.on('close', code => {
|
||||
debug('unzip tool close with code %d', code);
|
||||
|
||||
if (code === 0) {
|
||||
percent = 100;
|
||||
notify(percent);
|
||||
return resolve();
|
||||
}
|
||||
|
||||
debug('`unzip` failed %o', {
|
||||
code
|
||||
});
|
||||
return unzipFallback();
|
||||
});
|
||||
sp.stdout.on('data', data => {
|
||||
if (inflatingRe.test(data)) {
|
||||
return tick();
|
||||
}
|
||||
});
|
||||
sp.stderr.on('data', data => {
|
||||
debug('`unzip` stderr %s', data);
|
||||
});
|
||||
}; // we attempt to first unzip with the native osx
|
||||
// ditto because its less likely to have problems
|
||||
// with corruption, symlinks, or icons causing failures
|
||||
// and can handle resource forks
|
||||
// http://automatica.com.au/2011/02/unzip-mac-os-x-zip-in-terminal/
|
||||
|
||||
|
||||
const unzipWithOsx = () => {
|
||||
debug('unzipping via `ditto`');
|
||||
const copyingFileRe = /^copying file/;
|
||||
const sp = cp.spawn('ditto', ['-xkV', zipFilePath, installDir]); // f-it just unzip with node
|
||||
|
||||
sp.on('error', err => {
|
||||
debug(err.message);
|
||||
unzipFallback();
|
||||
});
|
||||
sp.on('close', code => {
|
||||
if (code === 0) {
|
||||
// make sure we get to 100% on the progress bar
|
||||
// because reading in lines is not really accurate
|
||||
percent = 100;
|
||||
notify(percent);
|
||||
return resolve();
|
||||
}
|
||||
|
||||
debug('`ditto` failed %o', {
|
||||
code
|
||||
});
|
||||
return unzipFallback();
|
||||
});
|
||||
return readline.createInterface({
|
||||
input: sp.stderr
|
||||
}).on('line', line => {
|
||||
if (copyingFileRe.test(line)) {
|
||||
return tick();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
switch (os.platform()) {
|
||||
case 'darwin':
|
||||
return unzipWithOsx();
|
||||
|
||||
case 'linux':
|
||||
return unzipWithUnzipTool();
|
||||
|
||||
case 'win32':
|
||||
return unzipWithNode();
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
});
|
||||
}).tap(() => {
|
||||
debug('unzip completed %o', {
|
||||
yauzlMs: yauzlDoneTime - startTime,
|
||||
unzipMs: Date.now() - yauzlDoneTime
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const start = ({
|
||||
zipFilePath,
|
||||
installDir,
|
||||
progress
|
||||
}) => {
|
||||
la(is.unemptyString(installDir), 'missing installDir');
|
||||
|
||||
if (!progress) {
|
||||
progress = {
|
||||
onProgress: () => {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return fs.pathExists(installDir).then(exists => {
|
||||
if (exists) {
|
||||
debug('removing existing unzipped binary', installDir);
|
||||
return fs.removeAsync(installDir);
|
||||
}
|
||||
}).then(() => {
|
||||
return unzip({
|
||||
zipFilePath,
|
||||
installDir,
|
||||
progress
|
||||
});
|
||||
}).catch(throwFormErrorText(errors.failedUnzip));
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
start,
|
||||
utils: {
|
||||
unzip,
|
||||
unzipTools
|
||||
}
|
||||
};
|
349
node_modules/cypress/lib/tasks/verify.js
generated
vendored
Normal file
349
node_modules/cypress/lib/tasks/verify.js
generated
vendored
Normal file
@@ -0,0 +1,349 @@
|
||||
"use strict";
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
const chalk = require('chalk');
|
||||
|
||||
const {
|
||||
Listr
|
||||
} = require('listr2');
|
||||
|
||||
const debug = require('debug')('cypress:cli');
|
||||
|
||||
const {
|
||||
stripIndent
|
||||
} = require('common-tags');
|
||||
|
||||
const Promise = require('bluebird');
|
||||
|
||||
const logSymbols = require('log-symbols');
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const os = require('os');
|
||||
|
||||
const verbose = require('../VerboseRenderer');
|
||||
|
||||
const {
|
||||
throwFormErrorText,
|
||||
errors
|
||||
} = require('../errors');
|
||||
|
||||
const util = require('../util');
|
||||
|
||||
const logger = require('../logger');
|
||||
|
||||
const xvfb = require('../exec/xvfb');
|
||||
|
||||
const state = require('./state');
|
||||
|
||||
const VERIFY_TEST_RUNNER_TIMEOUT_MS = 30000;
|
||||
|
||||
const checkExecutable = binaryDir => {
|
||||
const executable = state.getPathToExecutable(binaryDir);
|
||||
debug('checking if executable exists', executable);
|
||||
return util.isExecutableAsync(executable).then(isExecutable => {
|
||||
debug('Binary is executable? :', isExecutable);
|
||||
|
||||
if (!isExecutable) {
|
||||
return throwFormErrorText(errors.binaryNotExecutable(executable))();
|
||||
}
|
||||
}).catch({
|
||||
code: 'ENOENT'
|
||||
}, () => {
|
||||
if (util.isCi()) {
|
||||
return throwFormErrorText(errors.notInstalledCI(executable))();
|
||||
}
|
||||
|
||||
return throwFormErrorText(errors.missingApp(binaryDir))(stripIndent`
|
||||
Cypress executable not found at: ${chalk.cyan(executable)}
|
||||
`);
|
||||
});
|
||||
};
|
||||
|
||||
const runSmokeTest = (binaryDir, options) => {
|
||||
let executable = state.getPathToExecutable(binaryDir);
|
||||
|
||||
const onSmokeTestError = (smokeTestCommand, linuxWithDisplayEnv) => {
|
||||
return err => {
|
||||
debug('Smoke test failed:', err);
|
||||
let errMessage = err.stderr || err.message;
|
||||
debug('error message:', errMessage);
|
||||
|
||||
if (err.timedOut) {
|
||||
debug('error timedOut is true');
|
||||
return throwFormErrorText(errors.smokeTestFailure(smokeTestCommand, true))(errMessage);
|
||||
}
|
||||
|
||||
if (linuxWithDisplayEnv && util.isBrokenGtkDisplay(errMessage)) {
|
||||
util.logBrokenGtkDisplayWarning();
|
||||
return throwFormErrorText(errors.invalidSmokeTestDisplayError)(errMessage);
|
||||
}
|
||||
|
||||
return throwFormErrorText(errors.missingDependency)(errMessage);
|
||||
};
|
||||
};
|
||||
|
||||
const needsXvfb = xvfb.isNeeded();
|
||||
debug('needs Xvfb?', needsXvfb);
|
||||
/**
|
||||
* Spawn Cypress running smoke test to check if all operating system
|
||||
* dependencies are good.
|
||||
*/
|
||||
|
||||
const spawn = linuxWithDisplayEnv => {
|
||||
const random = _.random(0, 1000);
|
||||
|
||||
const args = ['--smoke-test', `--ping=${random}`];
|
||||
|
||||
if (needsSandbox()) {
|
||||
// electron requires --no-sandbox to run as root
|
||||
debug('disabling Electron sandbox');
|
||||
args.unshift('--no-sandbox');
|
||||
}
|
||||
|
||||
if (options.dev) {
|
||||
executable = 'node';
|
||||
args.unshift(path.resolve(__dirname, '..', '..', '..', 'scripts', 'start.js'));
|
||||
}
|
||||
|
||||
const smokeTestCommand = `${executable} ${args.join(' ')}`;
|
||||
debug('running smoke test');
|
||||
debug('using Cypress executable %s', executable);
|
||||
debug('smoke test command:', smokeTestCommand);
|
||||
debug('smoke test timeout %d ms', options.smokeTestTimeout);
|
||||
|
||||
const env = _.extend({}, process.env, {
|
||||
ELECTRON_ENABLE_LOGGING: true
|
||||
});
|
||||
|
||||
const stdioOptions = _.extend({}, {
|
||||
env,
|
||||
timeout: options.smokeTestTimeout
|
||||
});
|
||||
|
||||
return Promise.resolve(util.exec(executable, args, stdioOptions)).catch(onSmokeTestError(smokeTestCommand, linuxWithDisplayEnv)).then(result => {
|
||||
// TODO: when execa > 1.1 is released
|
||||
// change this to `result.all` for both stderr and stdout
|
||||
// use lodash to be robust during tests against null result or missing stdout
|
||||
const smokeTestStdout = _.get(result, 'stdout', '');
|
||||
|
||||
debug('smoke test stdout "%s"', smokeTestStdout);
|
||||
|
||||
if (!util.stdoutLineMatches(String(random), smokeTestStdout)) {
|
||||
debug('Smoke test failed because could not find %d in:', random, result);
|
||||
|
||||
const smokeTestStderr = _.get(result, 'stderr', '');
|
||||
|
||||
const errorText = smokeTestStderr || smokeTestStdout;
|
||||
return throwFormErrorText(errors.smokeTestFailure(smokeTestCommand, false))(errorText);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const spawnInXvfb = linuxWithDisplayEnv => {
|
||||
return xvfb.start().then(() => {
|
||||
return spawn(linuxWithDisplayEnv);
|
||||
}).finally(xvfb.stop);
|
||||
};
|
||||
|
||||
const userFriendlySpawn = linuxWithDisplayEnv => {
|
||||
debug('spawning, should retry on display problem?', Boolean(linuxWithDisplayEnv));
|
||||
return spawn(linuxWithDisplayEnv).catch({
|
||||
code: 'INVALID_SMOKE_TEST_DISPLAY_ERROR'
|
||||
}, () => {
|
||||
return spawnInXvfb(linuxWithDisplayEnv);
|
||||
});
|
||||
};
|
||||
|
||||
if (needsXvfb) {
|
||||
return spawnInXvfb();
|
||||
} // if we are on linux and there's already a DISPLAY
|
||||
// set, then we may need to rerun cypress after
|
||||
// spawning our own Xvfb server
|
||||
|
||||
|
||||
const linuxWithDisplayEnv = util.isPossibleLinuxWithIncorrectDisplay();
|
||||
return userFriendlySpawn(linuxWithDisplayEnv);
|
||||
};
|
||||
|
||||
function testBinary(version, binaryDir, options) {
|
||||
debug('running binary verification check', version); // if running from 'cypress verify', don't print this message
|
||||
|
||||
if (!options.force) {
|
||||
logger.log(stripIndent`
|
||||
It looks like this is your first time using Cypress: ${chalk.cyan(version)}
|
||||
`);
|
||||
}
|
||||
|
||||
logger.log(); // if we are running in CI then use
|
||||
// the verbose renderer else use
|
||||
// the default
|
||||
|
||||
let renderer = util.isCi() ? verbose : 'default';
|
||||
if (logger.logLevel() === 'silent') renderer = 'silent';
|
||||
const rendererOptions = {
|
||||
renderer
|
||||
};
|
||||
const tasks = new Listr([{
|
||||
options: {
|
||||
title: util.titleize('Verifying Cypress can run', chalk.gray(binaryDir))
|
||||
},
|
||||
task: (ctx, task) => {
|
||||
debug('clearing out the verified version');
|
||||
return state.clearBinaryStateAsync(binaryDir).then(() => {
|
||||
return Promise.all([runSmokeTest(binaryDir, options), Promise.resolve().delay(1500) // good user experience
|
||||
]);
|
||||
}).then(() => {
|
||||
debug('write verified: true');
|
||||
return state.writeBinaryVerifiedAsync(true, binaryDir);
|
||||
}).then(() => {
|
||||
util.setTaskTitle(task, util.titleize(chalk.green('Verified Cypress!'), chalk.gray(binaryDir)), rendererOptions.renderer);
|
||||
});
|
||||
}
|
||||
}], {
|
||||
rendererOptions
|
||||
});
|
||||
return tasks.run();
|
||||
}
|
||||
|
||||
const maybeVerify = (installedVersion, binaryDir, options) => {
|
||||
return state.getBinaryVerifiedAsync(binaryDir).then(isVerified => {
|
||||
debug('is Verified ?', isVerified);
|
||||
let shouldVerify = !isVerified; // force verify if options.force
|
||||
|
||||
if (options.force) {
|
||||
debug('force verify');
|
||||
shouldVerify = true;
|
||||
}
|
||||
|
||||
if (shouldVerify) {
|
||||
return testBinary(installedVersion, binaryDir, options).then(() => {
|
||||
if (options.welcomeMessage) {
|
||||
logger.log();
|
||||
logger.log('Opening Cypress...');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const start = (options = {}) => {
|
||||
debug('verifying Cypress app');
|
||||
const packageVersion = util.pkgVersion();
|
||||
let binaryDir = state.getBinaryDir(packageVersion);
|
||||
|
||||
_.defaults(options, {
|
||||
dev: false,
|
||||
force: false,
|
||||
welcomeMessage: true,
|
||||
smokeTestTimeout: VERIFY_TEST_RUNNER_TIMEOUT_MS
|
||||
});
|
||||
|
||||
if (options.dev) {
|
||||
return runSmokeTest('', options);
|
||||
}
|
||||
|
||||
const parseBinaryEnvVar = () => {
|
||||
const envBinaryPath = util.getEnv('CYPRESS_RUN_BINARY');
|
||||
debug('CYPRESS_RUN_BINARY exists, =', envBinaryPath);
|
||||
logger.log(stripIndent`
|
||||
${chalk.yellow('Note:')} You have set the environment variable:
|
||||
|
||||
${chalk.white('CYPRESS_RUN_BINARY=')}${chalk.cyan(envBinaryPath)}
|
||||
|
||||
This overrides the default Cypress binary path used.
|
||||
`);
|
||||
logger.log();
|
||||
return util.isExecutableAsync(envBinaryPath).then(isExecutable => {
|
||||
debug('CYPRESS_RUN_BINARY is executable? :', isExecutable);
|
||||
|
||||
if (!isExecutable) {
|
||||
return throwFormErrorText(errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath))(stripIndent`
|
||||
The supplied binary path is not executable
|
||||
`);
|
||||
}
|
||||
}).then(() => {
|
||||
return state.parseRealPlatformBinaryFolderAsync(envBinaryPath);
|
||||
}).then(envBinaryDir => {
|
||||
if (!envBinaryDir) {
|
||||
return throwFormErrorText(errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath))();
|
||||
}
|
||||
|
||||
debug('CYPRESS_RUN_BINARY has binaryDir:', envBinaryDir);
|
||||
binaryDir = envBinaryDir;
|
||||
}).catch({
|
||||
code: 'ENOENT'
|
||||
}, err => {
|
||||
return throwFormErrorText(errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath))(err.message);
|
||||
});
|
||||
};
|
||||
|
||||
return Promise.try(() => {
|
||||
debug('checking environment variables');
|
||||
|
||||
if (util.getEnv('CYPRESS_RUN_BINARY')) {
|
||||
return parseBinaryEnvVar();
|
||||
}
|
||||
}).then(() => {
|
||||
return checkExecutable(binaryDir);
|
||||
}).tap(() => {
|
||||
return debug('binaryDir is ', binaryDir);
|
||||
}).then(() => {
|
||||
return state.getBinaryPkgAsync(binaryDir);
|
||||
}).then(pkg => {
|
||||
return state.getBinaryPkgVersion(pkg);
|
||||
}).then(binaryVersion => {
|
||||
if (!binaryVersion) {
|
||||
debug('no Cypress binary found for cli version ', packageVersion);
|
||||
return throwFormErrorText(errors.missingApp(binaryDir))(`
|
||||
Cannot read binary version from: ${chalk.cyan(state.getBinaryPkgPath(binaryDir))}
|
||||
`);
|
||||
}
|
||||
|
||||
debug(`Found binary version ${chalk.green(binaryVersion)} installed in: ${chalk.cyan(binaryDir)}`);
|
||||
|
||||
if (binaryVersion !== packageVersion) {
|
||||
// warn if we installed with CYPRESS_INSTALL_BINARY or changed version
|
||||
// in the package.json
|
||||
logger.log(`Found binary version ${chalk.green(binaryVersion)} installed in: ${chalk.cyan(binaryDir)}`);
|
||||
logger.log();
|
||||
logger.warn(stripIndent`
|
||||
|
||||
|
||||
${logSymbols.warning} Warning: Binary version ${chalk.green(binaryVersion)} does not match the expected package version ${chalk.green(packageVersion)}
|
||||
|
||||
These versions may not work properly together.
|
||||
`);
|
||||
logger.log();
|
||||
}
|
||||
|
||||
return maybeVerify(binaryVersion, binaryDir, options);
|
||||
}).catch(err => {
|
||||
if (err.known) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
return throwFormErrorText(errors.unexpected)(err.stack);
|
||||
});
|
||||
};
|
||||
|
||||
const isLinuxLike = () => os.platform() !== 'win32';
|
||||
/**
|
||||
* Returns true if running on a system where Electron needs "--no-sandbox" flag.
|
||||
* @see https://crbug.com/638180
|
||||
*
|
||||
* On Debian we had problems running in sandbox even for non-root users.
|
||||
* @see https://github.com/cypress-io/cypress/issues/5434
|
||||
* Seems there is a lot of discussion around this issue among Electron users
|
||||
* @see https://github.com/electron/electron/issues/17972
|
||||
*/
|
||||
|
||||
|
||||
const needsSandbox = () => isLinuxLike();
|
||||
|
||||
module.exports = {
|
||||
start,
|
||||
VERIFY_TEST_RUNNER_TIMEOUT_MS,
|
||||
needsSandbox
|
||||
};
|
Reference in New Issue
Block a user