cypress 15.1.0 → 15.3.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/bin/cypress +3 -1
- package/dist/VerboseRenderer.js +61 -0
- package/dist/cli.js +544 -0
- package/dist/cypress.js +104 -0
- package/dist/errors.js +391 -0
- package/dist/exec/info.js +103 -0
- package/dist/exec/open.js +103 -0
- package/dist/exec/run.js +177 -0
- package/dist/exec/shared.js +55 -0
- package/dist/exec/spawn.js +301 -0
- package/dist/exec/versions.js +67 -0
- package/dist/exec/xvfb.js +118 -0
- package/dist/index.js +52 -0
- package/dist/index.mjs +9 -0
- package/dist/logger.js +55 -0
- package/dist/tasks/cache.js +144 -0
- package/dist/tasks/download.js +304 -0
- package/dist/tasks/get-folder-size.js +44 -0
- package/dist/tasks/install.js +326 -0
- package/dist/tasks/state.js +184 -0
- package/dist/tasks/unzip.js +192 -0
- package/dist/tasks/verify.js +303 -0
- package/dist/util.js +452 -0
- package/package.json +10 -13
- package/types/cypress-automation.d.ts +2 -1
- package/types/cypress.d.ts +1 -0
- package/index.js +0 -27
- package/index.mjs +0 -17
- package/lib/VerboseRenderer.js +0 -58
- package/lib/cli.js +0 -411
- package/lib/cypress.js +0 -98
- package/lib/errors.js +0 -392
- package/lib/exec/info.js +0 -92
- package/lib/exec/open.js +0 -90
- package/lib/exec/run.js +0 -176
- package/lib/exec/shared.js +0 -62
- package/lib/exec/spawn.js +0 -247
- package/lib/exec/versions.js +0 -53
- package/lib/exec/xvfb.js +0 -93
- package/lib/fs.js +0 -4
- package/lib/logger.js +0 -50
- package/lib/tasks/cache.js +0 -132
- package/lib/tasks/download.js +0 -324
- package/lib/tasks/get-folder-size.js +0 -33
- package/lib/tasks/install.js +0 -368
- package/lib/tasks/state.js +0 -185
- package/lib/tasks/unzip.js +0 -200
- package/lib/tasks/verify.js +0 -300
- package/lib/util.js +0 -448
@@ -0,0 +1,304 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
9
|
+
});
|
10
|
+
};
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
13
|
+
};
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
15
|
+
const os_1 = __importDefault(require("os"));
|
16
|
+
const assert_1 = __importDefault(require("assert"));
|
17
|
+
const lodash_1 = __importDefault(require("lodash"));
|
18
|
+
const url_1 = __importDefault(require("url"));
|
19
|
+
const path_1 = __importDefault(require("path"));
|
20
|
+
const debug_1 = __importDefault(require("debug"));
|
21
|
+
const request_1 = __importDefault(require("@cypress/request"));
|
22
|
+
const bluebird_1 = __importDefault(require("bluebird"));
|
23
|
+
const request_progress_1 = __importDefault(require("request-progress"));
|
24
|
+
const common_tags_1 = require("common-tags");
|
25
|
+
const proxy_from_env_1 = require("proxy-from-env");
|
26
|
+
const errors_1 = require("../errors");
|
27
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
28
|
+
const util_1 = __importDefault(require("../util"));
|
29
|
+
const debug = (0, debug_1.default)('cypress:cli');
|
30
|
+
const defaultBaseUrl = 'https://download.cypress.io/';
|
31
|
+
const defaultMaxRedirects = 10;
|
32
|
+
const getProxyForUrlWithNpmConfig = (url) => {
|
33
|
+
return (0, proxy_from_env_1.getProxyForUrl)(url) ||
|
34
|
+
process.env.npm_config_https_proxy ||
|
35
|
+
process.env.npm_config_proxy ||
|
36
|
+
null;
|
37
|
+
};
|
38
|
+
const getBaseUrl = () => {
|
39
|
+
if (util_1.default.getEnv('CYPRESS_DOWNLOAD_MIRROR')) {
|
40
|
+
let baseUrl = util_1.default.getEnv('CYPRESS_DOWNLOAD_MIRROR');
|
41
|
+
if (!(baseUrl === null || baseUrl === void 0 ? void 0 : baseUrl.endsWith('/'))) {
|
42
|
+
baseUrl += '/';
|
43
|
+
}
|
44
|
+
return baseUrl || defaultBaseUrl;
|
45
|
+
}
|
46
|
+
return defaultBaseUrl;
|
47
|
+
};
|
48
|
+
const getCA = () => __awaiter(void 0, void 0, void 0, function* () {
|
49
|
+
if (process.env.npm_config_cafile) {
|
50
|
+
try {
|
51
|
+
const caFileContent = yield fs_extra_1.default.readFile(process.env.npm_config_cafile, 'utf8');
|
52
|
+
return caFileContent;
|
53
|
+
}
|
54
|
+
catch (error) {
|
55
|
+
debug('error reading ca file', error);
|
56
|
+
return;
|
57
|
+
}
|
58
|
+
}
|
59
|
+
if (process.env.npm_config_ca) {
|
60
|
+
return process.env.npm_config_ca;
|
61
|
+
}
|
62
|
+
return;
|
63
|
+
});
|
64
|
+
const prepend = (arch, urlPath, version) => {
|
65
|
+
const endpoint = url_1.default.resolve(getBaseUrl(), urlPath);
|
66
|
+
const platform = os_1.default.platform();
|
67
|
+
const pathTemplate = util_1.default.getEnv('CYPRESS_DOWNLOAD_PATH_TEMPLATE', true);
|
68
|
+
if ((platform === 'win32') && (arch === 'arm64')) {
|
69
|
+
debug(`detected platform ${platform} architecture ${arch} combination`);
|
70
|
+
arch = 'x64';
|
71
|
+
debug(`overriding to download ${platform}-${arch} instead`);
|
72
|
+
}
|
73
|
+
return pathTemplate
|
74
|
+
? (pathTemplate
|
75
|
+
.replace(/\\?\$\{endpoint\}/g, endpoint)
|
76
|
+
.replace(/\\?\$\{platform\}/g, platform)
|
77
|
+
.replace(/\\?\$\{arch\}/g, arch)
|
78
|
+
.replace(/\\?\$\{version\}/g, version))
|
79
|
+
: `${endpoint}?platform=${platform}&arch=${arch}`;
|
80
|
+
};
|
81
|
+
const getUrl = (arch, version) => {
|
82
|
+
if (lodash_1.default.isString(version) && version.match(/^https?:\/\/.*$/)) {
|
83
|
+
debug('version is already an url', version);
|
84
|
+
return version;
|
85
|
+
}
|
86
|
+
const urlPath = version ? `desktop/${version}` : 'desktop';
|
87
|
+
return prepend(arch, urlPath, version || '');
|
88
|
+
};
|
89
|
+
const statusMessage = (err) => {
|
90
|
+
return (err.statusCode
|
91
|
+
? [err.statusCode, err.statusMessage].join(' - ')
|
92
|
+
: err.toString());
|
93
|
+
};
|
94
|
+
const prettyDownloadErr = (err, url) => {
|
95
|
+
const msg = (0, common_tags_1.stripIndent) `
|
96
|
+
URL: ${url}
|
97
|
+
${statusMessage(err)}
|
98
|
+
`;
|
99
|
+
debug(msg);
|
100
|
+
return (0, errors_1.throwFormErrorText)(errors_1.errors.failedDownload)(msg);
|
101
|
+
};
|
102
|
+
/**
|
103
|
+
* Checks checksum and file size for the given file. Allows both
|
104
|
+
* values or just one of them to be checked.
|
105
|
+
*/
|
106
|
+
const verifyDownloadedFile = (filename, expectedSize, expectedChecksum) => __awaiter(void 0, void 0, void 0, function* () {
|
107
|
+
if (expectedSize && expectedChecksum) {
|
108
|
+
debug('verifying checksum and file size');
|
109
|
+
return bluebird_1.default.join(util_1.default.getFileChecksum(filename), util_1.default.getFileSize(filename), (checksum, filesize) => {
|
110
|
+
if (checksum === expectedChecksum && filesize === expectedSize) {
|
111
|
+
debug('downloaded file has the expected checksum and size ✅');
|
112
|
+
return;
|
113
|
+
}
|
114
|
+
debug('raising error: checksum or file size mismatch');
|
115
|
+
const text = (0, common_tags_1.stripIndent) `
|
116
|
+
Corrupted download
|
117
|
+
|
118
|
+
Expected downloaded file to have checksum: ${expectedChecksum}
|
119
|
+
Computed checksum: ${checksum}
|
120
|
+
|
121
|
+
Expected downloaded file to have size: ${expectedSize}
|
122
|
+
Computed size: ${filesize}
|
123
|
+
`;
|
124
|
+
debug(text);
|
125
|
+
throw new Error(text);
|
126
|
+
});
|
127
|
+
}
|
128
|
+
if (expectedChecksum) {
|
129
|
+
debug('only checking expected file checksum %d', expectedChecksum);
|
130
|
+
const checksum = yield util_1.default.getFileChecksum(filename);
|
131
|
+
if (checksum === expectedChecksum) {
|
132
|
+
debug('downloaded file has the expected checksum ✅');
|
133
|
+
return;
|
134
|
+
}
|
135
|
+
debug('raising error: file checksum mismatch');
|
136
|
+
const text = (0, common_tags_1.stripIndent) `
|
137
|
+
Corrupted download
|
138
|
+
|
139
|
+
Expected downloaded file to have checksum: ${expectedChecksum}
|
140
|
+
Computed checksum: ${checksum}
|
141
|
+
`;
|
142
|
+
throw new Error(text);
|
143
|
+
}
|
144
|
+
if (expectedSize) {
|
145
|
+
// maybe we don't have a checksum, but at least CDN returns content length
|
146
|
+
// which we can check against the file size
|
147
|
+
debug('only checking expected file size %d', expectedSize);
|
148
|
+
const filesize = yield util_1.default.getFileSize(filename);
|
149
|
+
if (filesize === expectedSize) {
|
150
|
+
debug('downloaded file has the expected size ✅');
|
151
|
+
return;
|
152
|
+
}
|
153
|
+
debug('raising error: file size mismatch');
|
154
|
+
const text = (0, common_tags_1.stripIndent) `
|
155
|
+
Corrupted download
|
156
|
+
|
157
|
+
Expected downloaded file to have size: ${expectedSize}
|
158
|
+
Computed size: ${filesize}
|
159
|
+
`;
|
160
|
+
throw new Error(text);
|
161
|
+
}
|
162
|
+
debug('downloaded file lacks checksum or size to verify');
|
163
|
+
return;
|
164
|
+
});
|
165
|
+
// downloads from given url
|
166
|
+
// return an object with
|
167
|
+
// {filename: ..., downloaded: true}
|
168
|
+
const downloadFromUrl = ({ url, downloadDestination, progress, ca, version, redirectTTL = defaultMaxRedirects }) => {
|
169
|
+
if (redirectTTL <= 0) {
|
170
|
+
return bluebird_1.default.reject(new Error((0, common_tags_1.stripIndent) `
|
171
|
+
Failed downloading the Cypress binary.
|
172
|
+
There were too many redirects. The default allowance is ${defaultMaxRedirects}.
|
173
|
+
Maybe you got stuck in a redirect loop?
|
174
|
+
`));
|
175
|
+
}
|
176
|
+
return new bluebird_1.default((resolve, reject) => {
|
177
|
+
const proxy = getProxyForUrlWithNpmConfig(url);
|
178
|
+
debug('Downloading package', {
|
179
|
+
url,
|
180
|
+
proxy,
|
181
|
+
downloadDestination,
|
182
|
+
});
|
183
|
+
if (ca) {
|
184
|
+
debug('using custom CA details from npm config');
|
185
|
+
}
|
186
|
+
const reqOptions = Object.assign(Object.assign(Object.assign({ uri: url }, (proxy ? { proxy } : {})), (ca ? { agentOptions: { ca } } : {})), { method: 'GET', followRedirect: false });
|
187
|
+
const req = (0, request_1.default)(reqOptions);
|
188
|
+
// closure
|
189
|
+
let started = null;
|
190
|
+
let expectedSize;
|
191
|
+
let expectedChecksum;
|
192
|
+
(0, request_progress_1.default)(req, {
|
193
|
+
throttle: progress.throttle,
|
194
|
+
})
|
195
|
+
.on('response', (response) => {
|
196
|
+
// we have computed checksum and filesize during test runner binary build
|
197
|
+
// and have set it on the S3 object as user meta data, available via
|
198
|
+
// these custom headers "x-amz-meta-..."
|
199
|
+
// see https://github.com/cypress-io/cypress/pull/4092
|
200
|
+
expectedSize = response.headers['x-amz-meta-size'] ||
|
201
|
+
response.headers['content-length'];
|
202
|
+
expectedChecksum = response.headers['x-amz-meta-checksum'];
|
203
|
+
if (expectedChecksum) {
|
204
|
+
debug('expected checksum %s', expectedChecksum);
|
205
|
+
}
|
206
|
+
if (expectedSize) {
|
207
|
+
// convert from string (all Amazon custom headers are strings)
|
208
|
+
expectedSize = Number(expectedSize);
|
209
|
+
debug('expected file size %d', expectedSize);
|
210
|
+
}
|
211
|
+
// start counting now once we've gotten
|
212
|
+
// response headers
|
213
|
+
started = new Date();
|
214
|
+
if (/^3/.test(response.statusCode)) {
|
215
|
+
const redirectVersion = response.headers['x-version'];
|
216
|
+
const redirectUrl = response.headers.location;
|
217
|
+
debug('redirect version:', redirectVersion);
|
218
|
+
debug('redirect url:', redirectUrl);
|
219
|
+
downloadFromUrl({ url: redirectUrl, progress, ca, downloadDestination, version: redirectVersion, redirectTTL: redirectTTL - 1 })
|
220
|
+
.then(resolve).catch(reject);
|
221
|
+
// if our status code does not start with 200
|
222
|
+
}
|
223
|
+
else if (!/^2/.test(response.statusCode)) {
|
224
|
+
debug('response code %d', response.statusCode);
|
225
|
+
const err = new Error((0, common_tags_1.stripIndent) `
|
226
|
+
Failed downloading the Cypress binary.
|
227
|
+
Response code: ${response.statusCode}
|
228
|
+
Response message: ${response.statusMessage}
|
229
|
+
`);
|
230
|
+
reject(err);
|
231
|
+
// status codes here are all 2xx
|
232
|
+
}
|
233
|
+
else {
|
234
|
+
// We only enable this pipe connection when we know we've got a successful return
|
235
|
+
// and handle the completion with verify and resolve
|
236
|
+
// there was a possible race condition between end of request and close of writeStream
|
237
|
+
// that is made ordered with this Promise.all
|
238
|
+
bluebird_1.default.all([new bluebird_1.default((r) => {
|
239
|
+
return response.pipe(fs_extra_1.default.createWriteStream(downloadDestination).on('close', r));
|
240
|
+
}), new bluebird_1.default((r) => response.on('end', r))])
|
241
|
+
.then(() => {
|
242
|
+
debug('downloading finished');
|
243
|
+
verifyDownloadedFile(downloadDestination, expectedSize, expectedChecksum)
|
244
|
+
.then(() => debug('verified'))
|
245
|
+
.then(() => resolve(version))
|
246
|
+
.catch(reject);
|
247
|
+
});
|
248
|
+
}
|
249
|
+
})
|
250
|
+
.on('error', (e) => {
|
251
|
+
if (e.code === 'ECONNRESET')
|
252
|
+
return; // sometimes proxies give ECONNRESET but we don't care
|
253
|
+
reject(e);
|
254
|
+
})
|
255
|
+
.on('progress', (state) => {
|
256
|
+
// total time we've elapsed
|
257
|
+
// starting on our first progress notification
|
258
|
+
const elapsed = +new Date() - +started;
|
259
|
+
// request-progress sends a value between 0 and 1
|
260
|
+
const percentage = util_1.default.convertPercentToPercentage(state.percent);
|
261
|
+
const eta = util_1.default.calculateEta(percentage, elapsed);
|
262
|
+
// send up our percent and seconds remaining
|
263
|
+
progress.onProgress(percentage, util_1.default.secsRemaining(eta));
|
264
|
+
});
|
265
|
+
});
|
266
|
+
};
|
267
|
+
/**
|
268
|
+
* Download Cypress.zip from external versionUrl to local file.
|
269
|
+
* @param [string] version Could be "3.3.0" or full URL
|
270
|
+
* @param [string] downloadDestination Local filename to save as
|
271
|
+
*/
|
272
|
+
const start = (opts) => __awaiter(void 0, void 0, void 0, function* () {
|
273
|
+
let { version, downloadDestination, progress, redirectTTL } = opts;
|
274
|
+
if (!downloadDestination) {
|
275
|
+
assert_1.default.ok(lodash_1.default.isString(downloadDestination) && !lodash_1.default.isEmpty(downloadDestination), 'missing download dir');
|
276
|
+
}
|
277
|
+
if (!progress) {
|
278
|
+
progress = { onProgress: () => {
|
279
|
+
return {};
|
280
|
+
} };
|
281
|
+
}
|
282
|
+
const arch = yield util_1.default.getRealArch();
|
283
|
+
const versionUrl = getUrl(arch, version);
|
284
|
+
progress.throttle = 100;
|
285
|
+
debug('needed Cypress version: %s', version);
|
286
|
+
debug('source url %s', versionUrl);
|
287
|
+
debug(`downloading cypress.zip to "${downloadDestination}"`);
|
288
|
+
try {
|
289
|
+
// ensure download dir exists
|
290
|
+
yield fs_extra_1.default.ensureDir(path_1.default.dirname(downloadDestination));
|
291
|
+
const ca = yield getCA();
|
292
|
+
return downloadFromUrl(Object.assign({ url: versionUrl, downloadDestination, progress, ca, version }, (redirectTTL ? { redirectTTL } : {})));
|
293
|
+
}
|
294
|
+
catch (err) {
|
295
|
+
return prettyDownloadErr(err, versionUrl);
|
296
|
+
}
|
297
|
+
});
|
298
|
+
const downloadModule = {
|
299
|
+
start,
|
300
|
+
getUrl,
|
301
|
+
getProxyForUrlWithNpmConfig,
|
302
|
+
getCA,
|
303
|
+
};
|
304
|
+
exports.default = downloadModule;
|
@@ -0,0 +1,44 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
9
|
+
});
|
10
|
+
};
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
13
|
+
};
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
15
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
16
|
+
const path_1 = require("path");
|
17
|
+
const bluebird_1 = __importDefault(require("bluebird"));
|
18
|
+
/**
|
19
|
+
* Get the size of a folder or a file.
|
20
|
+
*
|
21
|
+
* This function returns the actual file size of the folder (size), not the allocated space on disk (size on disk).
|
22
|
+
* For more details between the difference, check this link:
|
23
|
+
* https://www.howtogeek.com/180369/why-is-there-a-big-difference-between-size-and-size-on-disk/
|
24
|
+
*
|
25
|
+
* @param {string} path path to the file or the folder.
|
26
|
+
*/
|
27
|
+
function getSize(path) {
|
28
|
+
return __awaiter(this, void 0, void 0, function* () {
|
29
|
+
const stat = yield fs_extra_1.default.lstat(path);
|
30
|
+
if (stat.isDirectory()) {
|
31
|
+
const list = yield fs_extra_1.default.readdir(path);
|
32
|
+
return bluebird_1.default.resolve(list).reduce((prev, curr) => __awaiter(this, void 0, void 0, function* () {
|
33
|
+
const currPath = (0, path_1.join)(path, curr);
|
34
|
+
const s = yield fs_extra_1.default.lstat(currPath);
|
35
|
+
if (s.isDirectory()) {
|
36
|
+
return prev + (yield getSize(currPath));
|
37
|
+
}
|
38
|
+
return prev + s.size;
|
39
|
+
}), 0);
|
40
|
+
}
|
41
|
+
return stat.size;
|
42
|
+
});
|
43
|
+
}
|
44
|
+
exports.default = getSize;
|
@@ -0,0 +1,326 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
9
|
+
});
|
10
|
+
};
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
13
|
+
};
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
15
|
+
const lodash_1 = __importDefault(require("lodash"));
|
16
|
+
const os_1 = __importDefault(require("os"));
|
17
|
+
const path_1 = __importDefault(require("path"));
|
18
|
+
const chalk_1 = __importDefault(require("chalk"));
|
19
|
+
const debug_1 = __importDefault(require("debug"));
|
20
|
+
const listr2_1 = require("listr2");
|
21
|
+
const log_symbols_1 = __importDefault(require("log-symbols"));
|
22
|
+
const common_tags_1 = require("common-tags");
|
23
|
+
const promises_1 = __importDefault(require("timers/promises"));
|
24
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
25
|
+
const download_1 = __importDefault(require("./download"));
|
26
|
+
const util_1 = __importDefault(require("../util"));
|
27
|
+
const state_1 = __importDefault(require("./state"));
|
28
|
+
const unzip_1 = __importDefault(require("./unzip"));
|
29
|
+
const logger_1 = __importDefault(require("../logger"));
|
30
|
+
const errors_1 = require("../errors");
|
31
|
+
const VerboseRenderer_1 = __importDefault(require("../VerboseRenderer"));
|
32
|
+
const debug = (0, debug_1.default)('cypress:cli');
|
33
|
+
// Import package.json dynamically to avoid TypeScript JSON import issues
|
34
|
+
const { buildInfo, version } = require('../../package.json');
|
35
|
+
function _getBinaryUrlFromBuildInfo(arch, { commitSha, commitBranch }) {
|
36
|
+
const platform = os_1.default.platform();
|
37
|
+
if ((platform === 'win32') && (arch === 'arm64')) {
|
38
|
+
debug(`detected platform ${platform} architecture ${arch} combination`);
|
39
|
+
arch = 'x64';
|
40
|
+
debug(`overriding to download ${platform}-${arch} pre-release binary instead`);
|
41
|
+
}
|
42
|
+
return `https://cdn.cypress.io/beta/binary/${version}/${platform}-${arch}/${commitBranch}-${commitSha}/cypress.zip`;
|
43
|
+
}
|
44
|
+
const alreadyInstalledMsg = () => {
|
45
|
+
if (!util_1.default.isPostInstall()) {
|
46
|
+
logger_1.default.log((0, common_tags_1.stripIndent) `
|
47
|
+
Skipping installation:
|
48
|
+
|
49
|
+
Pass the ${chalk_1.default.yellow('--force')} option if you'd like to reinstall anyway.
|
50
|
+
`);
|
51
|
+
}
|
52
|
+
};
|
53
|
+
const displayCompletionMsg = () => {
|
54
|
+
// check here to see if we are globally installed
|
55
|
+
if (util_1.default.isInstalledGlobally()) {
|
56
|
+
// if we are display a warning
|
57
|
+
logger_1.default.log();
|
58
|
+
logger_1.default.warn((0, common_tags_1.stripIndent) `
|
59
|
+
${log_symbols_1.default.warning} Warning: It looks like you\'ve installed Cypress globally.
|
60
|
+
|
61
|
+
The recommended way to install Cypress is as a devDependency per project.
|
62
|
+
|
63
|
+
You should probably run these commands:
|
64
|
+
|
65
|
+
- ${chalk_1.default.cyan('npm uninstall -g cypress')}
|
66
|
+
- ${chalk_1.default.cyan('npm install --save-dev cypress')}
|
67
|
+
`);
|
68
|
+
return;
|
69
|
+
}
|
70
|
+
logger_1.default.log();
|
71
|
+
logger_1.default.log('You can now open Cypress by running one of the following, depending on your package manager:');
|
72
|
+
logger_1.default.log();
|
73
|
+
logger_1.default.log(chalk_1.default.cyan('- npx cypress open'));
|
74
|
+
logger_1.default.log(chalk_1.default.cyan('- yarn cypress open'));
|
75
|
+
logger_1.default.log(chalk_1.default.cyan('- pnpm cypress open'));
|
76
|
+
logger_1.default.log();
|
77
|
+
logger_1.default.log(chalk_1.default.grey('https://on.cypress.io/opening-the-app'));
|
78
|
+
logger_1.default.log();
|
79
|
+
};
|
80
|
+
const downloadAndUnzip = ({ version, installDir, downloadDir }) => {
|
81
|
+
const progress = {
|
82
|
+
throttle: 100,
|
83
|
+
onProgress: null,
|
84
|
+
};
|
85
|
+
const downloadDestination = path_1.default.join(downloadDir, `cypress-${process.pid}.zip`);
|
86
|
+
const rendererOptions = getRendererOptions();
|
87
|
+
// let the user know what version of cypress we're downloading!
|
88
|
+
logger_1.default.log(`Installing Cypress ${chalk_1.default.gray(`(version: ${version})`)}`);
|
89
|
+
logger_1.default.log();
|
90
|
+
const tasks = new listr2_1.Listr([
|
91
|
+
{
|
92
|
+
options: { title: util_1.default.titleize('Downloading Cypress') },
|
93
|
+
task: (ctx, task) => __awaiter(void 0, void 0, void 0, function* () {
|
94
|
+
// as our download progresses indicate the status
|
95
|
+
progress.onProgress = progessify(task, 'Downloading Cypress');
|
96
|
+
const redirectVersion = yield download_1.default.start({ version, downloadDestination, progress });
|
97
|
+
if (redirectVersion)
|
98
|
+
version = redirectVersion;
|
99
|
+
debug(`finished downloading file: ${downloadDestination}`);
|
100
|
+
// save the download destination for unzipping
|
101
|
+
util_1.default.setTaskTitle(task, util_1.default.titleize(chalk_1.default.green('Downloaded Cypress')), rendererOptions.renderer);
|
102
|
+
}),
|
103
|
+
},
|
104
|
+
unzipTask({
|
105
|
+
progress,
|
106
|
+
zipFilePath: downloadDestination,
|
107
|
+
installDir,
|
108
|
+
rendererOptions,
|
109
|
+
}),
|
110
|
+
{
|
111
|
+
options: { title: util_1.default.titleize('Finishing Installation') },
|
112
|
+
task: (ctx, task) => __awaiter(void 0, void 0, void 0, function* () {
|
113
|
+
const cleanup = () => __awaiter(void 0, void 0, void 0, function* () {
|
114
|
+
debug('removing zip file %s', downloadDestination);
|
115
|
+
yield fs_extra_1.default.remove(downloadDestination);
|
116
|
+
});
|
117
|
+
yield cleanup();
|
118
|
+
debug('finished installation in', installDir);
|
119
|
+
util_1.default.setTaskTitle(task, util_1.default.titleize(chalk_1.default.green('Finished Installation'), chalk_1.default.gray(installDir)), rendererOptions.renderer);
|
120
|
+
}),
|
121
|
+
},
|
122
|
+
], { rendererOptions });
|
123
|
+
// start the tasks!
|
124
|
+
return tasks.run();
|
125
|
+
};
|
126
|
+
const validateOS = () => __awaiter(void 0, void 0, void 0, function* () {
|
127
|
+
const platformInfo = yield util_1.default.getPlatformInfo();
|
128
|
+
return platformInfo.match(/(win32-x64|win32-arm64|linux-x64|linux-arm64|darwin-x64|darwin-arm64)/);
|
129
|
+
});
|
130
|
+
/**
|
131
|
+
* Returns the version to install - either a string like `1.2.3` to be fetched
|
132
|
+
* from the download server or a file path or HTTP URL.
|
133
|
+
*/
|
134
|
+
function getVersionOverride({ arch, envVarVersion, buildInfo }) {
|
135
|
+
// let this environment variable reset the binary version we need
|
136
|
+
if (envVarVersion) {
|
137
|
+
return envVarVersion;
|
138
|
+
}
|
139
|
+
if (buildInfo && !buildInfo.stable) {
|
140
|
+
logger_1.default.log(chalk_1.default.yellow((0, common_tags_1.stripIndent) `
|
141
|
+
${log_symbols_1.default.warning} Warning: You are installing a pre-release build of Cypress.
|
142
|
+
|
143
|
+
Bugs may be present which do not exist in production builds.
|
144
|
+
|
145
|
+
This build was created from:
|
146
|
+
* Commit SHA: ${buildInfo.commitSha}
|
147
|
+
* Commit Branch: ${buildInfo.commitBranch}
|
148
|
+
* Commit Timestamp: ${buildInfo.commitDate}
|
149
|
+
`));
|
150
|
+
logger_1.default.log();
|
151
|
+
return _getBinaryUrlFromBuildInfo(arch, buildInfo);
|
152
|
+
}
|
153
|
+
}
|
154
|
+
function getEnvVarVersion() {
|
155
|
+
if (!util_1.default.getEnv('CYPRESS_INSTALL_BINARY'))
|
156
|
+
return;
|
157
|
+
// because passed file paths are often double quoted
|
158
|
+
// and might have extra whitespace around, be robust and trim the string
|
159
|
+
const trimAndRemoveDoubleQuotes = true;
|
160
|
+
const envVarVersion = util_1.default.getEnv('CYPRESS_INSTALL_BINARY', trimAndRemoveDoubleQuotes);
|
161
|
+
debug('using environment variable CYPRESS_INSTALL_BINARY "%s"', envVarVersion);
|
162
|
+
return envVarVersion;
|
163
|
+
}
|
164
|
+
const start = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (options = {}) {
|
165
|
+
debug('installing with options %j', options);
|
166
|
+
const envVarVersion = getEnvVarVersion();
|
167
|
+
if (envVarVersion === '0') {
|
168
|
+
debug('environment variable CYPRESS_INSTALL_BINARY = 0, skipping install');
|
169
|
+
logger_1.default.log((0, common_tags_1.stripIndent) `
|
170
|
+
${chalk_1.default.yellow('Note:')} Skipping binary installation: Environment variable CYPRESS_INSTALL_BINARY = 0.`);
|
171
|
+
logger_1.default.log();
|
172
|
+
return;
|
173
|
+
}
|
174
|
+
lodash_1.default.defaults(options, {
|
175
|
+
force: false,
|
176
|
+
buildInfo,
|
177
|
+
});
|
178
|
+
if (util_1.default.getEnv('CYPRESS_CACHE_FOLDER')) {
|
179
|
+
const envCache = util_1.default.getEnv('CYPRESS_CACHE_FOLDER');
|
180
|
+
logger_1.default.log((0, common_tags_1.stripIndent) `
|
181
|
+
${chalk_1.default.yellow('Note:')} Overriding Cypress cache directory to: ${chalk_1.default.cyan(envCache)}
|
182
|
+
|
183
|
+
Previous installs of Cypress may not be found.
|
184
|
+
`);
|
185
|
+
logger_1.default.log();
|
186
|
+
}
|
187
|
+
const pkgVersion = util_1.default.pkgVersion();
|
188
|
+
const arch = yield util_1.default.getRealArch();
|
189
|
+
const versionOverride = getVersionOverride({ arch, envVarVersion, buildInfo: options.buildInfo });
|
190
|
+
const versionToInstall = versionOverride || pkgVersion;
|
191
|
+
debug('version in package.json is %s, version to install is %s', pkgVersion, versionToInstall);
|
192
|
+
const installDir = state_1.default.getVersionDir(pkgVersion, options.buildInfo);
|
193
|
+
const cacheDir = state_1.default.getCacheDir();
|
194
|
+
const binaryDir = state_1.default.getBinaryDir(pkgVersion);
|
195
|
+
if (!(yield validateOS())) {
|
196
|
+
return (0, errors_1.throwFormErrorText)(errors_1.errors.invalidOS)();
|
197
|
+
}
|
198
|
+
try {
|
199
|
+
yield fs_extra_1.default.ensureDir(cacheDir);
|
200
|
+
}
|
201
|
+
catch (err) {
|
202
|
+
if (err.code === 'EACCES') {
|
203
|
+
return (0, errors_1.throwFormErrorText)(errors_1.errors.invalidCacheDirectory)((0, common_tags_1.stripIndent) `
|
204
|
+
Failed to access ${chalk_1.default.cyan(cacheDir)}:
|
205
|
+
|
206
|
+
${err.message}
|
207
|
+
`);
|
208
|
+
}
|
209
|
+
throw err;
|
210
|
+
}
|
211
|
+
const binaryPkg = yield state_1.default.getBinaryPkgAsync(binaryDir);
|
212
|
+
const binaryVersion = yield state_1.default.getBinaryPkgVersion(binaryPkg);
|
213
|
+
const shouldInstall = () => {
|
214
|
+
if (!binaryVersion) {
|
215
|
+
debug('no binary installed under cli version');
|
216
|
+
return true;
|
217
|
+
}
|
218
|
+
logger_1.default.log();
|
219
|
+
logger_1.default.log((0, common_tags_1.stripIndent) `
|
220
|
+
Cypress ${chalk_1.default.green(binaryVersion)} is installed in ${chalk_1.default.cyan(installDir)}
|
221
|
+
`);
|
222
|
+
logger_1.default.log();
|
223
|
+
if (options.force) {
|
224
|
+
debug('performing force install over existing binary');
|
225
|
+
return true;
|
226
|
+
}
|
227
|
+
if ((binaryVersion === versionToInstall) || !util_1.default.isSemver(versionToInstall)) {
|
228
|
+
// our version matches, tell the user this is a noop
|
229
|
+
alreadyInstalledMsg();
|
230
|
+
return false;
|
231
|
+
}
|
232
|
+
return true;
|
233
|
+
};
|
234
|
+
// noop if we've been told not to download
|
235
|
+
if (!shouldInstall()) {
|
236
|
+
return debug('Not downloading or installing binary');
|
237
|
+
}
|
238
|
+
if (envVarVersion) {
|
239
|
+
logger_1.default.log(chalk_1.default.yellow((0, common_tags_1.stripIndent) `
|
240
|
+
${log_symbols_1.default.warning} Warning: Forcing a binary version different than the default.
|
241
|
+
|
242
|
+
The CLI expected to install version: ${chalk_1.default.green(pkgVersion)}
|
243
|
+
|
244
|
+
Instead we will install version: ${chalk_1.default.green(versionToInstall)}
|
245
|
+
|
246
|
+
These versions may not work properly together.
|
247
|
+
`));
|
248
|
+
logger_1.default.log();
|
249
|
+
}
|
250
|
+
const getLocalFilePath = () => __awaiter(void 0, void 0, void 0, function* () {
|
251
|
+
// see if version supplied is a path to a binary
|
252
|
+
if (yield fs_extra_1.default.pathExists(versionToInstall)) {
|
253
|
+
return path_1.default.extname(versionToInstall) === '.zip' ? versionToInstall : false;
|
254
|
+
}
|
255
|
+
const possibleFile = util_1.default.formAbsolutePath(versionToInstall);
|
256
|
+
debug('checking local file', possibleFile, 'cwd', process.cwd());
|
257
|
+
// if this exists return the path to it
|
258
|
+
// else false
|
259
|
+
if ((yield fs_extra_1.default.pathExists(possibleFile)) && path_1.default.extname(possibleFile) === '.zip') {
|
260
|
+
return possibleFile;
|
261
|
+
}
|
262
|
+
return false;
|
263
|
+
});
|
264
|
+
const pathToLocalFile = yield getLocalFilePath();
|
265
|
+
if (pathToLocalFile) {
|
266
|
+
const absolutePath = path_1.default.resolve(versionToInstall);
|
267
|
+
debug('found local file at', absolutePath);
|
268
|
+
debug('skipping download');
|
269
|
+
const rendererOptions = getRendererOptions();
|
270
|
+
return new listr2_1.Listr([unzipTask({
|
271
|
+
progress: {
|
272
|
+
throttle: 100,
|
273
|
+
onProgress: null,
|
274
|
+
},
|
275
|
+
zipFilePath: absolutePath,
|
276
|
+
installDir,
|
277
|
+
rendererOptions,
|
278
|
+
})], { rendererOptions }).run();
|
279
|
+
}
|
280
|
+
if (options.force) {
|
281
|
+
debug('Cypress already installed at', installDir);
|
282
|
+
debug('but the installation was forced');
|
283
|
+
}
|
284
|
+
debug('preparing to download and unzip version ', versionToInstall, 'to path', installDir);
|
285
|
+
const downloadDir = os_1.default.tmpdir();
|
286
|
+
yield downloadAndUnzip({ version: versionToInstall, installDir, downloadDir });
|
287
|
+
// delay 1 sec for UX, unless we are testing
|
288
|
+
yield promises_1.default.setTimeout(1000);
|
289
|
+
displayCompletionMsg();
|
290
|
+
});
|
291
|
+
const unzipTask = ({ zipFilePath, installDir, progress, rendererOptions }) => {
|
292
|
+
return {
|
293
|
+
options: { title: util_1.default.titleize('Unzipping Cypress') },
|
294
|
+
task: (ctx, task) => __awaiter(void 0, void 0, void 0, function* () {
|
295
|
+
// as our unzip progresses indicate the status
|
296
|
+
progress.onProgress = progessify(task, 'Unzipping Cypress');
|
297
|
+
yield unzip_1.default.start({ zipFilePath, installDir, progress });
|
298
|
+
util_1.default.setTaskTitle(task, util_1.default.titleize(chalk_1.default.green('Unzipped Cypress')), rendererOptions.renderer);
|
299
|
+
}),
|
300
|
+
};
|
301
|
+
};
|
302
|
+
const progessify = (task, title) => {
|
303
|
+
// return higher order function
|
304
|
+
return (percentComplete, remaining) => {
|
305
|
+
const percentCompleteStr = chalk_1.default.white(` ${percentComplete}%`);
|
306
|
+
// pluralize seconds remaining
|
307
|
+
const remainingStr = chalk_1.default.gray(`${remaining}s`);
|
308
|
+
util_1.default.setTaskTitle(task, util_1.default.titleize(title, percentCompleteStr, remainingStr), getRendererOptions().renderer);
|
309
|
+
};
|
310
|
+
};
|
311
|
+
// if we are running in CI then use
|
312
|
+
// the verbose renderer else use
|
313
|
+
// the default
|
314
|
+
const getRendererOptions = () => {
|
315
|
+
let renderer = util_1.default.isCi() ? VerboseRenderer_1.default : 'default';
|
316
|
+
if (logger_1.default.logLevel() === 'silent') {
|
317
|
+
renderer = 'silent';
|
318
|
+
}
|
319
|
+
return {
|
320
|
+
renderer,
|
321
|
+
};
|
322
|
+
};
|
323
|
+
exports.default = {
|
324
|
+
start,
|
325
|
+
_getBinaryUrlFromBuildInfo,
|
326
|
+
};
|