download.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. 'use strict';
  2. var _templateObject = _taggedTemplateLiteral(['\n URL: ', '\n ', '\n '], ['\n URL: ', '\n ', '\n ']),
  3. _templateObject2 = _taggedTemplateLiteral(['\n Corrupted download\n\n Expected downloaded file to have checksum: ', '\n Computed checksum: ', '\n\n Expected downloaded file to have size: ', '\n Computed size: ', '\n '], ['\n Corrupted download\n\n Expected downloaded file to have checksum: ', '\n Computed checksum: ', '\n\n Expected downloaded file to have size: ', '\n Computed size: ', '\n ']),
  4. _templateObject3 = _taggedTemplateLiteral(['\n Corrupted download\n\n Expected downloaded file to have checksum: ', '\n Computed checksum: ', '\n '], ['\n Corrupted download\n\n Expected downloaded file to have checksum: ', '\n Computed checksum: ', '\n ']),
  5. _templateObject4 = _taggedTemplateLiteral(['\n Corrupted download\n\n Expected downloaded file to have size: ', '\n Computed size: ', '\n '], ['\n Corrupted download\n\n Expected downloaded file to have size: ', '\n Computed size: ', '\n ']),
  6. _templateObject5 = _taggedTemplateLiteral(['\n Failed downloading the Cypress binary.\n Response code: ', '\n Response message: ', '\n '], ['\n Failed downloading the Cypress binary.\n Response code: ', '\n Response message: ', '\n ']);
  7. function _taggedTemplateLiteral(strings, raw) { return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); }
  8. var arch = require('arch');
  9. var la = require('lazy-ass');
  10. var is = require('check-more-types');
  11. var os = require('os');
  12. var url = require('url');
  13. var path = require('path');
  14. var debug = require('debug')('cypress:cli');
  15. var request = require('request');
  16. var Promise = require('bluebird');
  17. var requestProgress = require('request-progress');
  18. var _require = require('common-tags'),
  19. stripIndent = _require.stripIndent;
  20. var _require2 = require('../errors'),
  21. throwFormErrorText = _require2.throwFormErrorText,
  22. errors = _require2.errors;
  23. var fs = require('../fs');
  24. var util = require('../util');
  25. var defaultBaseUrl = 'https://download.cypress.io/';
  26. var getProxyUrl = function getProxyUrl() {
  27. 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;
  28. };
  29. var getRealOsArch = function getRealOsArch() {
  30. // os.arch() returns the arch for which this node was compiled
  31. // we want the operating system's arch instead: x64 or x86
  32. var osArch = arch();
  33. if (osArch === 'x86') {
  34. // match process.platform output
  35. return 'ia32';
  36. }
  37. return osArch;
  38. };
  39. var getBaseUrl = function getBaseUrl() {
  40. if (util.getEnv('CYPRESS_DOWNLOAD_MIRROR')) {
  41. var baseUrl = util.getEnv('CYPRESS_DOWNLOAD_MIRROR');
  42. if (!baseUrl.endsWith('/')) {
  43. baseUrl += '/';
  44. }
  45. return baseUrl;
  46. }
  47. return defaultBaseUrl;
  48. };
  49. var prepend = function prepend(urlPath) {
  50. var endpoint = url.resolve(getBaseUrl(), urlPath);
  51. var platform = os.platform();
  52. var arch = getRealOsArch();
  53. return endpoint + '?platform=' + platform + '&arch=' + arch;
  54. };
  55. var getUrl = function getUrl(version) {
  56. if (is.url(version)) {
  57. debug('version is already an url', version);
  58. return version;
  59. }
  60. return version ? prepend('desktop/' + version) : prepend('desktop');
  61. };
  62. var statusMessage = function statusMessage(err) {
  63. return err.statusCode ? [err.statusCode, err.statusMessage].join(' - ') : err.toString();
  64. };
  65. var prettyDownloadErr = function prettyDownloadErr(err, version) {
  66. var msg = stripIndent(_templateObject, getUrl(version), statusMessage(err));
  67. debug(msg);
  68. return throwFormErrorText(errors.failedDownload)(msg);
  69. };
  70. /**
  71. * Checks checksum and file size for the given file. Allows both
  72. * values or just one of them to be checked.
  73. */
  74. var verifyDownloadedFile = function verifyDownloadedFile(filename, expectedSize, expectedChecksum) {
  75. if (expectedSize && expectedChecksum) {
  76. debug('verifying checksum and file size');
  77. return Promise.join(util.getFileChecksum(filename), util.getFileSize(filename), function (checksum, filesize) {
  78. if (checksum === expectedChecksum && filesize === expectedSize) {
  79. debug('downloaded file has the expected checksum and size ✅');
  80. return;
  81. }
  82. debug('raising error: checksum or file size mismatch');
  83. var text = stripIndent(_templateObject2, expectedChecksum, checksum, expectedSize, filesize);
  84. debug(text);
  85. throw new Error(text);
  86. });
  87. }
  88. if (expectedChecksum) {
  89. debug('only checking expected file checksum %d', expectedChecksum);
  90. return util.getFileChecksum(filename).then(function (checksum) {
  91. if (checksum === expectedChecksum) {
  92. debug('downloaded file has the expected checksum ✅');
  93. return;
  94. }
  95. debug('raising error: file checksum mismatch');
  96. var text = stripIndent(_templateObject3, expectedChecksum, checksum);
  97. throw new Error(text);
  98. });
  99. }
  100. if (expectedSize) {
  101. // maybe we don't have a checksum, but at least CDN returns content length
  102. // which we can check against the file size
  103. debug('only checking expected file size %d', expectedSize);
  104. return util.getFileSize(filename).then(function (filesize) {
  105. if (filesize === expectedSize) {
  106. debug('downloaded file has the expected size ✅');
  107. return;
  108. }
  109. debug('raising error: file size mismatch');
  110. var text = stripIndent(_templateObject4, expectedSize, filesize);
  111. throw new Error(text);
  112. });
  113. }
  114. debug('downloaded file lacks checksum or size to verify');
  115. return Promise.resolve();
  116. };
  117. // downloads from given url
  118. // return an object with
  119. // {filename: ..., downloaded: true}
  120. var downloadFromUrl = function downloadFromUrl(_ref) {
  121. var url = _ref.url,
  122. downloadDestination = _ref.downloadDestination,
  123. progress = _ref.progress;
  124. return new Promise(function (resolve, reject) {
  125. var proxy = getProxyUrl();
  126. debug('Downloading package', {
  127. url: url,
  128. proxy: proxy,
  129. downloadDestination: downloadDestination
  130. });
  131. var redirectVersion = void 0;
  132. var req = request({
  133. url: url,
  134. proxy: proxy,
  135. followRedirect: function followRedirect(response) {
  136. var version = response.headers['x-version'];
  137. debug('redirect version:', version);
  138. if (version) {
  139. // set the version in options if we have one.
  140. // this insulates us from potential redirect
  141. // problems where version would be set to undefined.
  142. redirectVersion = version;
  143. }
  144. // yes redirect
  145. return true;
  146. }
  147. });
  148. // closure
  149. var started = null;
  150. var expectedSize = void 0;
  151. var expectedChecksum = void 0;
  152. requestProgress(req, {
  153. throttle: progress.throttle
  154. }).on('response', function (response) {
  155. // we have computed checksum and filesize during test runner binary build
  156. // and have set it on the S3 object as user meta data, available via
  157. // these custom headers "x-amz-meta-..."
  158. // see https://github.com/cypress-io/cypress/pull/4092
  159. expectedSize = response.headers['x-amz-meta-size'] || response.headers['content-length'];
  160. expectedChecksum = response.headers['x-amz-meta-checksum'];
  161. if (expectedChecksum) {
  162. debug('expected checksum %s', expectedChecksum);
  163. }
  164. if (expectedSize) {
  165. // convert from string (all Amazon custom headers are strings)
  166. expectedSize = Number(expectedSize);
  167. debug('expected file size %d', expectedSize);
  168. }
  169. // start counting now once we've gotten
  170. // response headers
  171. started = new Date();
  172. // if our status code does not start with 200
  173. if (!/^2/.test(response.statusCode)) {
  174. debug('response code %d', response.statusCode);
  175. var err = new Error(stripIndent(_templateObject5, response.statusCode, response.statusMessage));
  176. reject(err);
  177. }
  178. }).on('error', reject).on('progress', function (state) {
  179. // total time we've elapsed
  180. // starting on our first progress notification
  181. var elapsed = new Date() - started;
  182. // request-progress sends a value between 0 and 1
  183. var percentage = util.convertPercentToPercentage(state.percent);
  184. var eta = util.calculateEta(percentage, elapsed);
  185. // send up our percent and seconds remaining
  186. progress.onProgress(percentage, util.secsRemaining(eta));
  187. })
  188. // save this download here
  189. .pipe(fs.createWriteStream(downloadDestination)).on('finish', function () {
  190. debug('downloading finished');
  191. verifyDownloadedFile(downloadDestination, expectedSize, expectedChecksum).then(function () {
  192. return resolve(redirectVersion);
  193. }, reject);
  194. });
  195. });
  196. };
  197. /**
  198. * Download Cypress.zip from external url to local file.
  199. * @param [string] version Could be "3.3.0" or full URL
  200. * @param [string] downloadDestination Local filename to save as
  201. */
  202. var start = function start(opts) {
  203. var version = opts.version,
  204. downloadDestination = opts.downloadDestination,
  205. progress = opts.progress;
  206. if (!downloadDestination) {
  207. la(is.unemptyString(downloadDestination), 'missing download dir', opts);
  208. }
  209. if (!progress) {
  210. progress = { onProgress: function onProgress() {
  211. return {};
  212. } };
  213. }
  214. var url = getUrl(version);
  215. progress.throttle = 100;
  216. debug('needed Cypress version: %s', version);
  217. debug('source url %s', url);
  218. debug('downloading cypress.zip to "' + downloadDestination + '"');
  219. // ensure download dir exists
  220. return fs.ensureDirAsync(path.dirname(downloadDestination)).then(function () {
  221. return downloadFromUrl({ url: url, downloadDestination: downloadDestination, progress: progress });
  222. }).catch(function (err) {
  223. return prettyDownloadErr(err, version);
  224. });
  225. };
  226. module.exports = {
  227. start: start,
  228. getUrl: getUrl,
  229. getProxyUrl: getProxyUrl
  230. };