cypress 15.21.0 → 16.0.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.
Files changed (56) hide show
  1. package/angular/README.md +4 -1
  2. package/angular/angular/README.md +4 -1
  3. package/angular/angular/dist/index.d.ts +3 -19
  4. package/angular/angular/dist/index.js +70 -84
  5. package/angular/angular/package.json +11 -12
  6. package/angular/dist/index.d.ts +3 -19
  7. package/angular/dist/index.js +70 -84
  8. package/angular/package.json +11 -12
  9. package/dist/bin/cypress +2 -2
  10. package/dist/{cli-_xePNEPj.js → cli-Dl3GL9Ov.js} +1897 -1930
  11. package/dist/cli.js +2 -2
  12. package/dist/{cypress-DCSAOi2D.js → cypress-BfVf5_hP.js} +1 -1
  13. package/dist/cypress.js +3 -3
  14. package/dist/exec/spawn.js +1 -1
  15. package/dist/index.js +3 -3
  16. package/dist/{spawn-BkpC5qQ4.js → spawn-DpvsyDKg.js} +12 -12
  17. package/dist/{tap-BkTD6Abc.js → tap-tSM1ve9x.js} +41 -19
  18. package/mount-utils/dist/index.js +1 -1
  19. package/mount-utils/mount-utils/dist/index.js +1 -1
  20. package/mount-utils/mount-utils/package.json +5 -5
  21. package/mount-utils/package.json +5 -5
  22. package/package.json +5 -11
  23. package/react/dist/cypress-react.cjs.js +1 -1
  24. package/react/dist/cypress-react.esm-bundler.js +1 -1
  25. package/react/dist/index.d.ts +2 -1
  26. package/react/package.json +2 -2
  27. package/react/react/dist/cypress-react.cjs.js +1 -1
  28. package/react/react/dist/cypress-react.esm-bundler.js +1 -1
  29. package/react/react/dist/index.d.ts +2 -1
  30. package/react/react/package.json +2 -2
  31. package/svelte/dist/cypress-svelte.cjs.js +1 -1
  32. package/svelte/dist/cypress-svelte.esm-bundler.js +1 -1
  33. package/svelte/dist/index.d.ts +2 -1
  34. package/svelte/package.json +1 -1
  35. package/svelte/svelte/dist/cypress-svelte.cjs.js +1 -1
  36. package/svelte/svelte/dist/cypress-svelte.esm-bundler.js +1 -1
  37. package/svelte/svelte/dist/index.d.ts +2 -1
  38. package/svelte/svelte/package.json +1 -1
  39. package/types/cy-blob-util.d.ts +1 -1
  40. package/types/cypress.d.ts +32 -134
  41. package/vue/dist/cypress-vue.cjs.js +62 -66
  42. package/vue/dist/cypress-vue.esm-bundler.js +50 -54
  43. package/vue/dist/index.d.ts +120 -82
  44. package/vue/package.json +4 -4
  45. package/vue/vue/dist/cypress-vue.cjs.js +62 -66
  46. package/vue/vue/dist/cypress-vue.esm-bundler.js +50 -54
  47. package/vue/vue/dist/index.d.ts +120 -82
  48. package/vue/vue/package.json +4 -4
  49. package/angular-zoneless/README.md +0 -15
  50. package/angular-zoneless/angular-zoneless/README.md +0 -15
  51. package/angular-zoneless/angular-zoneless/dist/index.d.ts +0 -119
  52. package/angular-zoneless/angular-zoneless/dist/index.js +0 -482
  53. package/angular-zoneless/angular-zoneless/package.json +0 -74
  54. package/angular-zoneless/dist/index.d.ts +0 -119
  55. package/angular-zoneless/dist/index.js +0 -482
  56. package/angular-zoneless/package.json +0 -74
@@ -13,7 +13,7 @@ var dayjs = require('dayjs');
13
13
  var relativeTime = require('dayjs/plugin/relativeTime');
14
14
  var chalk = require('chalk');
15
15
  var Bluebird = require('bluebird');
16
- var spawn = require('./spawn-BkpC5qQ4.js');
16
+ var spawn = require('./spawn-DpvsyDKg.js');
17
17
  var os = require('os');
18
18
  var listr2 = require('listr2');
19
19
  var timers = require('timers/promises');
@@ -30,1211 +30,54 @@ var require$$0 = require('stream');
30
30
  var readline = require('readline');
31
31
  var prettyBytes = require('pretty-bytes');
32
32
 
33
- const debug$8 = Debug('cypress:cli');
34
- const defaultBaseUrl = 'https://download.cypress.io/';
35
- const defaultMaxRedirects = 10;
36
- const getProxyForUrlWithNpmConfig = (url) => {
37
- return proxyFromEnv.getProxyForUrl(url) ||
38
- process.env.npm_config_https_proxy ||
39
- process.env.npm_config_proxy ||
40
- null;
41
- };
42
- const getBaseUrl = () => {
43
- if (xvfb.util.getEnv('CYPRESS_DOWNLOAD_MIRROR')) {
44
- let baseUrl = xvfb.util.getEnv('CYPRESS_DOWNLOAD_MIRROR');
45
- if (!(baseUrl === null || baseUrl === void 0 ? void 0 : baseUrl.endsWith('/'))) {
46
- baseUrl += '/';
47
- }
48
- return baseUrl || defaultBaseUrl;
49
- }
50
- return defaultBaseUrl;
51
- };
52
- const getCA = () => xvfb.__awaiter(void 0, void 0, void 0, function* () {
53
- if (process.env.npm_config_cafile) {
54
- try {
55
- const caFileContent = yield fs.readFile(process.env.npm_config_cafile, 'utf8');
56
- return caFileContent;
57
- }
58
- catch (error) {
59
- debug$8('error reading ca file', error);
60
- return;
61
- }
62
- }
63
- if (process.env.npm_config_ca) {
64
- return process.env.npm_config_ca;
65
- }
66
- return;
67
- });
68
- const prepend = (arch, urlPath, version) => {
69
- const endpoint = new URL(urlPath, getBaseUrl()).toString();
70
- const platform = os.platform();
71
- const pathTemplate = xvfb.util.getEnv('CYPRESS_DOWNLOAD_PATH_TEMPLATE', true);
72
- if ((platform === 'win32') && (arch === 'arm64')) {
73
- debug$8(`detected platform ${platform} architecture ${arch} combination`);
74
- arch = 'x64';
75
- debug$8(`overriding to download ${platform}-${arch} instead`);
76
- }
77
- return pathTemplate
78
- ? (pathTemplate
79
- .replace(/\\?\$\{endpoint\}/g, endpoint)
80
- .replace(/\\?\$\{platform\}/g, platform)
81
- .replace(/\\?\$\{arch\}/g, arch)
82
- .replace(/\\?\$\{version\}/g, version))
83
- : `${endpoint}?platform=${platform}&arch=${arch}`;
84
- };
85
- const getUrl = (arch, version) => {
86
- if (_.isString(version) && version.match(/^https?:\/\/.*$/)) {
87
- debug$8('version is already an url', version);
88
- return version;
89
- }
90
- const urlPath = version ? `desktop/${version}` : 'desktop';
91
- return prepend(arch, urlPath, version || '');
92
- };
93
- const statusMessage = (err) => {
94
- return (err.statusCode
95
- ? [err.statusCode, err.statusMessage].join(' - ')
96
- : err.toString());
97
- };
98
- const prettyDownloadErr = (err, url) => {
99
- const msg = commonTags.stripIndent `
100
- URL: ${url}
101
- ${statusMessage(err)}
102
- `;
103
- debug$8(msg);
104
- return xvfb.throwFormErrorText(xvfb.errors.failedDownload)(msg);
105
- };
106
33
  /**
107
- * Checks checksum and file size for the given file. Allows both
108
- * values or just one of them to be checked.
34
+ * Get the size of a folder or a file.
35
+ *
36
+ * This function returns the actual file size of the folder (size), not the allocated space on disk (size on disk).
37
+ * For more details between the difference, check this link:
38
+ * https://www.howtogeek.com/180369/why-is-there-a-big-difference-between-size-and-size-on-disk/
39
+ *
40
+ * @param {string} path path to the file or the folder.
109
41
  */
110
- const verifyDownloadedFile = (filename, expectedSize, expectedChecksum) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
111
- if (expectedSize && expectedChecksum) {
112
- debug$8('verifying checksum and file size');
113
- return Bluebird.join(xvfb.util.getFileChecksum(filename), xvfb.util.getFileSize(filename), (checksum, filesize) => {
114
- if (checksum === expectedChecksum && filesize === expectedSize) {
115
- debug$8('downloaded file has the expected checksum and size ✅');
116
- return;
117
- }
118
- debug$8('raising error: checksum or file size mismatch');
119
- const text = commonTags.stripIndent `
120
- Corrupted download
42
+ function getSize(path$1) {
43
+ return xvfb.__awaiter(this, void 0, void 0, function* () {
44
+ const stat = yield fs.lstat(path$1);
45
+ if (stat.isDirectory()) {
46
+ const list = yield fs.readdir(path$1);
47
+ return Bluebird.resolve(list).reduce((prev, curr) => xvfb.__awaiter(this, void 0, void 0, function* () {
48
+ const currPath = path.join(path$1, curr);
49
+ const s = yield fs.lstat(currPath);
50
+ if (s.isDirectory()) {
51
+ return prev + (yield getSize(currPath));
52
+ }
53
+ return prev + s.size;
54
+ }), 0);
55
+ }
56
+ return stat.size;
57
+ });
58
+ }
121
59
 
122
- Expected downloaded file to have checksum: ${expectedChecksum}
123
- Computed checksum: ${checksum}
60
+ var dist = {};
124
61
 
125
- Expected downloaded file to have size: ${expectedSize}
126
- Computed size: ${filesize}
127
- `;
128
- debug$8(text);
129
- throw new Error(text);
130
- });
131
- }
132
- if (expectedChecksum) {
133
- debug$8('only checking expected file checksum %d', expectedChecksum);
134
- const checksum = yield xvfb.util.getFileChecksum(filename);
135
- if (checksum === expectedChecksum) {
136
- debug$8('downloaded file has the expected checksum ✅');
137
- return;
138
- }
139
- debug$8('raising error: file checksum mismatch');
140
- const text = commonTags.stripIndent `
141
- Corrupted download
62
+ var tapContract = {};
142
63
 
143
- Expected downloaded file to have checksum: ${expectedChecksum}
144
- Computed checksum: ${checksum}
145
- `;
146
- throw new Error(text);
147
- }
148
- if (expectedSize) {
149
- // maybe we don't have a checksum, but at least CDN returns content length
150
- // which we can check against the file size
151
- debug$8('only checking expected file size %d', expectedSize);
152
- const filesize = yield xvfb.util.getFileSize(filename);
153
- if (filesize === expectedSize) {
154
- debug$8('downloaded file has the expected size ✅');
155
- return;
156
- }
157
- debug$8('raising error: file size mismatch');
158
- const text = commonTags.stripIndent `
159
- Corrupted download
160
-
161
- Expected downloaded file to have size: ${expectedSize}
162
- Computed size: ${filesize}
163
- `;
164
- throw new Error(text);
165
- }
166
- debug$8('downloaded file lacks checksum or size to verify');
167
- return;
168
- });
169
- // downloads from given url
170
- // return an object with
171
- // {filename: ..., downloaded: true}
172
- const downloadFromUrl = ({ url, downloadDestination, progress, ca, version, redirectTTL = defaultMaxRedirects }) => {
173
- if (redirectTTL <= 0) {
174
- return Promise.reject(new Error(commonTags.stripIndent `
175
- Failed downloading the Cypress binary.
176
- There were too many redirects. The default allowance is ${defaultMaxRedirects}.
177
- Maybe you got stuck in a redirect loop?
178
- `));
179
- }
180
- return new Bluebird((resolve, reject) => {
181
- const proxy = getProxyForUrlWithNpmConfig(url);
182
- debug$8('Downloading package', {
183
- url,
184
- proxy,
185
- downloadDestination,
186
- });
187
- if (ca) {
188
- debug$8('using custom CA details from npm config');
189
- }
190
- const reqOptions = Object.assign(Object.assign(Object.assign({ uri: url }, (proxy ? { proxy } : {})), (ca ? { agentOptions: { ca } } : {})), { method: 'GET', followRedirect: false });
191
- const req = request(reqOptions);
192
- // closure
193
- let started = null;
194
- let expectedSize;
195
- let expectedChecksum;
196
- requestProgress(req, {
197
- throttle: progress.throttle,
198
- })
199
- .on('response', (response) => {
200
- // we have computed checksum and filesize during test runner binary build
201
- // and have set it on the S3 object as user meta data, available via
202
- // these custom headers "x-amz-meta-..."
203
- // see https://github.com/cypress-io/cypress/pull/4092
204
- expectedSize = response.headers['x-amz-meta-size'] ||
205
- response.headers['content-length'];
206
- expectedChecksum = response.headers['x-amz-meta-checksum'];
207
- if (expectedChecksum) {
208
- debug$8('expected checksum %s', expectedChecksum);
209
- }
210
- if (expectedSize) {
211
- // convert from string (all Amazon custom headers are strings)
212
- expectedSize = Number(expectedSize);
213
- debug$8('expected file size %d', expectedSize);
214
- }
215
- // start counting now once we've gotten
216
- // response headers
217
- started = new Date();
218
- if (/^3/.test(response.statusCode)) {
219
- const redirectVersion = response.headers['x-version'];
220
- const redirectUrl = response.headers.location;
221
- debug$8('redirect version:', redirectVersion);
222
- debug$8('redirect url:', redirectUrl);
223
- downloadFromUrl({ url: redirectUrl, progress, ca, downloadDestination, version: redirectVersion, redirectTTL: redirectTTL - 1 })
224
- .then(resolve).catch(reject);
225
- // if our status code does not start with 200
226
- }
227
- else if (!/^2/.test(response.statusCode)) {
228
- debug$8('response code %d', response.statusCode);
229
- const err = new Error(commonTags.stripIndent `
230
- Failed downloading the Cypress binary.
231
- Response code: ${response.statusCode}
232
- Response message: ${response.statusMessage}
233
- `);
234
- reject(err);
235
- // status codes here are all 2xx
236
- }
237
- else {
238
- // We only enable this pipe connection when we know we've got a successful return
239
- // and handle the completion with verify and resolve
240
- // there was a possible race condition between end of request and close of writeStream
241
- // that is made ordered with this Promise.all
242
- Bluebird.all([new Bluebird((r) => {
243
- return response.pipe(fs.createWriteStream(downloadDestination).on('close', r));
244
- }), new Bluebird((r) => response.on('end', r))])
245
- .then(() => {
246
- debug$8('downloading finished');
247
- verifyDownloadedFile(downloadDestination, expectedSize, expectedChecksum)
248
- .then(() => debug$8('verified'))
249
- .then(() => resolve(version))
250
- .catch(reject);
251
- });
252
- }
253
- })
254
- .on('error', (e) => {
255
- if (e.code === 'ECONNRESET')
256
- return; // sometimes proxies give ECONNRESET but we don't care
257
- reject(e);
258
- })
259
- .on('progress', (state) => {
260
- // total time we've elapsed
261
- // starting on our first progress notification
262
- const elapsed = +new Date() - +started;
263
- // request-progress sends a value between 0 and 1
264
- const percentage = xvfb.util.convertPercentToPercentage(state.percent);
265
- const eta = xvfb.util.calculateEta(percentage, elapsed);
266
- // send up our percent and seconds remaining
267
- progress.onProgress(percentage, xvfb.util.secsRemaining(eta));
268
- });
269
- });
270
- };
271
- /**
272
- * Download Cypress.zip from external versionUrl to local file.
273
- * @param [string] version Could be "3.3.0" or full URL
274
- * @param [string] downloadDestination Local filename to save as
275
- */
276
- const start$3 = (opts) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
277
- let { version, downloadDestination, progress, redirectTTL } = opts;
278
- if (!downloadDestination) {
279
- assert.ok(_.isString(downloadDestination) && !_.isEmpty(downloadDestination), 'missing download dir');
280
- }
281
- if (!progress) {
282
- progress = { onProgress: () => {
283
- return {};
284
- } };
285
- }
286
- const arch = yield xvfb.util.getRealArch();
287
- const versionUrl = getUrl(arch, version);
288
- progress.throttle = 100;
289
- debug$8('needed Cypress version: %s', version);
290
- debug$8('source url %s', versionUrl);
291
- debug$8(`downloading cypress.zip to "${downloadDestination}"`);
292
- try {
293
- // ensure download dir exists
294
- yield fs.ensureDir(path.dirname(downloadDestination));
295
- const ca = yield getCA();
296
- return downloadFromUrl(Object.assign({ url: versionUrl, downloadDestination, progress, ca, version }, (redirectTTL ? { redirectTTL } : {})));
297
- }
298
- catch (err) {
299
- return prettyDownloadErr(err, versionUrl);
300
- }
301
- });
302
- const downloadModule = {
303
- start: start$3,
304
- getUrl,
305
- getProxyForUrlWithNpmConfig,
306
- getCA,
307
- };
308
-
309
- const pipelineAsync = util.promisify(require$$0.pipeline);
310
- // Unix file mode masks for entries stored in zip's externalFileAttributes
311
- // (the high 16 bits when the file was zipped on a Unix host).
312
- const S_IFMT = 0o170000;
313
- const S_IFDIR = 0o040000;
314
- const S_IFLNK = 0o120000;
315
- // PATH_MAX on Linux/macOS is 4096; symlink targets larger than this are not
316
- // legal filesystem paths and almost certainly indicate a malformed or
317
- // malicious archive. The cap also prevents reading an arbitrarily large
318
- // "symlink" entry into memory.
319
- const MAX_SYMLINK_TARGET_BYTES = 4096;
320
- /**
321
- * Extracts the contents of a zip archive into the given destination directory.
322
- * Recreates directories, regular files, and symlinks while preserving Unix
323
- * file modes encoded in each entry's external attributes. Calls `onEntry` once
324
- * per archive entry processed. Refuses entries whose resolved path would
325
- * escape the destination directory.
326
- */
327
- const extractWithYauzl = (zipFilePath, destDir, onEntry) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
328
- const resolvedDest = path.resolve(destDir);
329
- yield new Promise((resolve, reject) => {
330
- // autoClose: false — `finish` below owns closing the zipfile, so we don't
331
- // want yauzl's internal end-listener closing it first and tripping a
332
- // double-close (EBADF) when we do.
333
- yauzl.open(zipFilePath, { lazyEntries: true, autoClose: false }, (err, zipFile) => {
334
- if (err) {
335
- return reject(err);
336
- }
337
- // `settled` guards against an in-flight `handleEntry` calling
338
- // `zipFile.readEntry()` on a now-closed handle when extraction has
339
- // already failed (e.g. yauzl emitted 'error' while we were writing
340
- // an entry to disk).
341
- let settled = false;
342
- const finish = _.once((err) => {
343
- var _a, _b;
344
- settled = true;
345
- (_a = zipFile.removeAllListeners) === null || _a === void 0 ? void 0 : _a.call(zipFile);
346
- (_b = zipFile.close) === null || _b === void 0 ? void 0 : _b.call(zipFile);
347
- if (err) {
348
- return reject(err);
349
- }
350
- return resolve();
351
- });
352
- // Normalize any thrown / emitted value into a real Error so that a
353
- // falsy rejection (e.g. `Promise.reject(undefined)`) doesn't get
354
- // misread by `finish` as a successful completion.
355
- const fail = (err) => {
356
- finish(err instanceof Error ? err : new Error(typeof err === 'string' && err ? err : 'zip extraction failed'));
357
- };
358
- zipFile.on('error', fail);
359
- zipFile.on('end', () => finish());
360
- zipFile.on('entry', (entry) => {
361
- handleEntry(zipFile, entry, resolvedDest)
362
- .then(() => {
363
- if (settled)
364
- return;
365
- onEntry();
366
- zipFile.readEntry();
367
- })
368
- .catch(fail);
369
- });
370
- zipFile.readEntry();
371
- });
372
- });
373
- });
374
- const handleEntry = (zipFile, entry, resolvedDest) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
375
- const fileDest = path.resolve(resolvedDest, entry.fileName);
376
- // refuse anything that would write outside the install dir
377
- if (fileDest !== resolvedDest &&
378
- !fileDest.startsWith(resolvedDest + path.sep)) {
379
- throw new Error(`Refusing to extract entry outside of destination: ${entry.fileName}`);
380
- }
381
- const unixMode = (entry.externalFileAttributes >>> 16) & 0xffff;
382
- // Some archivers mark directories by Unix mode bits instead of (or in
383
- // addition to) a trailing slash; honor both so we don't extract a
384
- // directory entry as a zero-byte file.
385
- const isDir = /\/$/.test(entry.fileName) || (unixMode & S_IFMT) === S_IFDIR;
386
- const isSymlink = (unixMode & S_IFMT) === S_IFLNK;
387
- if (isDir) {
388
- yield fsp.mkdir(fileDest, { recursive: true });
389
- return;
390
- }
391
- yield fsp.mkdir(path.dirname(fileDest), { recursive: true });
392
- if (isSymlink) {
393
- if (entry.uncompressedSize > MAX_SYMLINK_TARGET_BYTES) {
394
- throw new Error(`Refusing to extract symlink with target larger than ${MAX_SYMLINK_TARGET_BYTES} bytes: ${entry.fileName}`);
395
- }
396
- const linkTarget = yield readEntryAsString(zipFile, entry, MAX_SYMLINK_TARGET_BYTES);
397
- const resolvedTarget = path.resolve(path.dirname(fileDest), linkTarget);
398
- if (resolvedTarget !== resolvedDest &&
399
- !resolvedTarget.startsWith(resolvedDest + path.sep)) {
400
- throw new Error(`Refusing to extract symlink pointing outside of destination: ${entry.fileName} -> ${linkTarget}`);
401
- }
402
- yield fsp.rm(fileDest, { recursive: true, force: true });
403
- yield fsp.symlink(linkTarget, fileDest);
404
- return;
405
- }
406
- const readStream = yield new Promise((res, rej) => {
407
- zipFile.openReadStream(entry, (err, rs) => {
408
- if (err) {
409
- return rej(err);
410
- }
411
- return res(rs);
412
- });
413
- });
414
- // Preserve the Unix mode bits when present; otherwise fall back to a sane default.
415
- const fileMode = (unixMode & 0o7777) || 0o644;
416
- const writeStream = fs$1.createWriteStream(fileDest, { mode: fileMode });
417
- yield pipelineAsync(readStream, writeStream);
418
- });
419
- const readEntryAsString = (zipFile, entry, maxBytes) => {
420
- return new Promise((resolve, reject) => {
421
- zipFile.openReadStream(entry, (err, rs) => {
422
- if (err) {
423
- return reject(err);
424
- }
425
- const chunks = [];
426
- let received = 0;
427
- let bailed = false;
428
- const bail = (err) => {
429
- var _a;
430
- if (bailed)
431
- return;
432
- bailed = true;
433
- (_a = rs.destroy) === null || _a === void 0 ? void 0 : _a.call(rs, err);
434
- reject(err);
435
- };
436
- rs.on('data', (chunk) => {
437
- received += chunk.length;
438
- if (received > maxBytes) {
439
- bail(new Error(`Refusing to read entry body larger than ${maxBytes} bytes: ${entry.fileName}`));
440
- return;
441
- }
442
- chunks.push(chunk);
443
- });
444
- rs.on('end', () => {
445
- if (bailed)
446
- return;
447
- resolve(Buffer.concat(chunks).toString('utf8'));
448
- });
449
- rs.on('error', bail);
450
- });
451
- });
452
- };
453
-
454
- const debug$7 = Debug('cypress:cli:unzip');
455
- const unzipTools = {
456
- extractWithYauzl,
457
- };
458
- // expose this function for simple testing
459
- const unzip = (_a) => xvfb.__awaiter(void 0, [_a], void 0, function* ({ zipFilePath, installDir, progress }) {
460
- debug$7('unzipping from %s', zipFilePath);
461
- debug$7('into', installDir);
462
- if (!zipFilePath) {
463
- throw new Error('Missing zip filename');
464
- }
465
- const startTime = Date.now();
466
- let yauzlDoneTime = 0;
467
- yield fs.ensureDir(installDir);
468
- yield new Promise((resolve, reject) => {
469
- // Open with lazyEntries so yauzl doesn't auto-emit entries (which would
470
- // require the fd to stay open for the duration of the OS-tool extraction).
471
- // We only need the entryCount here for the progress calculation.
472
- return yauzl.open(zipFilePath, { lazyEntries: true }, (err, zipFile) => {
473
- yauzlDoneTime = Date.now();
474
- if (err) {
475
- debug$7('error using yauzl %s', err.message);
476
- return reject(err);
477
- }
478
- const total = zipFile.entryCount;
479
- debug$7('zipFile entries count', total);
480
- // Close the count-only handle — the Node fallback re-opens the zip for extraction.
481
- zipFile.close();
482
- const started = new Date();
483
- let percent = 0;
484
- let count = 0;
485
- const notify = (percent) => {
486
- const elapsed = +new Date() - +started;
487
- const eta = xvfb.util.calculateEta(percent, elapsed);
488
- progress.onProgress(percent, xvfb.util.secsRemaining(eta));
489
- };
490
- const tick = () => {
491
- count += 1;
492
- percent = ((count / total) * 100);
493
- const displayPercent = percent.toFixed(0);
494
- return notify(Number(displayPercent));
495
- };
496
- const unzipWithNode = () => xvfb.__awaiter(void 0, void 0, void 0, function* () {
497
- debug$7('unzipping with node.js (slow)');
498
- try {
499
- yield unzipTools.extractWithYauzl(zipFilePath, installDir, tick);
500
- debug$7('node unzip finished');
501
- return resolve();
502
- }
503
- catch (err) {
504
- const error = err || new Error('Unknown error with Node extract tool');
505
- debug$7('error %s', error.message);
506
- return reject(error);
507
- }
508
- });
509
- const unzipFallback = _.once(unzipWithNode);
510
- const unzipWithUnzipTool = () => {
511
- debug$7('unzipping via `unzip`');
512
- const inflatingRe = /inflating:/;
513
- const sp = cp.spawn('unzip', ['-o', zipFilePath, '-d', installDir]);
514
- sp.on('error', (err) => {
515
- debug$7('unzip tool error: %s', err.message);
516
- unzipFallback();
517
- });
518
- sp.on('close', (code) => {
519
- debug$7('unzip tool close with code %d', code);
520
- if (code === 0) {
521
- percent = 100;
522
- notify(percent);
523
- return resolve();
524
- }
525
- debug$7('`unzip` failed %o', { code });
526
- return unzipFallback();
527
- });
528
- sp.stdout.on('data', (data) => {
529
- if (inflatingRe.test(data)) {
530
- return tick();
531
- }
532
- });
533
- sp.stderr.on('data', (data) => {
534
- debug$7('`unzip` stderr %s', data);
535
- });
536
- };
537
- // we attempt to first unzip with the native osx
538
- // ditto because its less likely to have problems
539
- // with corruption, symlinks, or icons causing failures
540
- // and can handle resource forks
541
- // http://automatica.com.au/2011/02/unzip-mac-os-x-zip-in-terminal/
542
- const unzipWithOsx = () => {
543
- debug$7('unzipping via `ditto`');
544
- const copyingFileRe = /^copying file/;
545
- const sp = cp.spawn('ditto', ['-xkV', zipFilePath, installDir]);
546
- // f-it just unzip with node
547
- sp.on('error', (err) => {
548
- debug$7(err.message);
549
- unzipFallback();
550
- });
551
- sp.on('close', (code) => {
552
- if (code === 0) {
553
- // make sure we get to 100% on the progress bar
554
- // because reading in lines is not really accurate
555
- percent = 100;
556
- notify(percent);
557
- return resolve();
558
- }
559
- debug$7('`ditto` failed %o', { code });
560
- return unzipFallback();
561
- });
562
- return readline.createInterface({
563
- input: sp.stderr,
564
- })
565
- .on('line', (line) => {
566
- if (copyingFileRe.test(line)) {
567
- return tick();
568
- }
569
- });
570
- };
571
- switch (os.platform()) {
572
- case 'darwin':
573
- return unzipWithOsx();
574
- case 'linux':
575
- return unzipWithUnzipTool();
576
- case 'win32':
577
- return unzipWithNode();
578
- default:
579
- return;
580
- }
581
- });
582
- });
583
- debug$7('unzip completed %o', {
584
- yauzlMs: yauzlDoneTime - startTime,
585
- unzipMs: Date.now() - yauzlDoneTime,
586
- });
587
- });
588
- function isMaybeWindowsMaxPathLengthError(err) {
589
- return os.platform() === 'win32' && err.code === 'ENOENT' && err.syscall === 'realpath';
590
- }
591
- const start$2 = (_a) => xvfb.__awaiter(void 0, [_a], void 0, function* ({ zipFilePath, installDir, progress }) {
592
- assert.ok(_.isString(installDir) && !_.isEmpty(installDir), 'missing installDir');
593
- if (!progress) {
594
- progress = { onProgress: () => {
595
- return {};
596
- } };
597
- }
598
- try {
599
- const installDirExists = yield fs.pathExists(installDir);
600
- if (installDirExists) {
601
- debug$7('removing existing unzipped binary', installDir);
602
- yield fs.remove(installDir);
603
- }
604
- yield unzip({ zipFilePath, installDir, progress });
605
- }
606
- catch (err) {
607
- const errorTemplate = isMaybeWindowsMaxPathLengthError(err) ?
608
- xvfb.errors.failedUnzipWindowsMaxPathLength
609
- : xvfb.errors.failedUnzip;
610
- yield xvfb.throwFormErrorText(errorTemplate)(err);
611
- }
612
- });
613
- const unzipModule = {
614
- start: start$2,
615
- utils: {
616
- unzip,
617
- unzipTools,
618
- },
619
- };
620
-
621
- const debug$6 = Debug('cypress:cli:install');
622
- function _getBinaryUrlFromBuildInfo(version, arch, { commitSha, commitBranch }) {
623
- const platform = os.platform();
624
- if ((platform === 'win32') && (arch === 'arm64')) {
625
- debug$6(`detected platform ${platform} architecture ${arch} combination`);
626
- arch = 'x64';
627
- debug$6(`overriding to download ${platform}-${arch} pre-release binary instead`);
628
- }
629
- return `https://cdn.cypress.io/beta/binary/${version}/${platform}-${arch}/${commitBranch}-${commitSha}/cypress.zip`;
630
- }
631
- const alreadyInstalledMsg = () => {
632
- if (!xvfb.util.isPostInstall()) {
633
- xvfb.loggerModule.log(commonTags.stripIndent `
634
- Skipping installation:
635
-
636
- Pass the ${chalk.yellow('--force')} option if you'd like to reinstall anyway.
637
- `);
638
- }
639
- };
640
- const displayCompletionMsg = () => {
641
- xvfb.loggerModule.log();
642
- xvfb.loggerModule.log('You can now open Cypress by running one of the following, depending on your package manager:');
643
- xvfb.loggerModule.log();
644
- xvfb.loggerModule.log(chalk.cyan('- npx cypress open'));
645
- xvfb.loggerModule.log(chalk.cyan('- yarn cypress open'));
646
- xvfb.loggerModule.log(chalk.cyan('- pnpm cypress open'));
647
- xvfb.loggerModule.log();
648
- xvfb.loggerModule.log(chalk.grey('https://on.cypress.io/opening-the-app'));
649
- xvfb.loggerModule.log();
650
- };
651
- const validateOS = () => xvfb.__awaiter(void 0, void 0, void 0, function* () {
652
- const platformInfo = yield xvfb.util.getPlatformInfo();
653
- return platformInfo.match(/(win32-x64|win32-arm64|linux-x64|linux-arm64|darwin-x64|darwin-arm64)/);
654
- });
655
- /**
656
- * Returns the version to install - either a string like `1.2.3` to be fetched
657
- * from the download server or a file path or HTTP URL.
658
- */
659
- function getVersionOverride(version, { arch, envVarVersion, buildInfo }) {
660
- // let this environment variable reset the binary version we need
661
- if (envVarVersion) {
662
- return envVarVersion;
663
- }
664
- if (buildInfo && !buildInfo.stable) {
665
- xvfb.loggerModule.log(chalk.yellow(commonTags.stripIndent `
666
- ${logSymbols.warning} Warning: You are installing a pre-release build of Cypress.
667
-
668
- Bugs may be present which do not exist in production builds.
669
-
670
- This build was created from:
671
- * Commit SHA: ${buildInfo.commitSha}
672
- * Commit Branch: ${buildInfo.commitBranch}
673
- * Commit Timestamp: ${buildInfo.commitDate}
674
- `));
675
- xvfb.loggerModule.log();
676
- return _getBinaryUrlFromBuildInfo(version, arch, buildInfo);
677
- }
678
- }
679
- function getEnvVarVersion() {
680
- if (!xvfb.util.getEnv('CYPRESS_INSTALL_BINARY'))
681
- return;
682
- // because passed file paths are often double quoted
683
- // and might have extra whitespace around, be robust and trim the string
684
- const trimAndRemoveDoubleQuotes = true;
685
- const envVarVersion = xvfb.util.getEnv('CYPRESS_INSTALL_BINARY', trimAndRemoveDoubleQuotes);
686
- debug$6('using environment variable CYPRESS_INSTALL_BINARY "%s"', envVarVersion);
687
- return envVarVersion;
688
- }
689
- const start$1 = (...args_1) => xvfb.__awaiter(void 0, [...args_1], void 0, function* (options = {}) {
690
- debug$6('installing with options %j', options);
691
- const envVarVersion = getEnvVarVersion();
692
- if (envVarVersion === '0') {
693
- debug$6('environment variable CYPRESS_INSTALL_BINARY = 0, skipping install');
694
- xvfb.loggerModule.log(commonTags.stripIndent `
695
- ${chalk.yellow('Note:')} Skipping binary installation: Environment variable CYPRESS_INSTALL_BINARY = 0.`);
696
- xvfb.loggerModule.log();
697
- return;
698
- }
699
- const pkgPath = xvfb.relativeToRepoRoot('package.json');
700
- if (!pkgPath) {
701
- return xvfb.throwFormErrorText('Could not find package.json for Cypress package to determine build information')();
702
- }
703
- const { buildInfo, version } = JSON.parse(yield fsp.readFile(pkgPath, 'utf8'));
704
- _.defaults(options, {
705
- force: false,
706
- buildInfo,
707
- });
708
- if (xvfb.util.getEnv('CYPRESS_CACHE_FOLDER')) {
709
- const envCache = xvfb.util.getEnv('CYPRESS_CACHE_FOLDER');
710
- xvfb.loggerModule.log(commonTags.stripIndent `
711
- ${chalk.yellow('Note:')} Overriding Cypress cache directory to: ${chalk.cyan(envCache)}
712
-
713
- Previous installs of Cypress may not be found.
714
- `);
715
- xvfb.loggerModule.log();
716
- }
717
- const pkgVersion = xvfb.util.pkgVersion();
718
- const arch = yield xvfb.util.getRealArch();
719
- const versionOverride = getVersionOverride(version, { arch, envVarVersion, buildInfo: options.buildInfo });
720
- const versionToInstall = versionOverride || pkgVersion;
721
- debug$6('version in package.json is %s, version to install is %s', pkgVersion, versionToInstall);
722
- const installDir = xvfb.stateModule.getVersionDir(pkgVersion, options.buildInfo);
723
- const cacheDir = xvfb.stateModule.getCacheDir();
724
- const binaryDir = xvfb.stateModule.getBinaryDir(pkgVersion);
725
- if (!(yield validateOS())) {
726
- return xvfb.throwFormErrorText(xvfb.errors.invalidOS)();
727
- }
728
- try {
729
- yield fs.ensureDir(cacheDir);
730
- }
731
- catch (err) {
732
- if (err instanceof Error && 'code' in err && err.code === 'EACCES') {
733
- return xvfb.throwFormErrorText(xvfb.errors.invalidCacheDirectory)(commonTags.stripIndent `
734
- Failed to access ${chalk.cyan(cacheDir)}:
735
-
736
- ${err.message}
737
- `);
738
- }
739
- else {
740
- throw err;
741
- }
742
- }
743
- const binaryPkg = yield xvfb.stateModule.getBinaryPkgAsync(binaryDir);
744
- const binaryVersion = yield xvfb.stateModule.getBinaryPkgVersion(binaryPkg);
745
- const shouldInstall = () => {
746
- if (!binaryVersion) {
747
- debug$6('no binary installed under cli version');
748
- return true;
749
- }
750
- xvfb.loggerModule.log();
751
- xvfb.loggerModule.log(commonTags.stripIndent `
752
- Cypress ${chalk.green(binaryVersion)} is installed in ${chalk.cyan(installDir)}
753
- `);
754
- xvfb.loggerModule.log();
755
- if (options.force) {
756
- debug$6('performing force install over existing binary');
757
- return true;
758
- }
759
- if ((binaryVersion === versionToInstall) || !xvfb.util.isSemver(versionToInstall)) {
760
- // our version matches, tell the user this is a noop
761
- alreadyInstalledMsg();
762
- return false;
763
- }
764
- return true;
765
- };
766
- // noop if we've been told not to download
767
- if (!shouldInstall()) {
768
- return debug$6('Not downloading or installing binary');
769
- }
770
- if (envVarVersion) {
771
- xvfb.loggerModule.log(chalk.yellow(commonTags.stripIndent `
772
- ${logSymbols.warning} Warning: Forcing a binary version different than the default.
773
-
774
- The CLI expected to install version: ${chalk.green(pkgVersion)}
775
-
776
- Instead we will install version: ${chalk.green(versionToInstall)}
777
-
778
- These versions may not work properly together.
779
- `));
780
- xvfb.loggerModule.log();
781
- }
782
- const getLocalFilePath = () => xvfb.__awaiter(void 0, void 0, void 0, function* () {
783
- // see if version supplied is a path to a binary
784
- if (yield fs.pathExists(versionToInstall)) {
785
- return path.extname(versionToInstall) === '.zip' ? versionToInstall : false;
786
- }
787
- const possibleFile = xvfb.util.formAbsolutePath(versionToInstall);
788
- debug$6('checking local file', possibleFile, 'cwd', process.cwd());
789
- // if this exists return the path to it
790
- // else false
791
- if ((yield fs.pathExists(possibleFile)) && path.extname(possibleFile) === '.zip') {
792
- return possibleFile;
793
- }
794
- return false;
795
- });
796
- const pathToLocalFile = yield getLocalFilePath();
797
- const tasks = pathToLocalFile ?
798
- installFromLocal(pathToLocalFile, installDir) :
799
- installFromRemote(versionToInstall, installDir);
800
- if (options.force) {
801
- debug$6('Cypress already installed at', installDir);
802
- debug$6('but the installation was forced');
803
- }
804
- // let the user know what version of cypress we're downloading!
805
- xvfb.loggerModule.log(`Installing Cypress ${chalk.gray(`(version: ${versionToInstall})`)}`);
806
- xvfb.loggerModule.log();
807
- const taskRunner = new listr2.Listr(tasks, Object.assign(Object.assign({
808
- // In CI we want timestamped, line-per-event output. Locally,
809
- // the default in-place spinner is the better experience.
810
- renderer: xvfb.util.isCi() ? 'verbose' : 'default' }, (xvfb.util.isCi() && { rendererOptions: { timestamp: listr2.PRESET_TIMESTAMP } })), { silentRendererCondition: () => xvfb.loggerModule.logLevel() === 'silent' }));
811
- yield taskRunner.run();
812
- // delay 1 sec for UX, unless we are testing
813
- yield timers.setTimeout(1000);
814
- displayCompletionMsg();
815
- });
816
- function downloadArchive(version, downloadDestination) {
817
- const inProgressTitle = 'Downloading Cypress';
818
- const completedTitle = chalk.green('Downloaded Cypress');
819
- return {
820
- title: xvfb.util.titleize(inProgressTitle),
821
- task: (ctx, task) => xvfb.__awaiter(this, void 0, void 0, function* () {
822
- yield downloadModule.start({
823
- version,
824
- downloadDestination,
825
- progress: {
826
- throttle: 100,
827
- onProgress: (percentComplete, remaining) => {
828
- task.title = progressTitle(inProgressTitle, percentComplete, remaining);
829
- },
830
- },
831
- });
832
- debug$6(`finished downloading file: ${downloadDestination}`);
833
- task.title = xvfb.util.titleize(completedTitle);
834
- }),
835
- };
836
- }
837
- function installFromLocal(pathToLocalFile, installDir) {
838
- const zipFilePath = path.resolve(pathToLocalFile);
839
- debug$6('found local file at', zipFilePath);
840
- debug$6('skipping download');
841
- return [
842
- unzipArchive(zipFilePath, installDir),
843
- ];
844
- }
845
- function installFromRemote(version, installDir) {
846
- const downloadDestination = path.join(os.tmpdir(), `cypress-${process.pid}.zip`);
847
- debug$6('preparing to download and unzip version ', version, 'to path', installDir);
848
- return [
849
- downloadArchive(version, downloadDestination),
850
- unzipArchive(downloadDestination, installDir),
851
- cleanup(downloadDestination, installDir),
852
- ];
853
- }
854
- function unzipArchive(zipFilePath, installDir) {
855
- const inProgressTitle = 'Unzipping Cypress';
856
- const completedTitle = chalk.green('Unzipped Cypress');
857
- return {
858
- title: xvfb.util.titleize(inProgressTitle),
859
- task: (ctx, task) => xvfb.__awaiter(this, void 0, void 0, function* () {
860
- yield unzipModule.start({
861
- zipFilePath,
862
- installDir,
863
- progress: {
864
- onProgress: (percentComplete, remaining) => {
865
- task.title = progressTitle(inProgressTitle, percentComplete, remaining);
866
- },
867
- },
868
- });
869
- task.title = xvfb.util.titleize(completedTitle);
870
- }),
871
- };
872
- }
873
- function cleanup(archiveLocation, installDir) {
874
- return {
875
- title: xvfb.util.titleize('Finishing Installation'),
876
- task: (ctx, task) => xvfb.__awaiter(this, void 0, void 0, function* () {
877
- debug$6('removing zip file %s', archiveLocation);
878
- yield fs.remove(archiveLocation);
879
- debug$6('finished installation in', installDir);
880
- task.title = xvfb.util.titleize(chalk.green('Finished Installation'), chalk.gray(installDir));
881
- }),
882
- };
883
- }
884
- function progressTitle(title, percentComplete, remaining) {
885
- return xvfb.util.titleize(title, chalk.white(` ${percentComplete}%`), chalk.gray(`${remaining}s`));
886
- }
887
- var installModule = {
888
- start: start$1,
889
- _getBinaryUrlFromBuildInfo,
890
- };
891
-
892
- /**
893
- * Throws an error with "details" property from
894
- * "errors" object.
895
- * @param {Object} details - Error details
896
- */
897
- const throwInvalidOptionError = (details) => {
898
- if (!details) {
899
- details = xvfb.errors.unknownError;
900
- }
901
- // throw this error synchronously, it will be caught later on and
902
- // the details will be propagated to the promise chain
903
- const err = new Error();
904
- err.details = details;
905
- throw err;
906
- };
907
- /**
908
- * Selects exec args based on the configured `testingType`
909
- * @param {string} testingType The type of tests being executed
910
- * @returns {string[]} The array of new exec arguments
911
- */
912
- const processTestingType = (options) => {
913
- if (options.e2e && options.component) {
914
- return throwInvalidOptionError(xvfb.errors.incompatibleTestTypeFlags);
915
- }
916
- if (options.testingType && (options.component || options.e2e)) {
917
- return throwInvalidOptionError(xvfb.errors.incompatibleTestTypeFlags);
918
- }
919
- if (options.testingType === 'component' || options.component || options.ct) {
920
- return ['--testing-type', 'component'];
921
- }
922
- if (options.testingType === 'e2e' || options.e2e) {
923
- return ['--testing-type', 'e2e'];
924
- }
925
- if (options.testingType) {
926
- return throwInvalidOptionError(xvfb.errors.invalidTestingType);
927
- }
928
- return [];
929
- };
930
- /**
931
- * Throws an error if configFile is string 'false' or boolean false
932
- * @param {*} options
933
- */
934
- const checkConfigFile = (options) => {
935
- // CLI will parse as string, module API can pass in boolean
936
- if (options.configFile === 'false' || options.configFile === false) {
937
- throwInvalidOptionError(xvfb.errors.invalidConfigFile);
938
- }
939
- };
940
-
941
- const debug$5 = Debug('cypress:cli');
942
- /**
943
- * Maps options collected by the CLI
944
- * and forms list of CLI arguments to the server.
945
- *
946
- * Note: there is lightweight validation, with errors
947
- * thrown synchronously.
948
- *
949
- * @returns {string[]} list of CLI arguments
950
- */
951
- const processOpenOptions = (options = {}) => {
952
- // In addition to setting the project directory, setting the project option
953
- // here ultimately decides whether cypress is run in global mode or not.
954
- // It's first based off whether it's installed globally by npm/yarn (-g).
955
- // A global install can be overridden by the --project flag, putting Cypress
956
- // in project mode. A non-global install can be overridden by the --global
957
- // flag, putting it in global mode.
958
- if (!xvfb.util.isInstalledGlobally() && !options.global && !options.project) {
959
- options.project = process.cwd();
960
- }
961
- const args = [];
962
- if (options.config) {
963
- args.push('--config', options.config);
964
- }
965
- if (options.configFile !== undefined) {
966
- checkConfigFile(options);
967
- args.push('--config-file', options.configFile);
968
- }
969
- if (options.browser) {
970
- args.push('--browser', options.browser);
971
- }
972
- if (options.env) {
973
- args.push('--env', options.env);
974
- }
975
- if (options.expose) {
976
- args.push('--expose', options.expose);
977
- }
978
- if (options.port) {
979
- args.push('--port', options.port);
980
- }
981
- if (options.project) {
982
- args.push('--project', options.project);
983
- }
984
- if (options.global) {
985
- args.push('--global', options.global);
986
- }
987
- if (options.inspect) {
988
- args.push('--inspect');
989
- }
990
- if (options.inspectBrk) {
991
- args.push('--inspectBrk');
992
- }
993
- args.push(...processTestingType(options));
994
- debug$5('opening from options %j', options);
995
- debug$5('command line arguments %j', args);
996
- return args;
997
- };
998
- const start = (...args_1) => xvfb.__awaiter(void 0, [...args_1], void 0, function* (options = {}) {
999
- function open() {
1000
- try {
1001
- const args = processOpenOptions(options);
1002
- return spawn.start$1(args, {
1003
- dev: options.dev,
1004
- detached: Boolean(options.detached),
1005
- });
1006
- }
1007
- catch (err) {
1008
- if (err.details) {
1009
- return xvfb.exitWithError(err.details)();
1010
- }
1011
- throw err;
1012
- }
1013
- }
1014
- if (options.dev) {
1015
- return open();
1016
- }
1017
- yield spawn.start();
1018
- return open();
1019
- });
1020
- var openModule = {
1021
- start,
1022
- processOpenOptions,
1023
- };
1024
-
1025
- const debug$4 = Debug('cypress:cli:run');
1026
- /**
1027
- * Typically a user passes a string path to the project.
1028
- * But "cypress open" allows using `false` to open in global mode,
1029
- * and the user can accidentally execute `cypress run --project false`
1030
- * which should be invalid.
1031
- */
1032
- const isValidProject = (v) => {
1033
- if (typeof v === 'boolean') {
1034
- return false;
1035
- }
1036
- if (v === '' || v === 'false' || v === 'true') {
1037
- return false;
1038
- }
1039
- return true;
1040
- };
1041
- /**
1042
- * Maps options collected by the CLI
1043
- * and forms list of CLI arguments to the server.
1044
- *
1045
- * Note: there is lightweight validation, with errors
1046
- * thrown synchronously.
1047
- *
1048
- * @returns {string[]} list of CLI arguments
1049
- */
1050
- const processRunOptions = (options = {}) => {
1051
- debug$4('processing run options %o', options);
1052
- if (!isValidProject(options.project)) {
1053
- debug$4('invalid project option %o', { project: options.project });
1054
- return throwInvalidOptionError(xvfb.errors.invalidRunProjectPath);
1055
- }
1056
- const args = ['--run-project', options.project];
1057
- if (options.autoCancelAfterFailures || options.autoCancelAfterFailures === 0 || options.autoCancelAfterFailures === false) {
1058
- args.push('--auto-cancel-after-failures', options.autoCancelAfterFailures);
1059
- }
1060
- if (options.browser) {
1061
- args.push('--browser', options.browser);
1062
- }
1063
- if (options.ciBuildId) {
1064
- args.push('--ci-build-id', options.ciBuildId);
1065
- }
1066
- if (options.config) {
1067
- args.push('--config', options.config);
1068
- }
1069
- if (options.configFile !== undefined) {
1070
- checkConfigFile(options);
1071
- args.push('--config-file', options.configFile);
1072
- }
1073
- if (options.env) {
1074
- args.push('--env', options.env);
1075
- }
1076
- if (options.expose) {
1077
- args.push('--expose', options.expose);
1078
- }
1079
- if (options.exit === false) {
1080
- args.push('--no-exit');
1081
- }
1082
- if (options.group) {
1083
- args.push('--group', options.group);
1084
- }
1085
- if (options.headed) {
1086
- args.push('--headed', options.headed);
1087
- }
1088
- if (options.headless) {
1089
- if (options.headed) {
1090
- return throwInvalidOptionError(xvfb.errors.incompatibleHeadlessFlags);
1091
- }
1092
- args.push('--headed', String(!options.headless));
1093
- }
1094
- // if key is set use that - else attempt to find it by environment variable
1095
- if (options.key == null) {
1096
- debug$4('--key is not set, looking up environment variable CYPRESS_RECORD_KEY');
1097
- options.key = xvfb.util.getEnv('CYPRESS_RECORD_KEY');
1098
- }
1099
- // if we have a key assume we're in record mode
1100
- if (options.key) {
1101
- args.push('--key', options.key);
1102
- }
1103
- if (options.outputPath) {
1104
- args.push('--output-path', options.outputPath);
1105
- }
1106
- if (options.parallel) {
1107
- args.push('--parallel');
1108
- }
1109
- if (options.passWithNoTests) {
1110
- args.push('--pass-with-no-tests');
1111
- }
1112
- if (options.posixExitCodes) {
1113
- args.push('--posix-exit-codes');
1114
- }
1115
- if (options.port) {
1116
- args.push('--port', options.port);
1117
- }
1118
- if (options.quiet) {
1119
- args.push('--quiet');
1120
- }
1121
- // if record is defined and we're not
1122
- // already in ci mode, then send it up
1123
- if (options.record != null) {
1124
- args.push('--record', options.record);
1125
- }
1126
- // if we have a specific reporter push that into the args
1127
- if (options.reporter) {
1128
- args.push('--reporter', options.reporter);
1129
- }
1130
- // if we have a specific reporter push that into the args
1131
- if (options.reporterOptions) {
1132
- args.push('--reporter-options', options.reporterOptions);
1133
- }
1134
- if (options.runnerUi != null) {
1135
- args.push('--runner-ui', options.runnerUi);
1136
- }
1137
- // if we have specific spec(s) push that into the args
1138
- if (options.spec) {
1139
- args.push('--spec', options.spec);
1140
- }
1141
- if (options.tag) {
1142
- args.push('--tag', options.tag);
1143
- }
1144
- if (options.inspect) {
1145
- args.push('--inspect');
1146
- }
1147
- if (options.inspectBrk) {
1148
- args.push('--inspectBrk');
1149
- }
1150
- args.push(...processTestingType(options));
1151
- return args;
1152
- };
1153
- const runModule = {
1154
- processRunOptions,
1155
- isValidProject,
1156
- // resolves with the number of failed tests
1157
- start() {
1158
- return xvfb.__awaiter(this, arguments, void 0, function* (options = {}) {
1159
- _.defaults(options, {
1160
- key: null,
1161
- spec: null,
1162
- reporter: null,
1163
- reporterOptions: null,
1164
- project: process.cwd(),
1165
- });
1166
- function run() {
1167
- try {
1168
- const args = processRunOptions(options);
1169
- debug$4('run to spawn.start args %j', args);
1170
- return spawn.start$1(args, {
1171
- dev: options.dev,
1172
- });
1173
- }
1174
- catch (err) {
1175
- if (err.details) {
1176
- return xvfb.exitWithError(err.details)();
1177
- }
1178
- throw err;
1179
- }
1180
- }
1181
- if (options.dev) {
1182
- return run();
1183
- }
1184
- yield spawn.start();
1185
- return run();
1186
- });
1187
- },
1188
- };
1189
-
1190
- /**
1191
- * Get the size of a folder or a file.
1192
- *
1193
- * This function returns the actual file size of the folder (size), not the allocated space on disk (size on disk).
1194
- * For more details between the difference, check this link:
1195
- * https://www.howtogeek.com/180369/why-is-there-a-big-difference-between-size-and-size-on-disk/
1196
- *
1197
- * @param {string} path path to the file or the folder.
1198
- */
1199
- function getSize(path$1) {
1200
- return xvfb.__awaiter(this, void 0, void 0, function* () {
1201
- const stat = yield fs.lstat(path$1);
1202
- if (stat.isDirectory()) {
1203
- const list = yield fs.readdir(path$1);
1204
- return Bluebird.resolve(list).reduce((prev, curr) => xvfb.__awaiter(this, void 0, void 0, function* () {
1205
- const currPath = path.join(path$1, curr);
1206
- const s = yield fs.lstat(currPath);
1207
- if (s.isDirectory()) {
1208
- return prev + (yield getSize(currPath));
1209
- }
1210
- return prev + s.size;
1211
- }), 0);
1212
- }
1213
- return stat.size;
1214
- });
1215
- }
1216
-
1217
- var dist = {};
1218
-
1219
- var tapContract = {};
1220
-
1221
- var tapErrors = {};
64
+ var tapErrors = {};
1222
65
 
1223
66
  var hasRequiredTapErrors;
1224
67
 
1225
68
  function requireTapErrors () {
1226
69
  if (hasRequiredTapErrors) return tapErrors;
1227
70
  hasRequiredTapErrors = 1;
1228
- (function (exports$1) {
71
+ (function (exports) {
1229
72
  // The copy for every `cypress tap` failure, keyed by its code. Code is not usefacing and is
1230
73
  // just used in debug logs and telemetry.
1231
- Object.defineProperty(exports$1, "__esModule", { value: true });
1232
- exports$1.UnknownTapError = exports$1.SpecInProgressTapError = exports$1.VersionSkewTapError = exports$1.MissingOptionTapError = exports$1.MissingArgumentsTapError = exports$1.UnknownOptionTapError = exports$1.UnknownCommandTapError = exports$1.MissingCompanionOptionTapError = exports$1.SnapshotNotFoundTapError = exports$1.AttemptNotFoundTapError = exports$1.CommandNotFoundTapError = exports$1.TestNotFoundTapError = exports$1.SessionNotFoundTapError = exports$1.InvalidValueTapError = exports$1.isTapError = exports$1.TapError = exports$1.tapErrorCopy = exports$1.TAP_ERROR_COPY = exports$1.TAP_TARGET = void 0;
74
+ Object.defineProperty(exports, "__esModule", { value: true });
75
+ exports.UnknownTapError = exports.SpecInProgressTapError = exports.VersionSkewTapError = exports.MissingOptionTapError = exports.MissingArgumentsTapError = exports.UnknownOptionTapError = exports.UnknownCommandTapError = exports.MissingCompanionOptionTapError = exports.SnapshotNotFoundTapError = exports.AttemptNotFoundTapError = exports.CommandNotFoundTapError = exports.TestNotFoundTapError = exports.SessionNotFoundTapError = exports.InvalidValueTapError = exports.isTapError = exports.TapError = exports.tapErrorCopy = exports.TAP_ERROR_COPY = exports.TAP_TARGET = void 0;
1233
76
  const UPDATE_COMMAND = '`npm install --save-dev cypress@latest`';
1234
77
  /**
1235
78
  * What every failure calls the running Cypress a tap command targets.
1236
79
  */
1237
- exports$1.TAP_TARGET = 'Cypress session';
80
+ exports.TAP_TARGET = 'Cypress session';
1238
81
  /**
1239
82
  * Several codes stand for one thing a reader has to do about them, and saying it
1240
83
  * four slightly different ways only reads as four different problems. Each keeps
@@ -1243,14 +86,14 @@ function requireTapErrors () {
1243
86
  * The jsdoc on each entry is where the condition it really stands for is written.
1244
87
  */
1245
88
  const UNREACHABLE = {
1246
- description: `Could not reach the ${exports$1.TAP_TARGET}.`,
89
+ description: `Could not reach the ${exports.TAP_TARGET}.`,
1247
90
  solution: 'Make sure Cypress is still open with a browser, then run the command again. If it was just started, it may still be loading.',
1248
91
  };
1249
92
  // No "if it keeps failing" clause here: the entries that ask for a report already
1250
93
  // open theirs with one, and saying it twice reads as two separate escalations.
1251
94
  const COMMAND_FAILED = {
1252
- description: `The ${exports$1.TAP_TARGET} failed while running the command.`,
1253
- solution: `Check the ${exports$1.TAP_TARGET} with \`cypress tap status\`, then try again.`,
95
+ description: `The ${exports.TAP_TARGET} failed while running the command.`,
96
+ solution: `Check the ${exports.TAP_TARGET} with \`cypress tap status\`, then try again.`,
1254
97
  };
1255
98
  /**
1256
99
  * Nothing worth saying, for the conditions that should not arise: a failure with no
@@ -1262,11 +105,11 @@ function requireTapErrors () {
1262
105
  description: 'An error occurred.',
1263
106
  solution: 'Try running the command again.',
1264
107
  };
1265
- exports$1.TAP_ERROR_COPY = {
108
+ exports.TAP_ERROR_COPY = {
1266
109
  // Finding a session to drive
1267
110
  /** Raised when discovery found no session record at all, and none was named. */
1268
111
  NO_SESSION: {
1269
- description: `Could not find a ${exports$1.TAP_TARGET} to tap into.`,
112
+ description: `Could not find a ${exports.TAP_TARGET} to tap into.`,
1270
113
  solution: 'Start Cypress with `cypress open`, select a testing type and launch a browser, then try again.',
1271
114
  },
1272
115
  /**
@@ -1275,24 +118,24 @@ function requireTapErrors () {
1275
118
  * @deprecated - raise it with new SessionNotFoundTapError(), which writes its detail
1276
119
  */
1277
120
  SESSION_NOT_FOUND: {
1278
- description: `No ${exports$1.TAP_TARGET} matched the provided session id.`,
1279
- solution: `Run \`cypress tap sessions\` to list the ${exports$1.TAP_TARGET}s you can tap into.`,
121
+ description: `No ${exports.TAP_TARGET} matched the provided session id.`,
122
+ solution: `Run \`cypress tap sessions\` to list the ${exports.TAP_TARGET}s you can tap into.`,
1280
123
  },
1281
124
  /** Raised when records matched, but none answered its liveness probe. */
1282
125
  STALE_SESSION: { ...UNREACHABLE },
1283
126
  /** Raised when the session is live, but has no browser open to drive. */
1284
127
  NO_BROWSER_ATTACHED: {
1285
- description: `The ${exports$1.TAP_TARGET} is running, but no test browser is open.`,
128
+ description: `The ${exports.TAP_TARGET} is running, but no test browser is open.`,
1286
129
  solution: 'Open a Chromium-based browser in Cypress, then try again.',
1287
130
  },
1288
131
  /** Raised when every live session has a browser open that tap cannot drive. */
1289
132
  UNSUPPORTED_BROWSER: {
1290
- description: `The ${exports$1.TAP_TARGET} is running an unsupported browser.`,
133
+ description: `The ${exports.TAP_TARGET} is running an unsupported browser.`,
1291
134
  solution: '`cypress tap` drives Chromium-based browsers only. Reopen Cypress in a supported browser and try again.',
1292
135
  },
1293
136
  /** Raised when a CDP call went unanswered until the timeout elapsed. */
1294
137
  RENDERER_UNRESPONSIVE: {
1295
- description: `The ${exports$1.TAP_TARGET} is reachable, but the page running it is not responding.`,
138
+ description: `The ${exports.TAP_TARGET} is reachable, but the page running it is not responding.`,
1296
139
  solution: 'It may be paused in DevTools, stuck in a loop, or starved of memory. Pass `--timeout <ms>` to wait longer.',
1297
140
  },
1298
141
  // Connecting to it
@@ -1324,12 +167,12 @@ function requireTapErrors () {
1324
167
  },
1325
168
  /** Raised when the handshake reported a schema version newer than this CLI's. */
1326
169
  CLI_OUTDATED: {
1327
- description: `The targeted ${exports$1.TAP_TARGET} is newer than this CLI.`,
170
+ description: `The targeted ${exports.TAP_TARGET} is newer than this CLI.`,
1328
171
  solution: `Update the CLI with ${UPDATE_COMMAND}, then try again.`,
1329
172
  },
1330
- /** Raised when the handshake reported an older schema version, or GraphQL redirected. */
173
+ /** Raised when the handshake reported an older schema version, or the session does not serve the tap GraphQL route. */
1331
174
  SESSION_OUTDATED: {
1332
- description: `The targeted ${exports$1.TAP_TARGET} is older than this CLI.`,
175
+ description: `The targeted ${exports.TAP_TARGET} is older than this CLI.`,
1333
176
  solution: `Update Cypress in the running project with ${UPDATE_COMMAND}, restart it, then try again.`,
1334
177
  },
1335
178
  // Reading its data
@@ -1356,17 +199,17 @@ function requireTapErrors () {
1356
199
  },
1357
200
  /** Raised when the runSpec mutation failed, or answered with no result. */
1358
201
  SPEC_START_FAILED: {
1359
- description: `The ${exports$1.TAP_TARGET} could not start the spec.`,
1360
- solution: `Check the ${exports$1.TAP_TARGET} with \`cypress tap status\`, then try again.`,
202
+ description: `The ${exports.TAP_TARGET} could not start the spec.`,
203
+ solution: `Check the ${exports.TAP_TARGET} with \`cypress tap status\`, then try again.`,
1361
204
  },
1362
205
  /** Raised when the given path matches no spec the session can run. */
1363
206
  SPEC_NOT_FOUND: {
1364
- description: `The ${exports$1.TAP_TARGET} has no spec matching that path.`,
1365
- solution: `\`cypress tap specs\` lists the specs the ${exports$1.TAP_TARGET} can run. If the spec exists but is not listed, widen \`specPattern\` in the Cypress configuration.`,
207
+ description: `The ${exports.TAP_TARGET} has no spec matching that path.`,
208
+ solution: `\`cypress tap specs\` lists the specs the ${exports.TAP_TARGET} can run. If the spec exists but is not listed, widen \`specPattern\` in the Cypress configuration.`,
1366
209
  },
1367
210
  /** Raised when the session is running with no project open. */
1368
211
  NO_PROJECT: {
1369
- description: `The ${exports$1.TAP_TARGET} has no project open.`,
212
+ description: `The ${exports.TAP_TARGET} has no project open.`,
1370
213
  solution: 'Open a project in Cypress, then try again.',
1371
214
  },
1372
215
  /** Raised when the spec's testing type is not one this project configures. */
@@ -1378,7 +221,7 @@ function requireTapErrors () {
1378
221
  // Reading the app under test
1379
222
  /** Raised when the runner page holds no app-under-test frame to read. */
1380
223
  NO_AUT: {
1381
- description: `Failed to determine the app under test in the ${exports$1.TAP_TARGET}.`,
224
+ description: `Failed to determine the app under test in the ${exports.TAP_TARGET}.`,
1382
225
  solution: 'Run the spec again with `cypress tap run <spec>`. To read the app as it was at an earlier command, pin that command with `cypress tap pin`.',
1383
226
  },
1384
227
  /** Raised when an injected script threw inside the AUT frame. */
@@ -1490,19 +333,19 @@ function requireTapErrors () {
1490
333
  // An unknown code is a protocol mismatch by definition: the session speaks of a
1491
334
  // failure this CLI has no copy for, which is one more thing about its answer that
1492
335
  // cannot be read.
1493
- const FALLBACK = exports$1.TAP_ERROR_COPY.PROTOCOL_MISMATCH;
336
+ const FALLBACK = exports.TAP_ERROR_COPY.PROTOCOL_MISMATCH;
1494
337
  /**
1495
338
  * Copy for a code, which arrives from the session over the wire — so anything that
1496
339
  * is not a code we ship falls back rather than being trusted: a non-string, an
1497
340
  * inherited name like `constructor`, or a code only a newer Cypress knows about.
1498
341
  */
1499
342
  const tapErrorCopy = (code) => {
1500
- if (typeof code !== 'string' || !Object.prototype.hasOwnProperty.call(exports$1.TAP_ERROR_COPY, code)) {
343
+ if (typeof code !== 'string' || !Object.prototype.hasOwnProperty.call(exports.TAP_ERROR_COPY, code)) {
1501
344
  return FALLBACK;
1502
345
  }
1503
- return exports$1.TAP_ERROR_COPY[code];
346
+ return exports.TAP_ERROR_COPY[code];
1504
347
  };
1505
- exports$1.tapErrorCopy = tapErrorCopy;
348
+ exports.tapErrorCopy = tapErrorCopy;
1506
349
  /**
1507
350
  * The one error every tap failure is raised as, on both sides of the wire: the app
1508
351
  * throws it from a command handler, the CLI throws it from discovery, transport, and
@@ -1540,11 +383,11 @@ function requireTapErrors () {
1540
383
  return { code: this.code, ...(this.detail !== undefined ? { detail: this.detail } : {}) };
1541
384
  }
1542
385
  }
1543
- exports$1.TapError = TapError;
386
+ exports.TapError = TapError;
1544
387
  const isTapError = (err) => {
1545
388
  return err instanceof TapError;
1546
389
  };
1547
- exports$1.isTapError = isTapError;
390
+ exports.isTapError = isTapError;
1548
391
  // The one door through the constructor's guard, so that every code it keeps out is
1549
392
  // still built the same way — with the copy its class below writes.
1550
393
  class DetailedTapError extends TapError {
@@ -1562,7 +405,7 @@ function requireTapErrors () {
1562
405
  super('INVALID_VALUE', `Expected \`${name}\` to be ${expected}.\n\nInstead the value was: ${JSON.stringify(value)}`);
1563
406
  }
1564
407
  }
1565
- exports$1.InvalidValueTapError = InvalidValueTapError;
408
+ exports.InvalidValueTapError = InvalidValueTapError;
1566
409
  /**
1567
410
  * A value that was read fine but matched nothing there is. Each entry states what
1568
411
  * was being looked for and where the real ones are listed; this writes the one line
@@ -1581,13 +424,13 @@ function requireTapErrors () {
1581
424
  super('SESSION_NOT_FOUND', '--session', value);
1582
425
  }
1583
426
  }
1584
- exports$1.SessionNotFoundTapError = SessionNotFoundTapError;
427
+ exports.SessionNotFoundTapError = SessionNotFoundTapError;
1585
428
  class TestNotFoundTapError extends NotFoundTapError {
1586
429
  constructor(value) {
1587
430
  super('TEST_NOT_FOUND', '--test-id', value);
1588
431
  }
1589
432
  }
1590
- exports$1.TestNotFoundTapError = TestNotFoundTapError;
433
+ exports.TestNotFoundTapError = TestNotFoundTapError;
1591
434
  /**
1592
435
  * The one lookup whose remedy names a value the caller gave: the test whose commands
1593
436
  * to list. Which is why the test id is required rather than optional — the table
@@ -1601,19 +444,19 @@ function requireTapErrors () {
1601
444
  });
1602
445
  }
1603
446
  }
1604
- exports$1.CommandNotFoundTapError = CommandNotFoundTapError;
447
+ exports.CommandNotFoundTapError = CommandNotFoundTapError;
1605
448
  class AttemptNotFoundTapError extends NotFoundTapError {
1606
449
  constructor(value, context) {
1607
450
  super('ATTEMPT_NOT_FOUND', '--attempt', value, { context });
1608
451
  }
1609
452
  }
1610
- exports$1.AttemptNotFoundTapError = AttemptNotFoundTapError;
453
+ exports.AttemptNotFoundTapError = AttemptNotFoundTapError;
1611
454
  class SnapshotNotFoundTapError extends NotFoundTapError {
1612
455
  constructor(value, context) {
1613
456
  super('SNAPSHOT_NOT_FOUND', '--at', value, { context });
1614
457
  }
1615
458
  }
1616
- exports$1.SnapshotNotFoundTapError = SnapshotNotFoundTapError;
459
+ exports.SnapshotNotFoundTapError = SnapshotNotFoundTapError;
1617
460
  /**
1618
461
  * A flag that only means something alongside another one. Both are named here;
1619
462
  * `remedy` is the throw site's, because what dropping either one leaves you with
@@ -1624,7 +467,7 @@ function requireTapErrors () {
1624
467
  super('MISSING_COMPANION_OPTION', `You passed the \`${given}\` flag without also passing the \`${required}\` flag.\n\n${remedy}`);
1625
468
  }
1626
469
  }
1627
- exports$1.MissingCompanionOptionTapError = MissingCompanionOptionTapError;
470
+ exports.MissingCompanionOptionTapError = MissingCompanionOptionTapError;
1628
471
  /**
1629
472
  * A name no command answers to, and a flag no command declares: say which one was
1630
473
  * given, then list the real ones. The listing is the remedy, which is why neither
@@ -1637,13 +480,13 @@ function requireTapErrors () {
1637
480
  super('UNKNOWN_COMMAND', listing ? `Unknown command "${name}"\n\n${listing}` : `Unknown command "${name}"`);
1638
481
  }
1639
482
  }
1640
- exports$1.UnknownCommandTapError = UnknownCommandTapError;
483
+ exports.UnknownCommandTapError = UnknownCommandTapError;
1641
484
  class UnknownOptionTapError extends DetailedTapError {
1642
485
  constructor(flag, listing) {
1643
486
  super('UNKNOWN_OPTION', listing ? `Unknown option "${flag}"\n\n${listing}` : `Unknown option "${flag}"`);
1644
487
  }
1645
488
  }
1646
- exports$1.UnknownOptionTapError = UnknownOptionTapError;
489
+ exports.UnknownOptionTapError = UnknownOptionTapError;
1647
490
  /**
1648
491
  * A required input the invocation left out. Both sides raise these through here —
1649
492
  * the CLI when its own grammar catches the omission, the session when a call
@@ -1659,13 +502,13 @@ function requireTapErrors () {
1659
502
  super('INVALID_ARGUMENTS', { detail: `"${command}" is missing the required ${named} ${noun}.` });
1660
503
  }
1661
504
  }
1662
- exports$1.MissingArgumentsTapError = MissingArgumentsTapError;
505
+ exports.MissingArgumentsTapError = MissingArgumentsTapError;
1663
506
  class MissingOptionTapError extends TapError {
1664
507
  constructor(command, option) {
1665
508
  super('INVALID_OPTIONS', { detail: `"${command}" is missing the required --${option} option.` });
1666
509
  }
1667
510
  }
1668
- exports$1.MissingOptionTapError = MissingOptionTapError;
511
+ exports.MissingOptionTapError = MissingOptionTapError;
1669
512
  /**
1670
513
  * A CLI and a session that do not speak the same tap schema. Which of the two is
1671
514
  * behind is decided here rather than at the throw site, so the code and the copy
@@ -1680,12 +523,12 @@ function requireTapErrors () {
1680
523
  class VersionSkewTapError extends TapError {
1681
524
  constructor({ sessionSchema, cliSchema, sessionCypress, cliCypress }) {
1682
525
  super(sessionSchema > cliSchema ? 'CLI_OUTDATED' : 'SESSION_OUTDATED', {
1683
- detail: `The ${exports$1.TAP_TARGET} is running Cypress v${sessionCypress}; this CLI is v${cliCypress}.`,
1684
- message: `the ${exports$1.TAP_TARGET} speaks tap schema v${sessionSchema}; this CLI speaks v${cliSchema}.`,
526
+ detail: `The ${exports.TAP_TARGET} is running Cypress v${sessionCypress}; this CLI is v${cliCypress}.`,
527
+ message: `the ${exports.TAP_TARGET} speaks tap schema v${sessionSchema}; this CLI speaks v${cliSchema}.`,
1685
528
  });
1686
529
  }
1687
530
  }
1688
- exports$1.VersionSkewTapError = VersionSkewTapError;
531
+ exports.VersionSkewTapError = VersionSkewTapError;
1689
532
  /**
1690
533
  * The spec that is mid-run, which is what makes the condition actionable: it names
1691
534
  * what to wait on. Both sides raise it through here — the CLI from its run-state
@@ -1697,7 +540,7 @@ function requireTapErrors () {
1697
540
  super('SPEC_IN_PROGRESS', spec ? `The spec ${spec} is currently running.` : 'The spec is currently running.');
1698
541
  }
1699
542
  }
1700
- exports$1.SpecInProgressTapError = SpecInProgressTapError;
543
+ exports.SpecInProgressTapError = SpecInProgressTapError;
1701
544
  /**
1702
545
  * Whatever reached the renderer without having been raised as a tap failure — a
1703
546
  * TypeError from a command handler, an ENOTDIR from a read that should not have
@@ -1711,7 +554,7 @@ function requireTapErrors () {
1711
554
  super('UNKNOWN_ERROR', { message: String((_a = cause === null || cause === void 0 ? void 0 : cause.stack) !== null && _a !== void 0 ? _a : cause), cause });
1712
555
  }
1713
556
  }
1714
- exports$1.UnknownTapError = UnknownTapError;
557
+ exports.UnknownTapError = UnknownTapError;
1715
558
 
1716
559
  } (tapErrors));
1717
560
  return tapErrors;
@@ -1763,45 +606,392 @@ function requirePinned () {
1763
606
 
1764
607
  var pin = {};
1765
608
 
1766
- var hasRequiredPin;
609
+ var hasRequiredPin;
610
+
611
+ function requirePin () {
612
+ if (hasRequiredPin) return pin;
613
+ hasRequiredPin = 1;
614
+ Object.defineProperty(pin, "__esModule", { value: true });
615
+
616
+ return pin;
617
+ }
618
+
619
+ var resolveSelector = {};
620
+
621
+ var hasRequiredResolveSelector;
622
+
623
+ function requireResolveSelector () {
624
+ if (hasRequiredResolveSelector) return resolveSelector;
625
+ hasRequiredResolveSelector = 1;
626
+ // The `resolve-selector` command's result contract. A command's result interface
627
+ // lives here in `contracts/`, next to the command metadata in `../tap-contract`,
628
+ // so the app-side command and the CLI-side rendering type against the same shape.
629
+ Object.defineProperty(resolveSelector, "__esModule", { value: true });
630
+ resolveSelector.MAX_DERIVED_SELECTORS = void 0;
631
+ // Deriving a unique selector walks up from the element testing each candidate
632
+ // against the whole document, so deriving one per match for a selector as broad
633
+ // as `*` would hold the app's main thread for the size of the page. Past this
634
+ // many matches the list is no longer one a caller picks out of anyway — so the
635
+ // app derives up to here, and the CLI numbers up to here.
636
+ resolveSelector.MAX_DERIVED_SELECTORS = 10;
637
+
638
+ return resolveSelector;
639
+ }
640
+
641
+ var hasRequiredTapContract;
642
+
643
+ function requireTapContract () {
644
+ if (hasRequiredTapContract) return tapContract;
645
+ hasRequiredTapContract = 1;
646
+ (function (exports) {
647
+ var __createBinding = (tapContract && tapContract.__createBinding) || (Object.create ? (function(o, m, k, k2) {
648
+ if (k2 === undefined) k2 = k;
649
+ var desc = Object.getOwnPropertyDescriptor(m, k);
650
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
651
+ desc = { enumerable: true, get: function() { return m[k]; } };
652
+ }
653
+ Object.defineProperty(o, k2, desc);
654
+ }) : (function(o, m, k, k2) {
655
+ if (k2 === undefined) k2 = k;
656
+ o[k2] = m[k];
657
+ }));
658
+ var __exportStar = (tapContract && tapContract.__exportStar) || function(m, exports) {
659
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
660
+ };
661
+ Object.defineProperty(exports, "__esModule", { value: true });
662
+ exports.KNOWN_COMMANDS = exports.TAP_NATIVE_COMMANDS = exports.buildTapSchema = exports.TAP_COMMANDS = exports.TAP_EXEC_METHOD = exports.TAP_SCHEMA_METHOD = exports.TAP_BINDING_GLOBAL = exports.TAP_SCHEMA_VERSION = void 0;
663
+ exports.TAP_SCHEMA_VERSION = 1;
664
+ exports.TAP_BINDING_GLOBAL = '__CYPRESS_TAP_BINDING__';
665
+ exports.TAP_SCHEMA_METHOD = 'getSchema';
666
+ exports.TAP_EXEC_METHOD = 'exec';
667
+ // Options that recur across commands, defined once so their name, type, and help
668
+ // text can't drift between the commands that expose them. `test-id` and
669
+ // `command-id` are required in some commands and optional in others, so each use
670
+ // spreads it and sets `required`; the rest are identical everywhere and used
671
+ // directly.
672
+ const testIdField = { name: 'test-id', alias: 't', type: 'string', description: 'test id, as listed by the reporter command' };
673
+ const commandIdField = { name: 'command-id', alias: 'c', type: 'string', description: 'command id, as listed by the reporter command — a row number (test body first when duplicated), an e-prefixed event id, or hook-qualified like "h1:3"' };
674
+ const attemptField = { name: 'attempt', alias: 'a', type: 'number', required: false, description: '1-based attempt (attempt 1 = first run); defaults to the latest' };
675
+ const selectorField = { name: 'selector', alias: 'e', type: 'string', required: false, description: 'a CSS selector' };
676
+ const commandMeta = {
677
+ name: 'command',
678
+ description: 'retrieve the reporter row, the pinnable DOM snapshots, and the console properties of a single command in a test',
679
+ details: `Retrieves a single command from a test's command log: its reporter row, the DOM
680
+ snapshots pinnable on it, and any associated console properties.`,
681
+ params: [],
682
+ options: [
683
+ { ...testIdField, required: true },
684
+ { ...commandIdField, required: true },
685
+ // `--json` is the CLI's own flag, but this command declares it because it
686
+ // also changes what the command returns: nothing is withheld from a payload
687
+ // that is not being rendered for reading room.
688
+ { name: 'json', type: 'boolean', required: false, description: 'print the raw JSON result instead of the human-readable rendering. This will output every console property in full regardless of length.' },
689
+ attemptField,
690
+ { name: 'depth', alias: 'd', type: 'string', required: false, description: 'how many levels of nested console properties to expand before summarizing the rest as "{n keys}" / "[n items]": a number or "all" (default 3, and a section over 8 rows folds at any depth unless this is passed)' },
691
+ ],
692
+ };
693
+ const reporterMeta = {
694
+ name: 'reporter',
695
+ description: 'retrieve the test runner reporter for the spec or a specific test',
696
+ details: `Shows test results the way the Cypress reporter panel does.
697
+
698
+ By default, this will output the spec-level overview featuring the spec's
699
+ pass/fail stats and every suite's tests including their IDs.
700
+
701
+ Provide a --test-id <id> to see one test's full story: its routes, the hooks
702
+ that ran, the complete command log, and the failure output when something went
703
+ wrong. Add --attempt to view an earlier retry.`,
704
+ params: [],
705
+ options: [
706
+ { ...testIdField, required: false },
707
+ attemptField,
708
+ ],
709
+ };
710
+ const pinMeta = {
711
+ name: 'pin',
712
+ description: 'pin a command’s DOM snapshot into the live application-under-test frame',
713
+ details: `Pins a command's DOM snapshot into the live application-under-test frame so the
714
+ dom/aria/inspect commands can read it. Pass --clear to release.`,
715
+ params: [],
716
+ options: [
717
+ { ...testIdField, required: false },
718
+ { ...commandIdField, required: false },
719
+ attemptField,
720
+ { name: 'at', type: 'string', required: false, description: 'which snapshot to pin: a name like "before"/"after" or a 1-based index; defaults to the last (the command’s final state). Re-run on the pinned command to switch snapshots without releasing the pin' },
721
+ { name: 'clear', type: 'boolean', required: false, description: 'release the current pin and restore the app to its pre-pin state' },
722
+ ],
723
+ };
724
+ const runStateMeta = {
725
+ name: 'run-state',
726
+ description: 'report where the Cypress session is in its run lifecycle',
727
+ params: [],
728
+ options: [],
729
+ hidden: true,
730
+ };
731
+ const resolveSelectorMeta = {
732
+ name: 'resolve-selector',
733
+ description: 'list a unique CSS selector for each element a selector matches, null for any match none could be derived for',
734
+ params: [
735
+ { name: 'selector', type: 'string', required: true, description: 'a CSS selector' },
736
+ ],
737
+ options: [],
738
+ hidden: true,
739
+ };
740
+ // The canonical command metadata, the single source both sides build a schema
741
+ // from: the running session advertises it over the binding (see the app's
742
+ // TapManager), and the CLI stamps it with its own version to render help with no
743
+ // session attached. Order here is the order commands list in help.
744
+ exports.TAP_COMMANDS = [
745
+ commandMeta,
746
+ reporterMeta,
747
+ pinMeta,
748
+ runStateMeta,
749
+ resolveSelectorMeta,
750
+ ];
751
+ const buildTapSchema = (cypressVersion) => {
752
+ const commands = exports.TAP_COMMANDS.map((command) => {
753
+ return {
754
+ name: command.name,
755
+ description: command.description,
756
+ ...('details' in command ? { details: command.details } : {}),
757
+ params: command.params.map((param) => ({ ...param })),
758
+ options: command.options.map((option) => ({ ...option })),
759
+ ...('hidden' in command && command.hidden ? { hidden: true } : {}),
760
+ };
761
+ });
762
+ return {
763
+ schemaVersion: exports.TAP_SCHEMA_VERSION,
764
+ cypressVersion,
765
+ commands,
766
+ };
767
+ };
768
+ exports.buildTapSchema = buildTapSchema;
769
+ const runMeta = {
770
+ name: 'run',
771
+ description: 'run (or rerun) a spec by its project-relative path',
772
+ details: `Runs (or reruns) a spec by its project-relative path, as listed by the specs
773
+ command. If no browser is open it launches one, switching to the spec's testing
774
+ type when needed, then requests the spec and returns immediately. This requests
775
+ the spec to begin, but completion of this command does not guarantee the spec
776
+ has started or finished.
777
+
778
+ Poll the 'status' command for progress. Read status first and keep its
779
+ startedAt: a verdict still carrying that same startedAt describes the spec
780
+ before this one, so wait for a verdict whose startedAt differs.`,
781
+ params: [
782
+ { name: 'spec', type: 'string', required: true, description: 'project-relative spec path, as listed by the specs command' },
783
+ ],
784
+ };
785
+ const sessionsMeta = {
786
+ name: 'sessions',
787
+ description: 'list the Cypress sessions this CLI can reach',
788
+ details: `Lists the Cypress sessions this CLI can reach. Cypress must be running locally,
789
+ support the \`tap\` command, and have an active Testing Type to be listed. A
790
+ session can be targeted by its PID in other commands using \`--session\`.
791
+
792
+ tap only supports Chromium based browsers (Chrome, Chromium, Edge, Electron).
793
+ A session running any other browser is listed as unsupported, and every other
794
+ tap command refuses it.`,
795
+ };
796
+ const statusMeta = {
797
+ name: 'status',
798
+ description: 'report where a Cypress session is in its lifecycle',
799
+ details: `Reports where a Cypress session is in its lifecycle, as JSON — for polling and
800
+ "where am I?" checks. Always exits 0 for a determinable stage (including "not
801
+ connected"); a poller branches on the \`status\` field.
802
+
803
+ Stages:
804
+ - not connected: Cypress cannot be reached.
805
+ - browser not selected: Cypress does not have a supported browser open.
806
+ - spec not selected: Cypress has a browser open but no spec is selected.
807
+ - loading: Cypress is loading the spec.
808
+ - running: Cypress is running the spec.
809
+ - passed: Cypress has completed the spec and all executed tests passed.
810
+ - failed: Cypress failed to run the spec, or has completed the spec and at least
811
+ one test failed.
812
+
813
+ Statuses other than passed and failed are intermediate states, indicating that
814
+ Cypress requires additional instruction or is still processing. Loading stays
815
+ loading for as long as the spec takes to build, so a poller needs its own
816
+ timeout; a spec whose build fails reports failed and carries the reason as
817
+ error — it ran no tests, so it reports no counts either.
818
+
819
+ From loading onwards the output carries \`startedAt\`, the spec every other field
820
+ describes (null while loading). In the event of a rerun the previous spec's
821
+ verdict remains visible until the new one begins, identical on every other
822
+ field, so use \`startedAt\` to validate whether output describes the previous
823
+ spec or the new one.`,
824
+ };
825
+ const specsMeta = {
826
+ name: 'specs',
827
+ description: 'list the specs the Cypress session can run, most recently modified first',
828
+ details: `Lists the specs the connected Cypress session can run, in descending order by
829
+ last modified. To find other testing types you must connect to a Cypress
830
+ session with that testing type active.`,
831
+ };
832
+ // Every selector these three take must resolve to exactly one element, so a
833
+ // reader is never silently shown one of several matches. Their help says so in
834
+ // the same words, and names the remedy the ambiguity error offers.
835
+ const SINGLE_ELEMENT_SELECTOR = 'a CSS selector matching exactly one element';
836
+ const AMBIGUOUS_SELECTOR_HELP = `The selector must match exactly one element. If it matches multiple elements
837
+ nothing is read: the command answers with a numbered list of the matches (up to
838
+ 10), each with a unique selector. Re-run with --at <index> to read one of them,
839
+ or with whichever selector you meant.`;
840
+ // Where `dom` and `aria` read from with no selector: the document element only
841
+ // adds a <head> of script and style text; `--selector html` still reads it.
842
+ const TAP_DEFAULT_SELECTOR = 'body';
843
+ // Shared by the three selector-taking reads, so the way you pick one match out
844
+ // of several is identical across them.
845
+ const atField = {
846
+ name: 'at',
847
+ type: 'number',
848
+ required: false,
849
+ description: 'When a selector matches multiple elements, the 0-based index of the match to read',
850
+ };
851
+ const domMeta = {
852
+ name: 'dom',
853
+ description: 'read the application-under-test DOM as HTML: the page body, or a single element with its children by providing a CSS Selector',
854
+ details: `Reads the application-under-test DOM as HTML: a single element matching a CSS
855
+ Selector, and all of its children. Without --selector it reads the page body;
856
+ pass --selector html for the entire document. Output is capped browser-side so a
857
+ heavy page never ships megabytes at once.
858
+
859
+ ${AMBIGUOUS_SELECTOR_HELP}`,
860
+ params: [],
861
+ options: [
862
+ { ...selectorField, defaultValue: TAP_DEFAULT_SELECTOR, description: SINGLE_ELEMENT_SELECTOR },
863
+ { name: 'max-chars', alias: 'm', type: 'number', required: false, defaultValue: 30000, description: 'cap on returned HTML characters' },
864
+ atField,
865
+ ],
866
+ };
867
+ const ariaMeta = {
868
+ name: 'aria',
869
+ description: 'read the accessibility (ARIA) tree of the application-under-test page body, or the subtree at a selector',
870
+ details: `Reads the accessibility (ARIA) tree of the application under test, rooted at the
871
+ page body or at the element a CSS Selector matches. Structural and text-only
872
+ roles are dropped, leaving the compact role/name/state tree DevTools shows.
873
+
874
+ ${AMBIGUOUS_SELECTOR_HELP}`,
875
+ params: [],
876
+ options: [
877
+ { ...selectorField, defaultValue: TAP_DEFAULT_SELECTOR, description: SINGLE_ELEMENT_SELECTOR },
878
+ // The accessibility tree of a real app is deep; the cap keeps the projection
879
+ // affordable for an LLM. A selector roots it at a subtree for finer reads.
880
+ { name: 'max-nodes', alias: 'm', type: 'number', required: false, defaultValue: 200, description: 'cap on the number of accessibility nodes returned' },
881
+ atField,
882
+ ],
883
+ };
884
+ const inspectMeta = {
885
+ name: 'inspect',
886
+ description: 'retrieve the tag, attributes, computed styles, box model, and accessibility node of a specific element',
887
+ details: `Inspects a single element matching a CSS Selector: outputs the tag, attributes,
888
+ curated computed styles, box model, and that element's own accessibility node
889
+ — where \`aria\` reads the whole tree, this reads the one node.
1767
890
 
1768
- function requirePin () {
1769
- if (hasRequiredPin) return pin;
1770
- hasRequiredPin = 1;
1771
- Object.defineProperty(pin, "__esModule", { value: true });
1772
-
1773
- return pin;
891
+ ${AMBIGUOUS_SELECTOR_HELP}`,
892
+ params: [],
893
+ options: [
894
+ { ...selectorField, required: true, description: `${SINGLE_ELEMENT_SELECTOR}, identifying the element to inspect` },
895
+ atField,
896
+ ],
897
+ };
898
+ // CLI-native tap commands: implemented entirely in the CLI (session discovery
899
+ // over the filesystem, DOM/ARIA reads over CDP), so — unlike TAP_COMMANDS — they
900
+ // are never advertised by getSchema or exec'd on the session. Listed here in the
901
+ // order they appear in help, ahead of the schema-driven commands.
902
+ exports.TAP_NATIVE_COMMANDS = [
903
+ sessionsMeta,
904
+ statusMeta,
905
+ specsMeta,
906
+ runMeta,
907
+ domMeta,
908
+ ariaMeta,
909
+ inspectMeta,
910
+ ];
911
+ const allTapCommands = [...exports.TAP_NATIVE_COMMANDS, ...exports.TAP_COMMANDS];
912
+ /** Every command name this CLI ships, whether it dispatches to the session or handles it itself. */
913
+ exports.KNOWN_COMMANDS = new Set(allTapCommands.map(({ name }) => name));
914
+ // The failure catalogue and the error both sides raise. Re-exported here for the
915
+ // same reason the result contracts are: this module is dependency-free, so the
916
+ // browser-side app can import it where it cannot import the package barrel.
917
+ __exportStar(requireTapErrors(), exports);
918
+ // Per-command result contracts live in `./contracts/`; re-exported here so the
919
+ // app's deep import of this module and the package barrel both reach them.
920
+ __exportStar(requireReporter(), exports);
921
+ __exportStar(requireCommand(), exports);
922
+ __exportStar(requirePinned(), exports);
923
+ __exportStar(requirePin(), exports);
924
+ __exportStar(requireResolveSelector(), exports);
925
+
926
+ } (tapContract));
927
+ return tapContract;
1774
928
  }
1775
929
 
1776
- var resolveSelector = {};
930
+ var tapOperations = {};
1777
931
 
1778
- var hasRequiredResolveSelector;
932
+ var hasRequiredTapOperations;
1779
933
 
1780
- function requireResolveSelector () {
1781
- if (hasRequiredResolveSelector) return resolveSelector;
1782
- hasRequiredResolveSelector = 1;
1783
- // The `resolve-selector` command's result contract. A command's result interface
1784
- // lives here in `contracts/`, next to the command metadata in `../tap-contract`,
1785
- // so the app-side command and the CLI-side rendering type against the same shape.
1786
- Object.defineProperty(resolveSelector, "__esModule", { value: true });
1787
- resolveSelector.MAX_DERIVED_SELECTORS = void 0;
1788
- // Deriving a unique selector walks up from the element testing each candidate
1789
- // against the whole document, so deriving one per match for a selector as broad
1790
- // as `*` would hold the app's main thread for the size of the page. Past this
1791
- // many matches the list is no longer one a caller picks out of anyway — so the
1792
- // app derives up to here, and the CLI numbers up to here.
1793
- resolveSelector.MAX_DERIVED_SELECTORS = 10;
934
+ function requireTapOperations () {
935
+ if (hasRequiredTapOperations) return tapOperations;
936
+ hasRequiredTapOperations = 1;
937
+ Object.defineProperty(tapOperations, "__esModule", { value: true });
938
+ tapOperations.tapRunSpecOperation = tapOperations.TapSpecsOperation = void 0;
939
+ tapOperations.TapSpecsOperation = {
940
+ operationName: 'TapSpecs',
941
+ query: /* GraphQL */ `
942
+ query TapSpecs {
943
+ currentProject {
944
+ specs {
945
+ relative
946
+ absolute
947
+ gitInfo {
948
+ lastModifiedHumanReadable
949
+ lastModifiedTimestamp
950
+ }
951
+ }
952
+ }
953
+ }
954
+ `,
955
+ };
956
+ const TAP_RUN_SPEC_QUERY = /* GraphQL */ `
957
+ mutation TapRunSpec($specPath: String!) {
958
+ runSpec(specPath: $specPath) {
959
+ __typename
960
+ ... on RunSpecResponse {
961
+ testingType
962
+ browser {
963
+ displayName
964
+ }
965
+ spec {
966
+ relative
967
+ }
968
+ }
969
+ ... on RunSpecError {
970
+ code
971
+ detailMessage
972
+ }
973
+ }
974
+ }
975
+ `;
976
+ const tapRunSpecOperation = (specPath) => {
977
+ return {
978
+ operationName: 'TapRunSpec',
979
+ query: TAP_RUN_SPEC_QUERY,
980
+ variables: { specPath },
981
+ };
982
+ };
983
+ tapOperations.tapRunSpecOperation = tapRunSpecOperation;
1794
984
 
1795
- return resolveSelector;
985
+ return tapOperations;
1796
986
  }
1797
987
 
1798
- var hasRequiredTapContract;
988
+ var hasRequiredDist;
1799
989
 
1800
- function requireTapContract () {
1801
- if (hasRequiredTapContract) return tapContract;
1802
- hasRequiredTapContract = 1;
1803
- (function (exports$1) {
1804
- var __createBinding = (tapContract && tapContract.__createBinding) || (Object.create ? (function(o, m, k, k2) {
990
+ function requireDist () {
991
+ if (hasRequiredDist) return dist;
992
+ hasRequiredDist = 1;
993
+ (function (exports) {
994
+ var __createBinding = (dist && dist.__createBinding) || (Object.create ? (function(o, m, k, k2) {
1805
995
  if (k2 === undefined) k2 = k;
1806
996
  var desc = Object.getOwnPropertyDescriptor(m, k);
1807
997
  if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
@@ -1812,685 +1002,1506 @@ function requireTapContract () {
1812
1002
  if (k2 === undefined) k2 = k;
1813
1003
  o[k2] = m[k];
1814
1004
  }));
1815
- var __exportStar = (tapContract && tapContract.__exportStar) || function(m, exports$1) {
1816
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports$1, p)) __createBinding(exports$1, m, p);
1005
+ var __exportStar = (dist && dist.__exportStar) || function(m, exports) {
1006
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
1817
1007
  };
1818
- Object.defineProperty(exports$1, "__esModule", { value: true });
1819
- exports$1.KNOWN_COMMANDS = exports$1.TAP_NATIVE_COMMANDS = exports$1.buildTapSchema = exports$1.TAP_COMMANDS = exports$1.TAP_EXEC_METHOD = exports$1.TAP_SCHEMA_METHOD = exports$1.TAP_BINDING_GLOBAL = exports$1.TAP_SCHEMA_VERSION = void 0;
1820
- exports$1.TAP_SCHEMA_VERSION = 1;
1821
- exports$1.TAP_BINDING_GLOBAL = '__CYPRESS_TAP_BINDING__';
1822
- exports$1.TAP_SCHEMA_METHOD = 'getSchema';
1823
- exports$1.TAP_EXEC_METHOD = 'exec';
1824
- // Options that recur across commands, defined once so their name, type, and help
1825
- // text can't drift between the commands that expose them. `test-id` and
1826
- // `command-id` are required in some commands and optional in others, so each use
1827
- // spreads it and sets `required`; the rest are identical everywhere and used
1828
- // directly.
1829
- const testIdField = { name: 'test-id', alias: 't', type: 'string', description: 'test id, as listed by the reporter command' };
1830
- const commandIdField = { name: 'command-id', alias: 'c', type: 'string', description: 'command id, as listed by the reporter command — a row number (test body first when duplicated), an e-prefixed event id, or hook-qualified like "h1:3"' };
1831
- const attemptField = { name: 'attempt', alias: 'a', type: 'number', required: false, description: '1-based attempt (attempt 1 = first run); defaults to the latest' };
1832
- const selectorField = { name: 'selector', alias: 'e', type: 'string', required: false, description: 'a CSS selector' };
1833
- const commandMeta = {
1834
- name: 'command',
1835
- description: 'retrieve the reporter row, the pinnable DOM snapshots, and the console properties of a single command in a test',
1836
- details: `Retrieves a single command from a test's command log: its reporter row, the DOM
1837
- snapshots pinnable on it, and any associated console properties.`,
1838
- params: [],
1839
- options: [
1840
- { ...testIdField, required: true },
1841
- { ...commandIdField, required: true },
1842
- // `--json` is the CLI's own flag, but this command declares it because it
1843
- // also changes what the command returns: nothing is withheld from a payload
1844
- // that is not being rendered for reading room.
1845
- { name: 'json', type: 'boolean', required: false, description: 'print the raw JSON result instead of the human-readable rendering. This will output every console property in full regardless of length.' },
1846
- attemptField,
1847
- { name: 'depth', alias: 'd', type: 'string', required: false, description: 'how many levels of nested console properties to expand before summarizing the rest as "{n keys}" / "[n items]": a number or "all" (default 3, and a section over 8 rows folds at any depth unless this is passed)' },
1848
- ],
1008
+ var __importDefault = (dist && dist.__importDefault) || function (mod) {
1009
+ return (mod && mod.__esModule) ? mod : { "default": mod };
1849
1010
  };
1850
- const reporterMeta = {
1851
- name: 'reporter',
1852
- description: 'retrieve the test runner reporter for the spec or a specific test',
1853
- details: `Shows test results the way the Cypress reporter panel does.
1854
-
1855
- By default, this will output the spec-level overview featuring the spec's
1856
- pass/fail stats and every suite's tests including their IDs.
1857
-
1858
- Provide a --test-id <id> to see one test's full story: its routes, the hooks
1859
- that ran, the complete command log, and the failure output when something went
1860
- wrong. Add --attempt to view an earlier retry.`,
1861
- params: [],
1862
- options: [
1863
- { ...testIdField, required: false },
1864
- attemptField,
1865
- ],
1011
+ Object.defineProperty(exports, "__esModule", { value: true });
1012
+ exports.isCompatibleRecord = exports.tapGraphqlPath = exports.sessionProbePath = exports.recordPath = exports.cypressSessionsDir = exports.parseRecordPid = exports.recordFileName = exports.isTapSupportedBrowser = exports.TAP_SUPPORTED_BROWSER_FAMILY = exports.LEGACY_TAP_GRAPHQL_PATH = exports.TAP_GRAPHQL_ROUTE_PREFIX = exports.SESSION_ID_HEADER = exports.SESSIONS_ROUTE_PREFIX = exports.SESSIONS_DIRNAME = exports.MIN_SCHEMA_VERSION = exports.SCHEMA_VERSION = void 0;
1013
+ const path_1 = __importDefault(path);
1014
+ __exportStar(requireTapContract(), exports);
1015
+ __exportStar(requireTapOperations(), exports);
1016
+ exports.SCHEMA_VERSION = 1;
1017
+ exports.MIN_SCHEMA_VERSION = 1;
1018
+ exports.SESSIONS_DIRNAME = 'sessions';
1019
+ const RECORD_EXTENSION = '.json';
1020
+ exports.SESSIONS_ROUTE_PREFIX = '/__cypress/sessions/';
1021
+ exports.SESSION_ID_HEADER = 'x-cypress-session-id';
1022
+ // Fixed rather than namespaced, as the sessions route is: the CLI has to find it
1023
+ // knowing only the port from the record.
1024
+ exports.TAP_GRAPHQL_ROUTE_PREFIX = '/__cypress/tap/graphql/';
1025
+ // Where tap GraphQL lived before the fixed route, under the default `namespace`.
1026
+ // The CLI falls back to it to keep tapping sessions that predate the route, and it
1027
+ // can go once those are no longer supported.
1028
+ exports.LEGACY_TAP_GRAPHQL_PATH = '/__cypress/graphql';
1029
+ exports.TAP_SUPPORTED_BROWSER_FAMILY = 'chromium';
1030
+ // tap drives the browser over CDP, which only Chromium-based browsers speak. A
1031
+ // null family means no browser is open yet — nothing to call unsupported.
1032
+ const isTapSupportedBrowser = (browserFamily) => {
1033
+ return browserFamily === null || browserFamily === exports.TAP_SUPPORTED_BROWSER_FAMILY;
1866
1034
  };
1867
- const pinMeta = {
1868
- name: 'pin',
1869
- description: 'pin a command’s DOM snapshot into the live application-under-test frame',
1870
- details: `Pins a command's DOM snapshot into the live application-under-test frame so the
1871
- dom/aria/inspect commands can read it. Pass --clear to release.`,
1872
- params: [],
1873
- options: [
1874
- { ...testIdField, required: false },
1875
- { ...commandIdField, required: false },
1876
- attemptField,
1877
- { name: 'at', type: 'string', required: false, description: 'which snapshot to pin: a name like "before"/"after" or a 1-based index; defaults to the last (the command’s final state). Re-run on the pinned command to switch snapshots without releasing the pin' },
1878
- { name: 'clear', type: 'boolean', required: false, description: 'release the current pin and restore the app to its pre-pin state' },
1879
- ],
1035
+ exports.isTapSupportedBrowser = isTapSupportedBrowser;
1036
+ const recordFileName = (pid) => {
1037
+ return `${pid}${RECORD_EXTENSION}`;
1880
1038
  };
1881
- const runStateMeta = {
1882
- name: 'run-state',
1883
- description: 'report where the Cypress session is in its run lifecycle',
1884
- params: [],
1885
- options: [],
1886
- hidden: true,
1039
+ exports.recordFileName = recordFileName;
1040
+ const parseRecordPid = (entry) => {
1041
+ if (path_1.default.extname(entry) !== RECORD_EXTENSION) {
1042
+ return null;
1043
+ }
1044
+ const pid = Number(path_1.default.basename(entry, RECORD_EXTENSION));
1045
+ return Number.isInteger(pid) ? pid : null;
1887
1046
  };
1888
- const resolveSelectorMeta = {
1889
- name: 'resolve-selector',
1890
- description: 'list a unique CSS selector for each element a selector matches, null for any match none could be derived for',
1891
- params: [
1892
- { name: 'selector', type: 'string', required: true, description: 'a CSS selector' },
1893
- ],
1894
- options: [],
1895
- hidden: true,
1047
+ exports.parseRecordPid = parseRecordPid;
1048
+ const cypressSessionsDir = (cacheRoot) => {
1049
+ return path_1.default.join(cacheRoot, exports.SESSIONS_DIRNAME);
1896
1050
  };
1897
- // The canonical command metadata, the single source both sides build a schema
1898
- // from: the running session advertises it over the binding (see the app's
1899
- // TapManager), and the CLI stamps it with its own version to render help with no
1900
- // session attached. Order here is the order commands list in help.
1901
- exports$1.TAP_COMMANDS = [
1902
- commandMeta,
1903
- reporterMeta,
1904
- pinMeta,
1905
- runStateMeta,
1906
- resolveSelectorMeta,
1907
- ];
1908
- const buildTapSchema = (cypressVersion) => {
1909
- const commands = exports$1.TAP_COMMANDS.map((command) => {
1910
- return {
1911
- name: command.name,
1912
- description: command.description,
1913
- ...('details' in command ? { details: command.details } : {}),
1914
- params: command.params.map((param) => ({ ...param })),
1915
- options: command.options.map((option) => ({ ...option })),
1916
- ...('hidden' in command && command.hidden ? { hidden: true } : {}),
1917
- };
1918
- });
1919
- return {
1920
- schemaVersion: exports$1.TAP_SCHEMA_VERSION,
1921
- cypressVersion,
1922
- commands,
1923
- };
1051
+ exports.cypressSessionsDir = cypressSessionsDir;
1052
+ const recordPath = (cacheRoot, pid) => {
1053
+ return path_1.default.join((0, exports.cypressSessionsDir)(cacheRoot), (0, exports.recordFileName)(pid));
1054
+ };
1055
+ exports.recordPath = recordPath;
1056
+ const sessionProbePath = (sessionId) => {
1057
+ return `${exports.SESSIONS_ROUTE_PREFIX}${sessionId}`;
1058
+ };
1059
+ exports.sessionProbePath = sessionProbePath;
1060
+ const tapGraphqlPath = (operationName) => {
1061
+ return `${exports.TAP_GRAPHQL_ROUTE_PREFIX}${operationName}`;
1062
+ };
1063
+ exports.tapGraphqlPath = tapGraphqlPath;
1064
+ const isValidTestingType = (value) => {
1065
+ return value === 'e2e' || value === 'component' || value === null;
1066
+ };
1067
+ const isCompatibleRecord = (record) => {
1068
+ return Boolean(record)
1069
+ && typeof record.schemaVersion === 'number'
1070
+ && record.schemaVersion >= exports.MIN_SCHEMA_VERSION
1071
+ && typeof record.pid === 'number'
1072
+ && typeof record.projectRoot === 'string'
1073
+ && Number.isInteger(record.serverPort)
1074
+ && typeof record.sessionId === 'string'
1075
+ && record.sessionId.length > 0
1076
+ && isValidTestingType(record.testingType);
1924
1077
  };
1925
- exports$1.buildTapSchema = buildTapSchema;
1926
- const runMeta = {
1927
- name: 'run',
1928
- description: 'run (or rerun) a spec by its project-relative path',
1929
- details: `Runs (or reruns) a spec by its project-relative path, as listed by the specs
1930
- command. If no browser is open it launches one, switching to the spec's testing
1931
- type when needed, then requests the spec and returns immediately. This requests
1932
- the spec to begin, but completion of this command does not guarantee the spec
1933
- has started or finished.
1078
+ exports.isCompatibleRecord = isCompatibleRecord;
1079
+
1080
+ } (dist));
1081
+ return dist;
1082
+ }
1934
1083
 
1935
- Poll the 'status' command for progress. Read status first and keep its
1936
- startedAt: a verdict still carrying that same startedAt describes the spec
1937
- before this one, so wait for a verdict whose startedAt differs.`,
1938
- params: [
1939
- { name: 'spec', type: 'string', required: true, description: 'project-relative spec path, as listed by the specs command' },
1940
- ],
1941
- };
1942
- const sessionsMeta = {
1943
- name: 'sessions',
1944
- description: 'list the Cypress sessions this CLI can reach',
1945
- details: `Lists the Cypress sessions this CLI can reach. Cypress must be running locally,
1946
- support the \`tap\` command, and have an active Testing Type to be listed. A
1947
- session can be targeted by its PID in other commands using \`--session\`.
1084
+ var distExports = requireDist();
1948
1085
 
1949
- tap only supports Chromium based browsers (Chrome, Chromium, Edge, Electron).
1950
- A session running any other browser is listed as unsupported, and every other
1951
- tap command refuses it.`,
1952
- };
1953
- const statusMeta = {
1954
- name: 'status',
1955
- description: 'report where a Cypress session is in its lifecycle',
1956
- details: `Reports where a Cypress session is in its lifecycle, as JSON — for polling and
1957
- "where am I?" checks. Always exits 0 for a determinable stage (including "not
1958
- connected"); a poller branches on the \`status\` field.
1086
+ const debug$8 = Debug('cypress:cli:cypress-sessions');
1087
+ const PROBE_HOST = '127.0.0.1';
1088
+ const DEFAULT_PROBE_TIMEOUT_MS = 2000;
1089
+ const isPidAlive = (pid) => {
1090
+ try {
1091
+ process.kill(pid, 0);
1092
+ return true;
1093
+ }
1094
+ catch (err) {
1095
+ return (err === null || err === void 0 ? void 0 : err.code) === 'EPERM';
1096
+ }
1097
+ };
1098
+ // The server reports `cdpBrowserWsUrl` from in-memory state that it only clears
1099
+ // when the browser *process* exits, so a closed browser window whose process
1100
+ // lingers leaves the url stale. Hit the browser's own DevTools HTTP endpoint
1101
+ // (Chromium exposes `/json/version` whenever the CDP port is open) to confirm
1102
+ // the browser is really reachable before trusting the url.
1103
+ const cdpEndpointReachable = (cdpBrowserWsUrl, timeoutMs) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
1104
+ let versionUrl;
1105
+ try {
1106
+ const { protocol, host } = new URL(cdpBrowserWsUrl);
1107
+ versionUrl = `${protocol === 'wss:' ? 'https:' : 'http:'}//${host}/json/version`;
1108
+ }
1109
+ catch (err) {
1110
+ debug$8('could not derive a CDP version url from %s: %o', cdpBrowserWsUrl, err);
1111
+ return false;
1112
+ }
1113
+ try {
1114
+ const response = yield fetch(versionUrl, { signal: AbortSignal.timeout(timeoutMs) });
1115
+ return response.ok;
1116
+ }
1117
+ catch (err) {
1118
+ debug$8('CDP endpoint unreachable at %s: %o', versionUrl, err);
1119
+ return false;
1120
+ }
1121
+ });
1122
+ const verifySessionRecord = (record_1, ...args_1) => xvfb.__awaiter(void 0, [record_1, ...args_1], void 0, function* (record, timeoutMs = DEFAULT_PROBE_TIMEOUT_MS) {
1123
+ const url = `http://${PROBE_HOST}:${record.serverPort}${distExports.sessionProbePath(record.sessionId)}`;
1124
+ try {
1125
+ const response = yield fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
1126
+ if (response.status !== 200) {
1127
+ return null;
1128
+ }
1129
+ const live = yield response.json();
1130
+ if (live.sessionId !== record.sessionId) {
1131
+ return null;
1132
+ }
1133
+ const cdpBrowserWsUrl = typeof live.cdpBrowserWsUrl === 'string' ? live.cdpBrowserWsUrl : null;
1134
+ const attached = cdpBrowserWsUrl !== null && (yield cdpEndpointReachable(cdpBrowserWsUrl, timeoutMs));
1135
+ // The browser identity describes what the session has open, not what is
1136
+ // reachable over CDP — a browser tap cannot drive still has to be nameable.
1137
+ return Object.assign(Object.assign({}, record), { cdpBrowserWsUrl: attached ? cdpBrowserWsUrl : null, browserName: typeof live.browserName === 'string' ? live.browserName : null, browserFamily: typeof live.browserFamily === 'string' ? live.browserFamily : null, machineId: typeof live.machineId === 'string' ? live.machineId : null, userId: typeof live.userId === 'string' ? live.userId : null });
1138
+ }
1139
+ catch (err) {
1140
+ debug$8('liveness probe failed for pid %d on port %d: %o', record.pid, record.serverPort, err);
1141
+ return null;
1142
+ }
1143
+ });
1959
1144
 
1960
- Stages:
1961
- - not connected: Cypress cannot be reached.
1962
- - browser not selected: Cypress does not have a supported browser open.
1963
- - spec not selected: Cypress has a browser open but no spec is selected.
1964
- - loading: Cypress is loading the spec.
1965
- - running: Cypress is running the spec.
1966
- - passed: Cypress has completed the spec and all executed tests passed.
1967
- - failed: Cypress failed to run the spec, or has completed the spec and at least
1968
- one test failed.
1145
+ const debug$7 = Debug('cypress:cli:cypress-sessions');
1146
+ const getSessionsDir = () => {
1147
+ return distExports.cypressSessionsDir(xvfb.stateModule.getCacheDir());
1148
+ };
1149
+ const listRecordFiles = (dir) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
1150
+ let entries;
1151
+ try {
1152
+ entries = yield fs.readdir(dir);
1153
+ }
1154
+ catch (err) {
1155
+ if (err.code === 'ENOENT') {
1156
+ return [];
1157
+ }
1158
+ throw err;
1159
+ }
1160
+ const files = [];
1161
+ for (const entry of entries) {
1162
+ const pid = distExports.parseRecordPid(entry);
1163
+ if (pid !== null) {
1164
+ files.push({ path: path.join(dir, entry), pid });
1165
+ }
1166
+ }
1167
+ return files;
1168
+ });
1169
+ const readCompatibleRecord = (filePath) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
1170
+ let record;
1171
+ try {
1172
+ record = yield fs.readJson(filePath);
1173
+ }
1174
+ catch (err) {
1175
+ debug$7('could not read cypress sessions record %s: %o', filePath, err);
1176
+ return null;
1177
+ }
1178
+ if (!distExports.isCompatibleRecord(record)) {
1179
+ debug$7('incompatible cypress sessions record %s', filePath);
1180
+ return null;
1181
+ }
1182
+ return record;
1183
+ });
1184
+ // Reaps a record whose writer process is gone, returning whether it was dead.
1185
+ // Removal is best-effort: a file we can't delete (permissions, a Windows lock)
1186
+ // must not abort discovery of the other, live sessions, so the failure is
1187
+ // swallowed and the record is still reported dead.
1188
+ const reapIfDead = (file) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
1189
+ const { path, pid } = file;
1190
+ if (isPidAlive(pid)) {
1191
+ return false;
1192
+ }
1193
+ yield fs.remove(path).catch((err) => {
1194
+ debug$7('failed to reap dead cypress sessions record %s: %o', path, err);
1195
+ });
1196
+ return true;
1197
+ });
1198
+ /**
1199
+ * Reads all the current cypress session records that are still live (i.e. the writer process is still running).
1200
+ * Reaps any dead records.
1201
+ */
1202
+ const readLiveSessions = () => xvfb.__awaiter(void 0, void 0, void 0, function* () {
1203
+ const files = yield listRecordFiles(getSessionsDir());
1204
+ const records = yield Promise.all(files.map((file) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
1205
+ const record = yield readCompatibleRecord(file.path);
1206
+ if (!record) {
1207
+ return null;
1208
+ }
1209
+ if (yield reapIfDead(file)) {
1210
+ return null;
1211
+ }
1212
+ return record;
1213
+ })));
1214
+ return records.filter((record) => record !== null);
1215
+ });
1216
+ const pruneDeadSessionRecords = () => xvfb.__awaiter(void 0, void 0, void 0, function* () {
1217
+ const files = yield listRecordFiles(getSessionsDir());
1218
+ const pruned = yield Promise.all(files.map((file) => reapIfDead(file)));
1219
+ const removed = pruned.filter(Boolean).length;
1220
+ debug$7('pruned %d dead cypress sessions record(s)', removed);
1221
+ return removed;
1222
+ });
1969
1223
 
1970
- Statuses other than passed and failed are intermediate states, indicating that
1971
- Cypress requires additional instruction or is still processing. Loading stays
1972
- loading for as long as the spec takes to build, so a poller needs its own
1973
- timeout; a spec whose build fails reports failed and carries the reason as
1974
- error — it ran no tests, so it reports no counts either.
1224
+ dayjs.extend(relativeTime);
1225
+ // Subdirs under the cache root that are not binary version dirs. SESSIONS_DIRNAME
1226
+ // is sourced from cypress-sessions so a rename there can't silently make prune
1227
+ // treat the sessions dir as a stale binary cache and delete live records.
1228
+ const EXTERNAL_CACHE_ENTRIES = new Set(['bundles', distExports.SESSIONS_DIRNAME]);
1229
+ // output colors for the table
1230
+ const colors = {
1231
+ titles: chalk.white,
1232
+ dates: chalk.cyan,
1233
+ values: chalk.green,
1234
+ size: chalk.gray,
1235
+ };
1236
+ const logCachePath = () => {
1237
+ xvfb.loggerModule.always(xvfb.stateModule.getCacheDir());
1238
+ return undefined;
1239
+ };
1240
+ const clear = () => {
1241
+ return fs.remove(xvfb.stateModule.getCacheDir());
1242
+ };
1243
+ const prune = () => xvfb.__awaiter(void 0, void 0, void 0, function* () {
1244
+ const cacheDir = xvfb.stateModule.getCacheDir();
1245
+ const checkedInBinaryVersion = xvfb.util.pkgVersion();
1246
+ let deletedBinary = false;
1247
+ try {
1248
+ const versions = yield fs.readdir(cacheDir);
1249
+ for (const version of versions) {
1250
+ if (EXTERNAL_CACHE_ENTRIES.has(version))
1251
+ continue;
1252
+ if (version !== checkedInBinaryVersion) {
1253
+ deletedBinary = true;
1254
+ const versionDir = path.join(cacheDir, version);
1255
+ yield fs.remove(versionDir);
1256
+ }
1257
+ }
1258
+ if (deletedBinary) {
1259
+ xvfb.loggerModule.always(`Deleted all binary caches except for the ${checkedInBinaryVersion} binary cache.`);
1260
+ }
1261
+ else {
1262
+ xvfb.loggerModule.always(`No binary caches found to prune.`);
1263
+ }
1264
+ yield pruneDeadSessionRecords();
1265
+ }
1266
+ catch (e) {
1267
+ if (e.code === 'ENOENT') {
1268
+ xvfb.loggerModule.always(`No Cypress cache was found at ${cacheDir}. Nothing to prune.`);
1269
+ return;
1270
+ }
1271
+ throw e;
1272
+ }
1273
+ });
1274
+ const fileSizeInMB = (size) => {
1275
+ return `${(size / 1024 / 1024).toFixed(1)}MB`;
1276
+ };
1277
+ /**
1278
+ * Collects all cached versions, finds when each was used
1279
+ * and prints a table with results to the terminal
1280
+ */
1281
+ const list = (...args_1) => xvfb.__awaiter(void 0, [...args_1], void 0, function* (showSize = false) {
1282
+ const binaries = yield getCachedVersions(showSize);
1283
+ const head = [colors.titles('version'), colors.titles('last used')];
1284
+ if (showSize) {
1285
+ head.push(colors.titles('size'));
1286
+ }
1287
+ const table = new Table({
1288
+ head,
1289
+ });
1290
+ binaries.forEach((binary) => {
1291
+ const versionString = colors.values(binary.version);
1292
+ const lastUsed = binary.accessed ? colors.dates(binary.accessed) : 'unknown';
1293
+ const row = [versionString, lastUsed];
1294
+ if (showSize) {
1295
+ const size = colors.size(fileSizeInMB(binary.size));
1296
+ row.push(size);
1297
+ }
1298
+ return table.push(row);
1299
+ });
1300
+ xvfb.loggerModule.always(table.toString());
1301
+ });
1302
+ const getCachedVersions = (showSize) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
1303
+ const cacheDir = xvfb.stateModule.getCacheDir();
1304
+ const versions = yield fs.readdir(cacheDir);
1305
+ const filteredVersions = versions.filter(xvfb.util.isSemver).map((version) => {
1306
+ return {
1307
+ version,
1308
+ folderPath: path.join(cacheDir, version),
1309
+ };
1310
+ });
1311
+ const binaries = [];
1312
+ for (const binary of filteredVersions) {
1313
+ const binaryDir = xvfb.stateModule.getBinaryDir(binary.version);
1314
+ const executable = xvfb.stateModule.getPathToExecutable(binaryDir);
1315
+ try {
1316
+ const stat = yield fs.stat(executable);
1317
+ const lastAccessedTime = _.get(stat, 'atime');
1318
+ if (lastAccessedTime) {
1319
+ const accessed = dayjs(lastAccessedTime).fromNow();
1320
+ // @ts-expect-error - accessed is not defined in the type
1321
+ binary.accessed = accessed;
1322
+ }
1323
+ // if no lastAccessedTime
1324
+ // the test runner has never been opened
1325
+ // or could be a test simulating missing timestamp
1326
+ }
1327
+ catch (e) {
1328
+ // could not find the binary or gets its stats
1329
+ // no-op
1330
+ }
1331
+ if (showSize) {
1332
+ const binaryDir = xvfb.stateModule.getBinaryDir(binary.version);
1333
+ const size = yield getSize(binaryDir);
1334
+ binaries.push(Object.assign(Object.assign({}, binary), { size }));
1335
+ }
1336
+ else {
1337
+ binaries.push(binary);
1338
+ }
1339
+ }
1340
+ return binaries;
1341
+ });
1342
+ const cacheModule = {
1343
+ path: logCachePath,
1344
+ clear,
1345
+ prune,
1346
+ list,
1347
+ getCachedVersions,
1348
+ };
1975
1349
 
1976
- From loading onwards the output carries \`startedAt\`, the spec every other field
1977
- describes (null while loading). In the event of a rerun the previous spec's
1978
- verdict remains visible until the new one begins, identical on every other
1979
- field, so use \`startedAt\` to validate whether output describes the previous
1980
- spec or the new one.`,
1981
- };
1982
- const specsMeta = {
1983
- name: 'specs',
1984
- description: 'list the specs the Cypress session can run, most recently modified first',
1985
- details: `Lists the specs the connected Cypress session can run, in descending order by
1986
- last modified. To find other testing types you must connect to a Cypress
1987
- session with that testing type active.`,
1988
- };
1989
- // Every selector these three take must resolve to exactly one element, so a
1990
- // reader is never silently shown one of several matches. Their help says so in
1991
- // the same words, and names the remedy the ambiguity error offers.
1992
- const SINGLE_ELEMENT_SELECTOR = 'a CSS selector matching exactly one element';
1993
- const AMBIGUOUS_SELECTOR_HELP = `The selector must match exactly one element. If it matches multiple elements
1994
- nothing is read: the command answers with a numbered list of the matches (up to
1995
- 10), each with a unique selector. Re-run with --at <index> to read one of them,
1996
- or with whichever selector you meant.`;
1997
- // Where `dom` and `aria` read from with no selector: the document element only
1998
- // adds a <head> of script and style text; `--selector html` still reads it.
1999
- const TAP_DEFAULT_SELECTOR = 'body';
2000
- // Shared by the three selector-taking reads, so the way you pick one match out
2001
- // of several is identical across them.
2002
- const atField = {
2003
- name: 'at',
2004
- type: 'number',
2005
- required: false,
2006
- description: 'When a selector matches multiple elements, the 0-based index of the match to read',
2007
- };
2008
- const domMeta = {
2009
- name: 'dom',
2010
- description: 'read the application-under-test DOM as HTML: the page body, or a single element with its children by providing a CSS Selector',
2011
- details: `Reads the application-under-test DOM as HTML: a single element matching a CSS
2012
- Selector, and all of its children. Without --selector it reads the page body;
2013
- pass --selector html for the entire document. Output is capped browser-side so a
2014
- heavy page never ships megabytes at once.
1350
+ /**
1351
+ * Throws an error with "details" property from
1352
+ * "errors" object.
1353
+ * @param {Object} details - Error details
1354
+ */
1355
+ const throwInvalidOptionError = (details) => {
1356
+ if (!details) {
1357
+ details = xvfb.errors.unknownError;
1358
+ }
1359
+ // throw this error synchronously, it will be caught later on and
1360
+ // the details will be propagated to the promise chain
1361
+ const err = new Error();
1362
+ err.details = details;
1363
+ throw err;
1364
+ };
1365
+ /**
1366
+ * Selects exec args based on the configured `testingType`
1367
+ * @param {string} testingType The type of tests being executed
1368
+ * @returns {string[]} The array of new exec arguments
1369
+ */
1370
+ const processTestingType = (options) => {
1371
+ if (options.e2e && options.component) {
1372
+ return throwInvalidOptionError(xvfb.errors.incompatibleTestTypeFlags);
1373
+ }
1374
+ if (options.testingType && (options.component || options.e2e)) {
1375
+ return throwInvalidOptionError(xvfb.errors.incompatibleTestTypeFlags);
1376
+ }
1377
+ if (options.testingType === 'component' || options.component || options.ct) {
1378
+ return ['--testing-type', 'component'];
1379
+ }
1380
+ if (options.testingType === 'e2e' || options.e2e) {
1381
+ return ['--testing-type', 'e2e'];
1382
+ }
1383
+ if (options.testingType) {
1384
+ return throwInvalidOptionError(xvfb.errors.invalidTestingType);
1385
+ }
1386
+ return [];
1387
+ };
1388
+ /**
1389
+ * Throws an error if configFile is string 'false' or boolean false
1390
+ * @param {*} options
1391
+ */
1392
+ const checkConfigFile = (options) => {
1393
+ // CLI will parse as string, module API can pass in boolean
1394
+ if (options.configFile === 'false' || options.configFile === false) {
1395
+ throwInvalidOptionError(xvfb.errors.invalidConfigFile);
1396
+ }
1397
+ };
2015
1398
 
2016
- ${AMBIGUOUS_SELECTOR_HELP}`,
2017
- params: [],
2018
- options: [
2019
- { ...selectorField, defaultValue: TAP_DEFAULT_SELECTOR, description: SINGLE_ELEMENT_SELECTOR },
2020
- { name: 'max-chars', alias: 'm', type: 'number', required: false, defaultValue: 30000, description: 'cap on returned HTML characters' },
2021
- atField,
2022
- ],
2023
- };
2024
- const ariaMeta = {
2025
- name: 'aria',
2026
- description: 'read the accessibility (ARIA) tree of the application-under-test page body, or the subtree at a selector',
2027
- details: `Reads the accessibility (ARIA) tree of the application under test, rooted at the
2028
- page body or at the element a CSS Selector matches. Structural and text-only
2029
- roles are dropped, leaving the compact role/name/state tree DevTools shows.
1399
+ const debug$6 = Debug('cypress:cli');
1400
+ /**
1401
+ * Maps options collected by the CLI
1402
+ * and forms list of CLI arguments to the server.
1403
+ *
1404
+ * Note: there is lightweight validation, with errors
1405
+ * thrown synchronously.
1406
+ *
1407
+ * @returns {string[]} list of CLI arguments
1408
+ */
1409
+ const processOpenOptions = (options = {}) => {
1410
+ // In addition to setting the project directory, setting the project option
1411
+ // here ultimately decides whether cypress is run in global mode or not.
1412
+ // It's first based off whether it's installed globally by npm/yarn (-g).
1413
+ // A global install can be overridden by the --project flag, putting Cypress
1414
+ // in project mode. A non-global install can be overridden by the --global
1415
+ // flag, putting it in global mode.
1416
+ if (!xvfb.util.isInstalledGlobally() && !options.global && !options.project) {
1417
+ options.project = process.cwd();
1418
+ }
1419
+ const args = [];
1420
+ if (options.config) {
1421
+ args.push('--config', options.config);
1422
+ }
1423
+ if (options.configFile !== undefined) {
1424
+ checkConfigFile(options);
1425
+ args.push('--config-file', options.configFile);
1426
+ }
1427
+ if (options.browser) {
1428
+ args.push('--browser', options.browser);
1429
+ }
1430
+ if (options.env) {
1431
+ args.push('--env', options.env);
1432
+ }
1433
+ if (options.expose) {
1434
+ args.push('--expose', options.expose);
1435
+ }
1436
+ if (options.port) {
1437
+ args.push('--port', options.port);
1438
+ }
1439
+ if (options.project) {
1440
+ args.push('--project', options.project);
1441
+ }
1442
+ if (options.global) {
1443
+ args.push('--global', options.global);
1444
+ }
1445
+ if (options.inspect) {
1446
+ args.push('--inspect');
1447
+ }
1448
+ if (options.inspectBrk) {
1449
+ args.push('--inspectBrk');
1450
+ }
1451
+ args.push(...processTestingType(options));
1452
+ debug$6('opening from options %j', options);
1453
+ debug$6('command line arguments %j', args);
1454
+ return args;
1455
+ };
1456
+ const start$3 = (...args_1) => xvfb.__awaiter(void 0, [...args_1], void 0, function* (options = {}) {
1457
+ function open() {
1458
+ try {
1459
+ const args = processOpenOptions(options);
1460
+ return spawn.start$1(args, {
1461
+ dev: options.dev,
1462
+ detached: Boolean(options.detached),
1463
+ });
1464
+ }
1465
+ catch (err) {
1466
+ if (err.details) {
1467
+ return xvfb.exitWithError(err.details)();
1468
+ }
1469
+ throw err;
1470
+ }
1471
+ }
1472
+ if (options.dev) {
1473
+ return open();
1474
+ }
1475
+ yield spawn.start();
1476
+ return open();
1477
+ });
1478
+ var openModule = {
1479
+ start: start$3,
1480
+ processOpenOptions,
1481
+ };
2030
1482
 
2031
- ${AMBIGUOUS_SELECTOR_HELP}`,
2032
- params: [],
2033
- options: [
2034
- { ...selectorField, defaultValue: TAP_DEFAULT_SELECTOR, description: SINGLE_ELEMENT_SELECTOR },
2035
- // The accessibility tree of a real app is deep; the cap keeps the projection
2036
- // affordable for an LLM. A selector roots it at a subtree for finer reads.
2037
- { name: 'max-nodes', alias: 'm', type: 'number', required: false, defaultValue: 200, description: 'cap on the number of accessibility nodes returned' },
2038
- atField,
2039
- ],
2040
- };
2041
- const inspectMeta = {
2042
- name: 'inspect',
2043
- description: 'retrieve the tag, attributes, computed styles, box model, and accessibility node of a specific element',
2044
- details: `Inspects a single element matching a CSS Selector: outputs the tag, attributes,
2045
- curated computed styles, box model, and that element's own accessibility node
2046
- — where \`aria\` reads the whole tree, this reads the one node.
1483
+ const debug$5 = Debug('cypress:cli:run');
1484
+ /**
1485
+ * Typically a user passes a string path to the project.
1486
+ * But "cypress open" allows using `false` to open in global mode,
1487
+ * and the user can accidentally execute `cypress run --project false`
1488
+ * which should be invalid.
1489
+ */
1490
+ const isValidProject = (v) => {
1491
+ if (typeof v === 'boolean') {
1492
+ return false;
1493
+ }
1494
+ if (v === '' || v === 'false' || v === 'true') {
1495
+ return false;
1496
+ }
1497
+ return true;
1498
+ };
1499
+ /**
1500
+ * Maps options collected by the CLI
1501
+ * and forms list of CLI arguments to the server.
1502
+ *
1503
+ * Note: there is lightweight validation, with errors
1504
+ * thrown synchronously.
1505
+ *
1506
+ * @returns {string[]} list of CLI arguments
1507
+ */
1508
+ const processRunOptions = (options = {}) => {
1509
+ debug$5('processing run options %o', options);
1510
+ if (!isValidProject(options.project)) {
1511
+ debug$5('invalid project option %o', { project: options.project });
1512
+ return throwInvalidOptionError(xvfb.errors.invalidRunProjectPath);
1513
+ }
1514
+ const args = ['--run-project', options.project];
1515
+ if (options.autoCancelAfterFailures || options.autoCancelAfterFailures === 0 || options.autoCancelAfterFailures === false) {
1516
+ args.push('--auto-cancel-after-failures', options.autoCancelAfterFailures);
1517
+ }
1518
+ if (options.browser) {
1519
+ args.push('--browser', options.browser);
1520
+ }
1521
+ if (options.ciBuildId) {
1522
+ args.push('--ci-build-id', options.ciBuildId);
1523
+ }
1524
+ if (options.config) {
1525
+ args.push('--config', options.config);
1526
+ }
1527
+ if (options.configFile !== undefined) {
1528
+ checkConfigFile(options);
1529
+ args.push('--config-file', options.configFile);
1530
+ }
1531
+ if (options.env) {
1532
+ args.push('--env', options.env);
1533
+ }
1534
+ if (options.expose) {
1535
+ args.push('--expose', options.expose);
1536
+ }
1537
+ if (options.exit === false) {
1538
+ args.push('--no-exit');
1539
+ }
1540
+ if (options.group) {
1541
+ args.push('--group', options.group);
1542
+ }
1543
+ if (options.headed) {
1544
+ args.push('--headed', options.headed);
1545
+ }
1546
+ if (options.headless) {
1547
+ if (options.headed) {
1548
+ return throwInvalidOptionError(xvfb.errors.incompatibleHeadlessFlags);
1549
+ }
1550
+ args.push('--headed', String(!options.headless));
1551
+ }
1552
+ // if key is set use that - else attempt to find it by environment variable
1553
+ if (options.key == null) {
1554
+ debug$5('--key is not set, looking up environment variable CYPRESS_RECORD_KEY');
1555
+ options.key = xvfb.util.getEnv('CYPRESS_RECORD_KEY');
1556
+ }
1557
+ // if we have a key assume we're in record mode
1558
+ if (options.key) {
1559
+ args.push('--key', options.key);
1560
+ }
1561
+ if (options.outputPath) {
1562
+ args.push('--output-path', options.outputPath);
1563
+ }
1564
+ if (options.parallel) {
1565
+ args.push('--parallel');
1566
+ }
1567
+ if (options.passWithNoTests) {
1568
+ args.push('--pass-with-no-tests');
1569
+ }
1570
+ if (options.posixExitCodes) {
1571
+ args.push('--posix-exit-codes');
1572
+ }
1573
+ if (options.port) {
1574
+ args.push('--port', options.port);
1575
+ }
1576
+ if (options.quiet) {
1577
+ args.push('--quiet');
1578
+ }
1579
+ // if record is defined and we're not
1580
+ // already in ci mode, then send it up
1581
+ if (options.record != null) {
1582
+ args.push('--record', options.record);
1583
+ }
1584
+ // if we have a specific reporter push that into the args
1585
+ if (options.reporter) {
1586
+ args.push('--reporter', options.reporter);
1587
+ }
1588
+ // if we have a specific reporter push that into the args
1589
+ if (options.reporterOptions) {
1590
+ args.push('--reporter-options', options.reporterOptions);
1591
+ }
1592
+ if (options.runnerUi != null) {
1593
+ args.push('--runner-ui', options.runnerUi);
1594
+ }
1595
+ // if we have specific spec(s) push that into the args
1596
+ if (options.spec) {
1597
+ args.push('--spec', options.spec);
1598
+ }
1599
+ if (options.tag) {
1600
+ args.push('--tag', options.tag);
1601
+ }
1602
+ if (options.inspect) {
1603
+ args.push('--inspect');
1604
+ }
1605
+ if (options.inspectBrk) {
1606
+ args.push('--inspectBrk');
1607
+ }
1608
+ args.push(...processTestingType(options));
1609
+ return args;
1610
+ };
1611
+ const runModule = {
1612
+ processRunOptions,
1613
+ isValidProject,
1614
+ // resolves with the number of failed tests
1615
+ start() {
1616
+ return xvfb.__awaiter(this, arguments, void 0, function* (options = {}) {
1617
+ _.defaults(options, {
1618
+ key: null,
1619
+ spec: null,
1620
+ reporter: null,
1621
+ reporterOptions: null,
1622
+ project: process.cwd(),
1623
+ });
1624
+ function run() {
1625
+ try {
1626
+ const args = processRunOptions(options);
1627
+ debug$5('run to spawn.start args %j', args);
1628
+ return spawn.start$1(args, {
1629
+ dev: options.dev,
1630
+ });
1631
+ }
1632
+ catch (err) {
1633
+ if (err.details) {
1634
+ return xvfb.exitWithError(err.details)();
1635
+ }
1636
+ throw err;
1637
+ }
1638
+ }
1639
+ if (options.dev) {
1640
+ return run();
1641
+ }
1642
+ yield spawn.start();
1643
+ return run();
1644
+ });
1645
+ },
1646
+ };
2047
1647
 
2048
- ${AMBIGUOUS_SELECTOR_HELP}`,
2049
- params: [],
2050
- options: [
2051
- { ...selectorField, required: true, description: `${SINGLE_ELEMENT_SELECTOR}, identifying the element to inspect` },
2052
- atField,
2053
- ],
2054
- };
2055
- // CLI-native tap commands: implemented entirely in the CLI (session discovery
2056
- // over the filesystem, DOM/ARIA reads over CDP), so — unlike TAP_COMMANDS — they
2057
- // are never advertised by getSchema or exec'd on the session. Listed here in the
2058
- // order they appear in help, ahead of the schema-driven commands.
2059
- exports$1.TAP_NATIVE_COMMANDS = [
2060
- sessionsMeta,
2061
- statusMeta,
2062
- specsMeta,
2063
- runMeta,
2064
- domMeta,
2065
- ariaMeta,
2066
- inspectMeta,
2067
- ];
2068
- const allTapCommands = [...exports$1.TAP_NATIVE_COMMANDS, ...exports$1.TAP_COMMANDS];
2069
- /** Every command name this CLI ships, whether it dispatches to the session or handles it itself. */
2070
- exports$1.KNOWN_COMMANDS = new Set(allTapCommands.map(({ name }) => name));
2071
- // The failure catalogue and the error both sides raise. Re-exported here for the
2072
- // same reason the result contracts are: this module is dependency-free, so the
2073
- // browser-side app can import it where it cannot import the package barrel.
2074
- __exportStar(requireTapErrors(), exports$1);
2075
- // Per-command result contracts live in `./contracts/`; re-exported here so the
2076
- // app's deep import of this module and the package barrel both reach them.
2077
- __exportStar(requireReporter(), exports$1);
2078
- __exportStar(requireCommand(), exports$1);
2079
- __exportStar(requirePinned(), exports$1);
2080
- __exportStar(requirePin(), exports$1);
2081
- __exportStar(requireResolveSelector(), exports$1);
2082
-
2083
- } (tapContract));
2084
- return tapContract;
2085
- }
1648
+ const debug$4 = Debug('cypress:cli');
1649
+ const defaultBaseUrl = 'https://download.cypress.io/';
1650
+ const defaultMaxRedirects = 10;
1651
+ const getProxyForUrlWithNpmConfig = (url) => {
1652
+ return proxyFromEnv.getProxyForUrl(url) ||
1653
+ process.env.npm_config_https_proxy ||
1654
+ process.env.npm_config_proxy ||
1655
+ null;
1656
+ };
1657
+ const getBaseUrl = () => {
1658
+ if (xvfb.util.getEnv('CYPRESS_DOWNLOAD_MIRROR')) {
1659
+ let baseUrl = xvfb.util.getEnv('CYPRESS_DOWNLOAD_MIRROR');
1660
+ if (!(baseUrl === null || baseUrl === void 0 ? void 0 : baseUrl.endsWith('/'))) {
1661
+ baseUrl += '/';
1662
+ }
1663
+ return baseUrl || defaultBaseUrl;
1664
+ }
1665
+ return defaultBaseUrl;
1666
+ };
1667
+ const getCA = () => xvfb.__awaiter(void 0, void 0, void 0, function* () {
1668
+ if (process.env.npm_config_cafile) {
1669
+ try {
1670
+ const caFileContent = yield fs.readFile(process.env.npm_config_cafile, 'utf8');
1671
+ return caFileContent;
1672
+ }
1673
+ catch (error) {
1674
+ debug$4('error reading ca file', error);
1675
+ return;
1676
+ }
1677
+ }
1678
+ if (process.env.npm_config_ca) {
1679
+ return process.env.npm_config_ca;
1680
+ }
1681
+ return;
1682
+ });
1683
+ const prepend = (arch, urlPath, version) => {
1684
+ const endpoint = new URL(urlPath, getBaseUrl()).toString();
1685
+ const platform = os.platform();
1686
+ const pathTemplate = xvfb.util.getEnv('CYPRESS_DOWNLOAD_PATH_TEMPLATE', true);
1687
+ if ((platform === 'win32') && (arch === 'arm64')) {
1688
+ debug$4(`detected platform ${platform} architecture ${arch} combination`);
1689
+ arch = 'x64';
1690
+ debug$4(`overriding to download ${platform}-${arch} instead`);
1691
+ }
1692
+ return pathTemplate
1693
+ ? (pathTemplate
1694
+ .replace(/\\?\$\{endpoint\}/g, endpoint)
1695
+ .replace(/\\?\$\{platform\}/g, platform)
1696
+ .replace(/\\?\$\{arch\}/g, arch)
1697
+ .replace(/\\?\$\{version\}/g, version))
1698
+ : `${endpoint}?platform=${platform}&arch=${arch}`;
1699
+ };
1700
+ const getUrl = (arch, version) => {
1701
+ if (_.isString(version) && version.match(/^https?:\/\/.*$/)) {
1702
+ debug$4('version is already an url', version);
1703
+ return version;
1704
+ }
1705
+ const urlPath = version ? `desktop/${version}` : 'desktop';
1706
+ return prepend(arch, urlPath, version || '');
1707
+ };
1708
+ const statusMessage = (err) => {
1709
+ return (err.statusCode
1710
+ ? [err.statusCode, err.statusMessage].join(' - ')
1711
+ : err.toString());
1712
+ };
1713
+ const prettyDownloadErr = (err, url) => {
1714
+ const msg = commonTags.stripIndent `
1715
+ URL: ${url}
1716
+ ${statusMessage(err)}
1717
+ `;
1718
+ debug$4(msg);
1719
+ return xvfb.throwFormErrorText(xvfb.errors.failedDownload)(msg);
1720
+ };
1721
+ /**
1722
+ * Checks checksum and file size for the given file. Allows both
1723
+ * values or just one of them to be checked.
1724
+ */
1725
+ const verifyDownloadedFile = (filename, expectedSize, expectedChecksum) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
1726
+ if (expectedSize && expectedChecksum) {
1727
+ debug$4('verifying checksum and file size');
1728
+ return Bluebird.join(xvfb.util.getFileChecksum(filename), xvfb.util.getFileSize(filename), (checksum, filesize) => {
1729
+ if (checksum === expectedChecksum && filesize === expectedSize) {
1730
+ debug$4('downloaded file has the expected checksum and size ✅');
1731
+ return;
1732
+ }
1733
+ debug$4('raising error: checksum or file size mismatch');
1734
+ const text = commonTags.stripIndent `
1735
+ Corrupted download
2086
1736
 
2087
- var tapOperations = {};
1737
+ Expected downloaded file to have checksum: ${expectedChecksum}
1738
+ Computed checksum: ${checksum}
2088
1739
 
2089
- var hasRequiredTapOperations;
1740
+ Expected downloaded file to have size: ${expectedSize}
1741
+ Computed size: ${filesize}
1742
+ `;
1743
+ debug$4(text);
1744
+ throw new Error(text);
1745
+ });
1746
+ }
1747
+ if (expectedChecksum) {
1748
+ debug$4('only checking expected file checksum %d', expectedChecksum);
1749
+ const checksum = yield xvfb.util.getFileChecksum(filename);
1750
+ if (checksum === expectedChecksum) {
1751
+ debug$4('downloaded file has the expected checksum ✅');
1752
+ return;
1753
+ }
1754
+ debug$4('raising error: file checksum mismatch');
1755
+ const text = commonTags.stripIndent `
1756
+ Corrupted download
2090
1757
 
2091
- function requireTapOperations () {
2092
- if (hasRequiredTapOperations) return tapOperations;
2093
- hasRequiredTapOperations = 1;
2094
- Object.defineProperty(tapOperations, "__esModule", { value: true });
2095
- tapOperations.tapRunSpecOperation = tapOperations.TapSpecsOperation = void 0;
2096
- tapOperations.TapSpecsOperation = {
2097
- operationName: 'TapSpecs',
2098
- query: /* GraphQL */ `
2099
- query TapSpecs {
2100
- currentProject {
2101
- specs {
2102
- relative
2103
- absolute
2104
- gitInfo {
2105
- lastModifiedHumanReadable
2106
- lastModifiedTimestamp
2107
- }
1758
+ Expected downloaded file to have checksum: ${expectedChecksum}
1759
+ Computed checksum: ${checksum}
1760
+ `;
1761
+ throw new Error(text);
1762
+ }
1763
+ if (expectedSize) {
1764
+ // maybe we don't have a checksum, but at least CDN returns content length
1765
+ // which we can check against the file size
1766
+ debug$4('only checking expected file size %d', expectedSize);
1767
+ const filesize = yield xvfb.util.getFileSize(filename);
1768
+ if (filesize === expectedSize) {
1769
+ debug$4('downloaded file has the expected size ✅');
1770
+ return;
2108
1771
  }
2109
- }
1772
+ debug$4('raising error: file size mismatch');
1773
+ const text = commonTags.stripIndent `
1774
+ Corrupted download
1775
+
1776
+ Expected downloaded file to have size: ${expectedSize}
1777
+ Computed size: ${filesize}
1778
+ `;
1779
+ throw new Error(text);
1780
+ }
1781
+ debug$4('downloaded file lacks checksum or size to verify');
1782
+ return;
1783
+ });
1784
+ // downloads from given url
1785
+ // return an object with
1786
+ // {filename: ..., downloaded: true}
1787
+ const downloadFromUrl = ({ url, downloadDestination, progress, ca, version, redirectTTL = defaultMaxRedirects }) => {
1788
+ if (redirectTTL <= 0) {
1789
+ return Promise.reject(new Error(commonTags.stripIndent `
1790
+ Failed downloading the Cypress binary.
1791
+ There were too many redirects. The default allowance is ${defaultMaxRedirects}.
1792
+ Maybe you got stuck in a redirect loop?
1793
+ `));
1794
+ }
1795
+ return new Bluebird((resolve, reject) => {
1796
+ const proxy = getProxyForUrlWithNpmConfig(url);
1797
+ debug$4('Downloading package', {
1798
+ url,
1799
+ proxy,
1800
+ downloadDestination,
1801
+ });
1802
+ if (ca) {
1803
+ debug$4('using custom CA details from npm config');
1804
+ }
1805
+ const reqOptions = Object.assign(Object.assign(Object.assign({ uri: url }, (proxy ? { proxy } : {})), (ca ? { agentOptions: { ca } } : {})), { method: 'GET', followRedirect: false });
1806
+ const req = request(reqOptions);
1807
+ // closure
1808
+ let started = null;
1809
+ let expectedSize;
1810
+ let expectedChecksum;
1811
+ requestProgress(req, {
1812
+ throttle: progress.throttle,
1813
+ })
1814
+ .on('response', (response) => {
1815
+ // we have computed checksum and filesize during test runner binary build
1816
+ // and have set it on the S3 object as user meta data, available via
1817
+ // these custom headers "x-amz-meta-..."
1818
+ // see https://github.com/cypress-io/cypress/pull/4092
1819
+ expectedSize = response.headers['x-amz-meta-size'] ||
1820
+ response.headers['content-length'];
1821
+ expectedChecksum = response.headers['x-amz-meta-checksum'];
1822
+ if (expectedChecksum) {
1823
+ debug$4('expected checksum %s', expectedChecksum);
1824
+ }
1825
+ if (expectedSize) {
1826
+ // convert from string (all Amazon custom headers are strings)
1827
+ expectedSize = Number(expectedSize);
1828
+ debug$4('expected file size %d', expectedSize);
1829
+ }
1830
+ // start counting now once we've gotten
1831
+ // response headers
1832
+ started = new Date();
1833
+ if (/^3/.test(response.statusCode)) {
1834
+ const redirectVersion = response.headers['x-version'];
1835
+ const redirectUrl = response.headers.location;
1836
+ debug$4('redirect version:', redirectVersion);
1837
+ debug$4('redirect url:', redirectUrl);
1838
+ downloadFromUrl({ url: redirectUrl, progress, ca, downloadDestination, version: redirectVersion, redirectTTL: redirectTTL - 1 })
1839
+ .then(resolve).catch(reject);
1840
+ // if our status code does not start with 200
1841
+ }
1842
+ else if (!/^2/.test(response.statusCode)) {
1843
+ debug$4('response code %d', response.statusCode);
1844
+ const err = new Error(commonTags.stripIndent `
1845
+ Failed downloading the Cypress binary.
1846
+ Response code: ${response.statusCode}
1847
+ Response message: ${response.statusMessage}
1848
+ `);
1849
+ reject(err);
1850
+ // status codes here are all 2xx
1851
+ }
1852
+ else {
1853
+ // We only enable this pipe connection when we know we've got a successful return
1854
+ // and handle the completion with verify and resolve
1855
+ // there was a possible race condition between end of request and close of writeStream
1856
+ // that is made ordered with this Promise.all
1857
+ Bluebird.all([new Bluebird((r) => {
1858
+ return response.pipe(fs.createWriteStream(downloadDestination).on('close', r));
1859
+ }), new Bluebird((r) => response.on('end', r))])
1860
+ .then(() => {
1861
+ debug$4('downloading finished');
1862
+ verifyDownloadedFile(downloadDestination, expectedSize, expectedChecksum)
1863
+ .then(() => debug$4('verified'))
1864
+ .then(() => resolve(version))
1865
+ .catch(reject);
1866
+ });
1867
+ }
1868
+ })
1869
+ .on('error', (e) => {
1870
+ if (e.code === 'ECONNRESET')
1871
+ return; // sometimes proxies give ECONNRESET but we don't care
1872
+ reject(e);
1873
+ })
1874
+ .on('progress', (state) => {
1875
+ // total time we've elapsed
1876
+ // starting on our first progress notification
1877
+ const elapsed = +new Date() - +started;
1878
+ // request-progress sends a value between 0 and 1
1879
+ const percentage = xvfb.util.convertPercentToPercentage(state.percent);
1880
+ const eta = xvfb.util.calculateEta(percentage, elapsed);
1881
+ // send up our percent and seconds remaining
1882
+ progress.onProgress(percentage, xvfb.util.secsRemaining(eta));
1883
+ });
1884
+ });
1885
+ };
1886
+ /**
1887
+ * Download Cypress.zip from external versionUrl to local file.
1888
+ * @param [string] version Could be "3.3.0" or full URL
1889
+ * @param [string] downloadDestination Local filename to save as
1890
+ */
1891
+ const start$2 = (opts) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
1892
+ let { version, downloadDestination, progress, redirectTTL } = opts;
1893
+ if (!downloadDestination) {
1894
+ assert.ok(_.isString(downloadDestination) && !_.isEmpty(downloadDestination), 'missing download dir');
1895
+ }
1896
+ if (!progress) {
1897
+ progress = { onProgress: () => {
1898
+ return {};
1899
+ } };
1900
+ }
1901
+ const arch = yield xvfb.util.getRealArch();
1902
+ const versionUrl = getUrl(arch, version);
1903
+ progress.throttle = 100;
1904
+ debug$4('needed Cypress version: %s', version);
1905
+ debug$4('source url %s', versionUrl);
1906
+ debug$4(`downloading cypress.zip to "${downloadDestination}"`);
1907
+ try {
1908
+ // ensure download dir exists
1909
+ yield fs.ensureDir(path.dirname(downloadDestination));
1910
+ const ca = yield getCA();
1911
+ return downloadFromUrl(Object.assign({ url: versionUrl, downloadDestination, progress, ca, version }, (redirectTTL ? { redirectTTL } : {})));
1912
+ }
1913
+ catch (err) {
1914
+ return prettyDownloadErr(err, versionUrl);
1915
+ }
1916
+ });
1917
+ const downloadModule = {
1918
+ start: start$2,
1919
+ getUrl,
1920
+ getProxyForUrlWithNpmConfig,
1921
+ getCA,
1922
+ };
1923
+
1924
+ const pipelineAsync = util.promisify(require$$0.pipeline);
1925
+ // Unix file mode masks for entries stored in zip's externalFileAttributes
1926
+ // (the high 16 bits when the file was zipped on a Unix host).
1927
+ const S_IFMT = 0o170000;
1928
+ const S_IFDIR = 0o040000;
1929
+ const S_IFLNK = 0o120000;
1930
+ // PATH_MAX on Linux/macOS is 4096; symlink targets larger than this are not
1931
+ // legal filesystem paths and almost certainly indicate a malformed or
1932
+ // malicious archive. The cap also prevents reading an arbitrarily large
1933
+ // "symlink" entry into memory.
1934
+ const MAX_SYMLINK_TARGET_BYTES = 4096;
1935
+ /**
1936
+ * Extracts the contents of a zip archive into the given destination directory.
1937
+ * Recreates directories, regular files, and symlinks while preserving Unix
1938
+ * file modes encoded in each entry's external attributes. Calls `onEntry` once
1939
+ * per archive entry processed. Refuses entries whose resolved path would
1940
+ * escape the destination directory.
1941
+ */
1942
+ const extractWithYauzl = (zipFilePath, destDir, onEntry) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
1943
+ const resolvedDest = path.resolve(destDir);
1944
+ yield new Promise((resolve, reject) => {
1945
+ // autoClose: false — `finish` below owns closing the zipfile, so we don't
1946
+ // want yauzl's internal end-listener closing it first and tripping a
1947
+ // double-close (EBADF) when we do.
1948
+ yauzl.open(zipFilePath, { lazyEntries: true, autoClose: false }, (err, zipFile) => {
1949
+ if (err) {
1950
+ return reject(err);
1951
+ }
1952
+ // `settled` guards against an in-flight `handleEntry` calling
1953
+ // `zipFile.readEntry()` on a now-closed handle when extraction has
1954
+ // already failed (e.g. yauzl emitted 'error' while we were writing
1955
+ // an entry to disk).
1956
+ let settled = false;
1957
+ const finish = _.once((err) => {
1958
+ var _a, _b;
1959
+ settled = true;
1960
+ (_a = zipFile.removeAllListeners) === null || _a === void 0 ? void 0 : _a.call(zipFile);
1961
+ (_b = zipFile.close) === null || _b === void 0 ? void 0 : _b.call(zipFile);
1962
+ if (err) {
1963
+ return reject(err);
1964
+ }
1965
+ return resolve();
1966
+ });
1967
+ // Normalize any thrown / emitted value into a real Error so that a
1968
+ // falsy rejection (e.g. `Promise.reject(undefined)`) doesn't get
1969
+ // misread by `finish` as a successful completion.
1970
+ const fail = (err) => {
1971
+ finish(err instanceof Error ? err : new Error(typeof err === 'string' && err ? err : 'zip extraction failed'));
1972
+ };
1973
+ zipFile.on('error', fail);
1974
+ zipFile.on('end', () => finish());
1975
+ zipFile.on('entry', (entry) => {
1976
+ handleEntry(zipFile, entry, resolvedDest)
1977
+ .then(() => {
1978
+ if (settled)
1979
+ return;
1980
+ onEntry();
1981
+ zipFile.readEntry();
1982
+ })
1983
+ .catch(fail);
1984
+ });
1985
+ zipFile.readEntry();
1986
+ });
1987
+ });
1988
+ });
1989
+ const handleEntry = (zipFile, entry, resolvedDest) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
1990
+ const fileDest = path.resolve(resolvedDest, entry.fileName);
1991
+ // refuse anything that would write outside the install dir
1992
+ if (fileDest !== resolvedDest &&
1993
+ !fileDest.startsWith(resolvedDest + path.sep)) {
1994
+ throw new Error(`Refusing to extract entry outside of destination: ${entry.fileName}`);
2110
1995
  }
2111
- `,
2112
- };
2113
- const TAP_RUN_SPEC_QUERY = /* GraphQL */ `
2114
- mutation TapRunSpec($specPath: String!) {
2115
- runSpec(specPath: $specPath) {
2116
- __typename
2117
- ... on RunSpecResponse {
2118
- testingType
2119
- browser {
2120
- displayName
1996
+ const unixMode = (entry.externalFileAttributes >>> 16) & 0xffff;
1997
+ // Some archivers mark directories by Unix mode bits instead of (or in
1998
+ // addition to) a trailing slash; honor both so we don't extract a
1999
+ // directory entry as a zero-byte file.
2000
+ const isDir = /\/$/.test(entry.fileName) || (unixMode & S_IFMT) === S_IFDIR;
2001
+ const isSymlink = (unixMode & S_IFMT) === S_IFLNK;
2002
+ if (isDir) {
2003
+ yield fsp.mkdir(fileDest, { recursive: true });
2004
+ return;
2005
+ }
2006
+ yield fsp.mkdir(path.dirname(fileDest), { recursive: true });
2007
+ if (isSymlink) {
2008
+ if (entry.uncompressedSize > MAX_SYMLINK_TARGET_BYTES) {
2009
+ throw new Error(`Refusing to extract symlink with target larger than ${MAX_SYMLINK_TARGET_BYTES} bytes: ${entry.fileName}`);
2121
2010
  }
2122
- spec {
2123
- relative
2011
+ const linkTarget = yield readEntryAsString(zipFile, entry, MAX_SYMLINK_TARGET_BYTES);
2012
+ const resolvedTarget = path.resolve(path.dirname(fileDest), linkTarget);
2013
+ if (resolvedTarget !== resolvedDest &&
2014
+ !resolvedTarget.startsWith(resolvedDest + path.sep)) {
2015
+ throw new Error(`Refusing to extract symlink pointing outside of destination: ${entry.fileName} -> ${linkTarget}`);
2124
2016
  }
2125
- }
2126
- ... on RunSpecError {
2127
- code
2128
- detailMessage
2129
- }
2017
+ yield fsp.rm(fileDest, { recursive: true, force: true });
2018
+ yield fsp.symlink(linkTarget, fileDest);
2019
+ return;
2130
2020
  }
2131
- }
2132
- `;
2133
- const tapRunSpecOperation = (specPath) => {
2134
- return {
2135
- operationName: 'TapRunSpec',
2136
- query: TAP_RUN_SPEC_QUERY,
2137
- variables: { specPath },
2138
- };
2139
- };
2140
- tapOperations.tapRunSpecOperation = tapRunSpecOperation;
2141
-
2142
- return tapOperations;
2143
- }
2144
-
2145
- var hasRequiredDist;
2021
+ const readStream = yield new Promise((res, rej) => {
2022
+ zipFile.openReadStream(entry, (err, rs) => {
2023
+ if (err) {
2024
+ return rej(err);
2025
+ }
2026
+ return res(rs);
2027
+ });
2028
+ });
2029
+ // Preserve the Unix mode bits when present; otherwise fall back to a sane default.
2030
+ const fileMode = (unixMode & 0o7777) || 0o644;
2031
+ const writeStream = fs$1.createWriteStream(fileDest, { mode: fileMode });
2032
+ yield pipelineAsync(readStream, writeStream);
2033
+ });
2034
+ const readEntryAsString = (zipFile, entry, maxBytes) => {
2035
+ return new Promise((resolve, reject) => {
2036
+ zipFile.openReadStream(entry, (err, rs) => {
2037
+ if (err) {
2038
+ return reject(err);
2039
+ }
2040
+ const chunks = [];
2041
+ let received = 0;
2042
+ let bailed = false;
2043
+ const bail = (err) => {
2044
+ var _a;
2045
+ if (bailed)
2046
+ return;
2047
+ bailed = true;
2048
+ (_a = rs.destroy) === null || _a === void 0 ? void 0 : _a.call(rs, err);
2049
+ reject(err);
2050
+ };
2051
+ rs.on('data', (chunk) => {
2052
+ received += chunk.length;
2053
+ if (received > maxBytes) {
2054
+ bail(new Error(`Refusing to read entry body larger than ${maxBytes} bytes: ${entry.fileName}`));
2055
+ return;
2056
+ }
2057
+ chunks.push(chunk);
2058
+ });
2059
+ rs.on('end', () => {
2060
+ if (bailed)
2061
+ return;
2062
+ resolve(Buffer.concat(chunks).toString('utf8'));
2063
+ });
2064
+ rs.on('error', bail);
2065
+ });
2066
+ });
2067
+ };
2146
2068
 
2147
- function requireDist () {
2148
- if (hasRequiredDist) return dist;
2149
- hasRequiredDist = 1;
2150
- (function (exports$1) {
2151
- var __createBinding = (dist && dist.__createBinding) || (Object.create ? (function(o, m, k, k2) {
2152
- if (k2 === undefined) k2 = k;
2153
- var desc = Object.getOwnPropertyDescriptor(m, k);
2154
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
2155
- desc = { enumerable: true, get: function() { return m[k]; } };
2156
- }
2157
- Object.defineProperty(o, k2, desc);
2158
- }) : (function(o, m, k, k2) {
2159
- if (k2 === undefined) k2 = k;
2160
- o[k2] = m[k];
2161
- }));
2162
- var __exportStar = (dist && dist.__exportStar) || function(m, exports$1) {
2163
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports$1, p)) __createBinding(exports$1, m, p);
2164
- };
2165
- var __importDefault = (dist && dist.__importDefault) || function (mod) {
2166
- return (mod && mod.__esModule) ? mod : { "default": mod };
2167
- };
2168
- Object.defineProperty(exports$1, "__esModule", { value: true });
2169
- exports$1.isCompatibleRecord = exports$1.sessionProbePath = exports$1.recordPath = exports$1.cypressSessionsDir = exports$1.parseRecordPid = exports$1.recordFileName = exports$1.isTapSupportedBrowser = exports$1.TAP_SUPPORTED_BROWSER_FAMILY = exports$1.SESSION_ID_HEADER = exports$1.SESSIONS_ROUTE_PREFIX = exports$1.SESSIONS_DIRNAME = exports$1.MIN_SCHEMA_VERSION = exports$1.SCHEMA_VERSION = void 0;
2170
- const path_1 = __importDefault(path);
2171
- __exportStar(requireTapContract(), exports$1);
2172
- __exportStar(requireTapOperations(), exports$1);
2173
- exports$1.SCHEMA_VERSION = 1;
2174
- exports$1.MIN_SCHEMA_VERSION = 1;
2175
- exports$1.SESSIONS_DIRNAME = 'sessions';
2176
- const RECORD_EXTENSION = '.json';
2177
- exports$1.SESSIONS_ROUTE_PREFIX = '/__cypress/sessions/';
2178
- exports$1.SESSION_ID_HEADER = 'x-cypress-session-id';
2179
- exports$1.TAP_SUPPORTED_BROWSER_FAMILY = 'chromium';
2180
- // tap drives the browser over CDP, which only Chromium-based browsers speak. A
2181
- // null family means no browser is open yet — nothing to call unsupported.
2182
- const isTapSupportedBrowser = (browserFamily) => {
2183
- return browserFamily === null || browserFamily === exports$1.TAP_SUPPORTED_BROWSER_FAMILY;
2184
- };
2185
- exports$1.isTapSupportedBrowser = isTapSupportedBrowser;
2186
- const recordFileName = (pid) => {
2187
- return `${pid}${RECORD_EXTENSION}`;
2188
- };
2189
- exports$1.recordFileName = recordFileName;
2190
- const parseRecordPid = (entry) => {
2191
- if (path_1.default.extname(entry) !== RECORD_EXTENSION) {
2192
- return null;
2193
- }
2194
- const pid = Number(path_1.default.basename(entry, RECORD_EXTENSION));
2195
- return Number.isInteger(pid) ? pid : null;
2196
- };
2197
- exports$1.parseRecordPid = parseRecordPid;
2198
- const cypressSessionsDir = (cacheRoot) => {
2199
- return path_1.default.join(cacheRoot, exports$1.SESSIONS_DIRNAME);
2200
- };
2201
- exports$1.cypressSessionsDir = cypressSessionsDir;
2202
- const recordPath = (cacheRoot, pid) => {
2203
- return path_1.default.join((0, exports$1.cypressSessionsDir)(cacheRoot), (0, exports$1.recordFileName)(pid));
2204
- };
2205
- exports$1.recordPath = recordPath;
2206
- const sessionProbePath = (sessionId) => {
2207
- return `${exports$1.SESSIONS_ROUTE_PREFIX}${sessionId}`;
2208
- };
2209
- exports$1.sessionProbePath = sessionProbePath;
2210
- const isValidTestingType = (value) => {
2211
- return value === 'e2e' || value === 'component' || value === null;
2212
- };
2213
- const isCompatibleRecord = (record) => {
2214
- return Boolean(record)
2215
- && typeof record.schemaVersion === 'number'
2216
- && record.schemaVersion >= exports$1.MIN_SCHEMA_VERSION
2217
- && typeof record.pid === 'number'
2218
- && typeof record.projectRoot === 'string'
2219
- && Number.isInteger(record.serverPort)
2220
- && typeof record.sessionId === 'string'
2221
- && record.sessionId.length > 0
2222
- && isValidTestingType(record.testingType);
2223
- };
2224
- exports$1.isCompatibleRecord = isCompatibleRecord;
2225
-
2226
- } (dist));
2227
- return dist;
2069
+ const debug$3 = Debug('cypress:cli:unzip');
2070
+ const unzipTools = {
2071
+ extractWithYauzl,
2072
+ };
2073
+ // expose this function for simple testing
2074
+ const unzip = (_a) => xvfb.__awaiter(void 0, [_a], void 0, function* ({ zipFilePath, installDir, progress }) {
2075
+ debug$3('unzipping from %s', zipFilePath);
2076
+ debug$3('into', installDir);
2077
+ if (!zipFilePath) {
2078
+ throw new Error('Missing zip filename');
2079
+ }
2080
+ const startTime = Date.now();
2081
+ let yauzlDoneTime = 0;
2082
+ yield fs.ensureDir(installDir);
2083
+ yield new Promise((resolve, reject) => {
2084
+ // Open with lazyEntries so yauzl doesn't auto-emit entries (which would
2085
+ // require the fd to stay open for the duration of the OS-tool extraction).
2086
+ // We only need the entryCount here for the progress calculation.
2087
+ return yauzl.open(zipFilePath, { lazyEntries: true }, (err, zipFile) => {
2088
+ yauzlDoneTime = Date.now();
2089
+ if (err) {
2090
+ debug$3('error using yauzl %s', err.message);
2091
+ return reject(err);
2092
+ }
2093
+ const total = zipFile.entryCount;
2094
+ debug$3('zipFile entries count', total);
2095
+ // Close the count-only handle — the Node fallback re-opens the zip for extraction.
2096
+ zipFile.close();
2097
+ const started = new Date();
2098
+ let percent = 0;
2099
+ let count = 0;
2100
+ const notify = (percent) => {
2101
+ const elapsed = +new Date() - +started;
2102
+ const eta = xvfb.util.calculateEta(percent, elapsed);
2103
+ progress.onProgress(percent, xvfb.util.secsRemaining(eta));
2104
+ };
2105
+ const tick = () => {
2106
+ count += 1;
2107
+ percent = ((count / total) * 100);
2108
+ const displayPercent = percent.toFixed(0);
2109
+ return notify(Number(displayPercent));
2110
+ };
2111
+ const unzipWithNode = () => xvfb.__awaiter(void 0, void 0, void 0, function* () {
2112
+ debug$3('unzipping with node.js (slow)');
2113
+ try {
2114
+ yield unzipTools.extractWithYauzl(zipFilePath, installDir, tick);
2115
+ debug$3('node unzip finished');
2116
+ return resolve();
2117
+ }
2118
+ catch (err) {
2119
+ const error = err || new Error('Unknown error with Node extract tool');
2120
+ debug$3('error %s', error.message);
2121
+ return reject(error);
2122
+ }
2123
+ });
2124
+ const unzipFallback = _.once(unzipWithNode);
2125
+ const unzipWithUnzipTool = () => {
2126
+ debug$3('unzipping via `unzip`');
2127
+ const inflatingRe = /inflating:/;
2128
+ const sp = cp.spawn('unzip', ['-o', zipFilePath, '-d', installDir]);
2129
+ sp.on('error', (err) => {
2130
+ debug$3('unzip tool error: %s', err.message);
2131
+ unzipFallback();
2132
+ });
2133
+ sp.on('close', (code) => {
2134
+ debug$3('unzip tool close with code %d', code);
2135
+ if (code === 0) {
2136
+ percent = 100;
2137
+ notify(percent);
2138
+ return resolve();
2139
+ }
2140
+ debug$3('`unzip` failed %o', { code });
2141
+ return unzipFallback();
2142
+ });
2143
+ sp.stdout.on('data', (data) => {
2144
+ if (inflatingRe.test(data)) {
2145
+ return tick();
2146
+ }
2147
+ });
2148
+ sp.stderr.on('data', (data) => {
2149
+ debug$3('`unzip` stderr %s', data);
2150
+ });
2151
+ };
2152
+ // we attempt to first unzip with the native osx
2153
+ // ditto because its less likely to have problems
2154
+ // with corruption, symlinks, or icons causing failures
2155
+ // and can handle resource forks
2156
+ // http://automatica.com.au/2011/02/unzip-mac-os-x-zip-in-terminal/
2157
+ const unzipWithOsx = () => {
2158
+ debug$3('unzipping via `ditto`');
2159
+ const copyingFileRe = /^copying file/;
2160
+ const sp = cp.spawn('ditto', ['-xkV', zipFilePath, installDir]);
2161
+ // f-it just unzip with node
2162
+ sp.on('error', (err) => {
2163
+ debug$3(err.message);
2164
+ unzipFallback();
2165
+ });
2166
+ sp.on('close', (code) => {
2167
+ if (code === 0) {
2168
+ // make sure we get to 100% on the progress bar
2169
+ // because reading in lines is not really accurate
2170
+ percent = 100;
2171
+ notify(percent);
2172
+ return resolve();
2173
+ }
2174
+ debug$3('`ditto` failed %o', { code });
2175
+ return unzipFallback();
2176
+ });
2177
+ return readline.createInterface({
2178
+ input: sp.stderr,
2179
+ })
2180
+ .on('line', (line) => {
2181
+ if (copyingFileRe.test(line)) {
2182
+ return tick();
2183
+ }
2184
+ });
2185
+ };
2186
+ switch (os.platform()) {
2187
+ case 'darwin':
2188
+ return unzipWithOsx();
2189
+ case 'linux':
2190
+ return unzipWithUnzipTool();
2191
+ case 'win32':
2192
+ return unzipWithNode();
2193
+ default:
2194
+ return;
2195
+ }
2196
+ });
2197
+ });
2198
+ debug$3('unzip completed %o', {
2199
+ yauzlMs: yauzlDoneTime - startTime,
2200
+ unzipMs: Date.now() - yauzlDoneTime,
2201
+ });
2202
+ });
2203
+ function isMaybeWindowsMaxPathLengthError(err) {
2204
+ return os.platform() === 'win32' && err.code === 'ENOENT' && err.syscall === 'realpath';
2228
2205
  }
2229
-
2230
- var distExports = requireDist();
2231
-
2232
- const debug$3 = Debug('cypress:cli:cypress-sessions');
2233
- const PROBE_HOST = '127.0.0.1';
2234
- const DEFAULT_PROBE_TIMEOUT_MS = 2000;
2235
- const isPidAlive = (pid) => {
2236
- try {
2237
- process.kill(pid, 0);
2238
- return true;
2239
- }
2240
- catch (err) {
2241
- return (err === null || err === void 0 ? void 0 : err.code) === 'EPERM';
2242
- }
2243
- };
2244
- // The server reports `cdpBrowserWsUrl` from in-memory state that it only clears
2245
- // when the browser *process* exits, so a closed browser window whose process
2246
- // lingers leaves the url stale. Hit the browser's own DevTools HTTP endpoint
2247
- // (Chromium exposes `/json/version` whenever the CDP port is open) to confirm
2248
- // the browser is really reachable before trusting the url.
2249
- const cdpEndpointReachable = (cdpBrowserWsUrl, timeoutMs) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
2250
- let versionUrl;
2251
- try {
2252
- const { protocol, host } = new URL(cdpBrowserWsUrl);
2253
- versionUrl = `${protocol === 'wss:' ? 'https:' : 'http:'}//${host}/json/version`;
2254
- }
2255
- catch (err) {
2256
- debug$3('could not derive a CDP version url from %s: %o', cdpBrowserWsUrl, err);
2257
- return false;
2258
- }
2259
- try {
2260
- const response = yield fetch(versionUrl, { signal: AbortSignal.timeout(timeoutMs) });
2261
- return response.ok;
2262
- }
2263
- catch (err) {
2264
- debug$3('CDP endpoint unreachable at %s: %o', versionUrl, err);
2265
- return false;
2206
+ const start$1 = (_a) => xvfb.__awaiter(void 0, [_a], void 0, function* ({ zipFilePath, installDir, progress }) {
2207
+ assert.ok(_.isString(installDir) && !_.isEmpty(installDir), 'missing installDir');
2208
+ if (!progress) {
2209
+ progress = { onProgress: () => {
2210
+ return {};
2211
+ } };
2266
2212
  }
2267
- });
2268
- const verifySessionRecord = (record_1, ...args_1) => xvfb.__awaiter(void 0, [record_1, ...args_1], void 0, function* (record, timeoutMs = DEFAULT_PROBE_TIMEOUT_MS) {
2269
- const url = `http://${PROBE_HOST}:${record.serverPort}${distExports.sessionProbePath(record.sessionId)}`;
2270
2213
  try {
2271
- const response = yield fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
2272
- if (response.status !== 200) {
2273
- return null;
2274
- }
2275
- const live = yield response.json();
2276
- if (live.sessionId !== record.sessionId) {
2277
- return null;
2214
+ const installDirExists = yield fs.pathExists(installDir);
2215
+ if (installDirExists) {
2216
+ debug$3('removing existing unzipped binary', installDir);
2217
+ yield fs.remove(installDir);
2278
2218
  }
2279
- const cdpBrowserWsUrl = typeof live.cdpBrowserWsUrl === 'string' ? live.cdpBrowserWsUrl : null;
2280
- const attached = cdpBrowserWsUrl !== null && (yield cdpEndpointReachable(cdpBrowserWsUrl, timeoutMs));
2281
- // The browser identity describes what the session has open, not what is
2282
- // reachable over CDP — a browser tap cannot drive still has to be nameable.
2283
- return Object.assign(Object.assign({}, record), { cdpBrowserWsUrl: attached ? cdpBrowserWsUrl : null, browserName: typeof live.browserName === 'string' ? live.browserName : null, browserFamily: typeof live.browserFamily === 'string' ? live.browserFamily : null, machineId: typeof live.machineId === 'string' ? live.machineId : null, userId: typeof live.userId === 'string' ? live.userId : null });
2219
+ yield unzip({ zipFilePath, installDir, progress });
2284
2220
  }
2285
2221
  catch (err) {
2286
- debug$3('liveness probe failed for pid %d on port %d: %o', record.pid, record.serverPort, err);
2287
- return null;
2222
+ const errorTemplate = isMaybeWindowsMaxPathLengthError(err) ?
2223
+ xvfb.errors.failedUnzipWindowsMaxPathLength
2224
+ : xvfb.errors.failedUnzip;
2225
+ yield xvfb.throwFormErrorText(errorTemplate)(err);
2288
2226
  }
2289
2227
  });
2290
-
2291
- const debug$2 = Debug('cypress:cli:cypress-sessions');
2292
- const getSessionsDir = () => {
2293
- return distExports.cypressSessionsDir(xvfb.stateModule.getCacheDir());
2228
+ const unzipModule = {
2229
+ start: start$1,
2230
+ utils: {
2231
+ unzip,
2232
+ unzipTools,
2233
+ },
2294
2234
  };
2295
- const listRecordFiles = (dir) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
2296
- let entries;
2297
- try {
2298
- entries = yield fs.readdir(dir);
2299
- }
2300
- catch (err) {
2301
- if (err.code === 'ENOENT') {
2302
- return [];
2303
- }
2304
- throw err;
2235
+
2236
+ const debug$2 = Debug('cypress:cli:install');
2237
+ function _getBinaryUrlFromBuildInfo(version, arch, { commitSha, commitBranch }) {
2238
+ const platform = os.platform();
2239
+ if ((platform === 'win32') && (arch === 'arm64')) {
2240
+ debug$2(`detected platform ${platform} architecture ${arch} combination`);
2241
+ arch = 'x64';
2242
+ debug$2(`overriding to download ${platform}-${arch} pre-release binary instead`);
2305
2243
  }
2306
- const files = [];
2307
- for (const entry of entries) {
2308
- const pid = distExports.parseRecordPid(entry);
2309
- if (pid !== null) {
2310
- files.push({ path: path.join(dir, entry), pid });
2311
- }
2244
+ return `https://cdn.cypress.io/beta/binary/${version}/${platform}-${arch}/${commitBranch}-${commitSha}/cypress.zip`;
2245
+ }
2246
+ const alreadyInstalledMsg = () => {
2247
+ if (!xvfb.util.isPostInstall()) {
2248
+ xvfb.loggerModule.log(commonTags.stripIndent `
2249
+ Skipping installation:
2250
+
2251
+ Pass the ${chalk.yellow('--force')} option if you'd like to reinstall anyway.
2252
+ `);
2312
2253
  }
2313
- return files;
2254
+ };
2255
+ const displayCompletionMsg = () => {
2256
+ xvfb.loggerModule.log();
2257
+ xvfb.loggerModule.log('You can now open Cypress by running one of the following, depending on your package manager:');
2258
+ xvfb.loggerModule.log();
2259
+ xvfb.loggerModule.log(chalk.cyan('- npx cypress open'));
2260
+ xvfb.loggerModule.log(chalk.cyan('- yarn cypress open'));
2261
+ xvfb.loggerModule.log(chalk.cyan('- pnpm cypress open'));
2262
+ xvfb.loggerModule.log();
2263
+ xvfb.loggerModule.log(chalk.grey('https://on.cypress.io/opening-the-app'));
2264
+ xvfb.loggerModule.log();
2265
+ };
2266
+ const validateOS = () => xvfb.__awaiter(void 0, void 0, void 0, function* () {
2267
+ const platformInfo = yield xvfb.util.getPlatformInfo();
2268
+ return platformInfo.match(/(win32-x64|win32-arm64|linux-x64|linux-arm64|darwin-x64|darwin-arm64)/);
2314
2269
  });
2315
- const readCompatibleRecord = (filePath) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
2316
- let record;
2317
- try {
2318
- record = yield fs.readJson(filePath);
2270
+ /**
2271
+ * Returns the version to install - either a string like `1.2.3` to be fetched
2272
+ * from the download server or a file path or HTTP URL.
2273
+ */
2274
+ function getVersionOverride(version, { arch, envVarVersion, buildInfo }) {
2275
+ // let this environment variable reset the binary version we need
2276
+ if (envVarVersion) {
2277
+ return envVarVersion;
2319
2278
  }
2320
- catch (err) {
2321
- debug$2('could not read cypress sessions record %s: %o', filePath, err);
2322
- return null;
2279
+ if (buildInfo && !buildInfo.stable) {
2280
+ xvfb.loggerModule.log(chalk.yellow(commonTags.stripIndent `
2281
+ ${logSymbols.warning} Warning: You are installing a pre-release build of Cypress.
2282
+
2283
+ Bugs may be present which do not exist in production builds.
2284
+
2285
+ This build was created from:
2286
+ * Commit SHA: ${buildInfo.commitSha}
2287
+ * Commit Branch: ${buildInfo.commitBranch}
2288
+ * Commit Timestamp: ${buildInfo.commitDate}
2289
+ `));
2290
+ xvfb.loggerModule.log();
2291
+ return _getBinaryUrlFromBuildInfo(version, arch, buildInfo);
2323
2292
  }
2324
- if (!distExports.isCompatibleRecord(record)) {
2325
- debug$2('incompatible cypress sessions record %s', filePath);
2326
- return null;
2293
+ }
2294
+ function getEnvVarVersion() {
2295
+ if (!xvfb.util.getEnv('CYPRESS_INSTALL_BINARY'))
2296
+ return;
2297
+ // because passed file paths are often double quoted
2298
+ // and might have extra whitespace around, be robust and trim the string
2299
+ const trimAndRemoveDoubleQuotes = true;
2300
+ const envVarVersion = xvfb.util.getEnv('CYPRESS_INSTALL_BINARY', trimAndRemoveDoubleQuotes);
2301
+ debug$2('using environment variable CYPRESS_INSTALL_BINARY "%s"', envVarVersion);
2302
+ return envVarVersion;
2303
+ }
2304
+ const start = (...args_1) => xvfb.__awaiter(void 0, [...args_1], void 0, function* (options = {}) {
2305
+ debug$2('installing with options %j', options);
2306
+ const envVarVersion = getEnvVarVersion();
2307
+ if (envVarVersion === '0') {
2308
+ debug$2('environment variable CYPRESS_INSTALL_BINARY = 0, skipping install');
2309
+ xvfb.loggerModule.log(commonTags.stripIndent `
2310
+ ${chalk.yellow('Note:')} Skipping binary installation: Environment variable CYPRESS_INSTALL_BINARY = 0.`);
2311
+ xvfb.loggerModule.log();
2312
+ return;
2327
2313
  }
2328
- return record;
2329
- });
2330
- // Reaps a record whose writer process is gone, returning whether it was dead.
2331
- // Removal is best-effort: a file we can't delete (permissions, a Windows lock)
2332
- // must not abort discovery of the other, live sessions, so the failure is
2333
- // swallowed and the record is still reported dead.
2334
- const reapIfDead = (file) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
2335
- const { path, pid } = file;
2336
- if (isPidAlive(pid)) {
2337
- return false;
2314
+ const pkgPath = xvfb.relativeToRepoRoot('package.json');
2315
+ if (!pkgPath) {
2316
+ return xvfb.throwFormErrorText('Could not find package.json for Cypress package to determine build information')();
2338
2317
  }
2339
- yield fs.remove(path).catch((err) => {
2340
- debug$2('failed to reap dead cypress sessions record %s: %o', path, err);
2318
+ const { buildInfo, version } = JSON.parse(yield fsp.readFile(pkgPath, 'utf8'));
2319
+ _.defaults(options, {
2320
+ force: false,
2321
+ buildInfo,
2341
2322
  });
2342
- return true;
2343
- });
2344
- /**
2345
- * Reads all the current cypress session records that are still live (i.e. the writer process is still running).
2346
- * Reaps any dead records.
2347
- */
2348
- const readLiveSessions = () => xvfb.__awaiter(void 0, void 0, void 0, function* () {
2349
- const files = yield listRecordFiles(getSessionsDir());
2350
- const records = yield Promise.all(files.map((file) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
2351
- const record = yield readCompatibleRecord(file.path);
2352
- if (!record) {
2353
- return null;
2354
- }
2355
- if (yield reapIfDead(file)) {
2356
- return null;
2357
- }
2358
- return record;
2359
- })));
2360
- return records.filter((record) => record !== null);
2361
- });
2362
- const pruneDeadSessionRecords = () => xvfb.__awaiter(void 0, void 0, void 0, function* () {
2363
- const files = yield listRecordFiles(getSessionsDir());
2364
- const pruned = yield Promise.all(files.map((file) => reapIfDead(file)));
2365
- const removed = pruned.filter(Boolean).length;
2366
- debug$2('pruned %d dead cypress sessions record(s)', removed);
2367
- return removed;
2368
- });
2369
-
2370
- dayjs.extend(relativeTime);
2371
- // Subdirs under the cache root that are not binary version dirs. SESSIONS_DIRNAME
2372
- // is sourced from cypress-sessions so a rename there can't silently make prune
2373
- // treat the sessions dir as a stale binary cache and delete live records.
2374
- const EXTERNAL_CACHE_ENTRIES = new Set(['bundles', distExports.SESSIONS_DIRNAME]);
2375
- // output colors for the table
2376
- const colors = {
2377
- titles: chalk.white,
2378
- dates: chalk.cyan,
2379
- values: chalk.green,
2380
- size: chalk.gray,
2381
- };
2382
- const logCachePath = () => {
2383
- xvfb.loggerModule.always(xvfb.stateModule.getCacheDir());
2384
- return undefined;
2385
- };
2386
- const clear = () => {
2387
- return fs.remove(xvfb.stateModule.getCacheDir());
2388
- };
2389
- const prune = () => xvfb.__awaiter(void 0, void 0, void 0, function* () {
2323
+ if (xvfb.util.getEnv('CYPRESS_CACHE_FOLDER')) {
2324
+ const envCache = xvfb.util.getEnv('CYPRESS_CACHE_FOLDER');
2325
+ xvfb.loggerModule.log(commonTags.stripIndent `
2326
+ ${chalk.yellow('Note:')} Overriding Cypress cache directory to: ${chalk.cyan(envCache)}
2327
+
2328
+ Previous installs of Cypress may not be found.
2329
+ `);
2330
+ xvfb.loggerModule.log();
2331
+ }
2332
+ const pkgVersion = xvfb.util.pkgVersion();
2333
+ const arch = yield xvfb.util.getRealArch();
2334
+ const versionOverride = getVersionOverride(version, { arch, envVarVersion, buildInfo: options.buildInfo });
2335
+ const versionToInstall = versionOverride || pkgVersion;
2336
+ debug$2('version in package.json is %s, version to install is %s', pkgVersion, versionToInstall);
2337
+ const installDir = xvfb.stateModule.getVersionDir(pkgVersion, options.buildInfo);
2390
2338
  const cacheDir = xvfb.stateModule.getCacheDir();
2391
- const checkedInBinaryVersion = xvfb.util.pkgVersion();
2392
- let deletedBinary = false;
2339
+ const binaryDir = xvfb.stateModule.getBinaryDir(pkgVersion);
2340
+ if (!(yield validateOS())) {
2341
+ return xvfb.throwFormErrorText(xvfb.errors.invalidOS)();
2342
+ }
2393
2343
  try {
2394
- const versions = yield fs.readdir(cacheDir);
2395
- for (const version of versions) {
2396
- if (EXTERNAL_CACHE_ENTRIES.has(version))
2397
- continue;
2398
- if (version !== checkedInBinaryVersion) {
2399
- deletedBinary = true;
2400
- const versionDir = path.join(cacheDir, version);
2401
- yield fs.remove(versionDir);
2402
- }
2403
- }
2404
- if (deletedBinary) {
2405
- xvfb.loggerModule.always(`Deleted all binary caches except for the ${checkedInBinaryVersion} binary cache.`);
2344
+ yield fs.ensureDir(cacheDir);
2345
+ }
2346
+ catch (err) {
2347
+ if (err instanceof Error && 'code' in err && err.code === 'EACCES') {
2348
+ return xvfb.throwFormErrorText(xvfb.errors.invalidCacheDirectory)(commonTags.stripIndent `
2349
+ Failed to access ${chalk.cyan(cacheDir)}:
2350
+
2351
+ ${err.message}
2352
+ `);
2406
2353
  }
2407
2354
  else {
2408
- xvfb.loggerModule.always(`No binary caches found to prune.`);
2409
- }
2410
- yield pruneDeadSessionRecords();
2411
- }
2412
- catch (e) {
2413
- if (e.code === 'ENOENT') {
2414
- xvfb.loggerModule.always(`No Cypress cache was found at ${cacheDir}. Nothing to prune.`);
2415
- return;
2355
+ throw err;
2416
2356
  }
2417
- throw e;
2418
- }
2419
- });
2420
- const fileSizeInMB = (size) => {
2421
- return `${(size / 1024 / 1024).toFixed(1)}MB`;
2422
- };
2423
- /**
2424
- * Collects all cached versions, finds when each was used
2425
- * and prints a table with results to the terminal
2426
- */
2427
- const list = (...args_1) => xvfb.__awaiter(void 0, [...args_1], void 0, function* (showSize = false) {
2428
- const binaries = yield getCachedVersions(showSize);
2429
- const head = [colors.titles('version'), colors.titles('last used')];
2430
- if (showSize) {
2431
- head.push(colors.titles('size'));
2432
2357
  }
2433
- const table = new Table({
2434
- head,
2435
- });
2436
- binaries.forEach((binary) => {
2437
- const versionString = colors.values(binary.version);
2438
- const lastUsed = binary.accessed ? colors.dates(binary.accessed) : 'unknown';
2439
- const row = [versionString, lastUsed];
2440
- if (showSize) {
2441
- const size = colors.size(fileSizeInMB(binary.size));
2442
- row.push(size);
2358
+ const binaryPkg = yield xvfb.stateModule.getBinaryPkgAsync(binaryDir);
2359
+ const binaryVersion = yield xvfb.stateModule.getBinaryPkgVersion(binaryPkg);
2360
+ const shouldInstall = () => {
2361
+ if (!binaryVersion) {
2362
+ debug$2('no binary installed under cli version');
2363
+ return true;
2443
2364
  }
2444
- return table.push(row);
2445
- });
2446
- xvfb.loggerModule.always(table.toString());
2447
- });
2448
- const getCachedVersions = (showSize) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
2449
- const cacheDir = xvfb.stateModule.getCacheDir();
2450
- const versions = yield fs.readdir(cacheDir);
2451
- const filteredVersions = versions.filter(xvfb.util.isSemver).map((version) => {
2452
- return {
2453
- version,
2454
- folderPath: path.join(cacheDir, version),
2455
- };
2456
- });
2457
- const binaries = [];
2458
- for (const binary of filteredVersions) {
2459
- const binaryDir = xvfb.stateModule.getBinaryDir(binary.version);
2460
- const executable = xvfb.stateModule.getPathToExecutable(binaryDir);
2461
- try {
2462
- const stat = yield fs.stat(executable);
2463
- const lastAccessedTime = _.get(stat, 'atime');
2464
- if (lastAccessedTime) {
2465
- const accessed = dayjs(lastAccessedTime).fromNow();
2466
- // @ts-expect-error - accessed is not defined in the type
2467
- binary.accessed = accessed;
2468
- }
2469
- // if no lastAccessedTime
2470
- // the test runner has never been opened
2471
- // or could be a test simulating missing timestamp
2365
+ xvfb.loggerModule.log();
2366
+ xvfb.loggerModule.log(commonTags.stripIndent `
2367
+ Cypress ${chalk.green(binaryVersion)} is installed in ${chalk.cyan(installDir)}
2368
+ `);
2369
+ xvfb.loggerModule.log();
2370
+ if (options.force) {
2371
+ debug$2('performing force install over existing binary');
2372
+ return true;
2472
2373
  }
2473
- catch (e) {
2474
- // could not find the binary or gets its stats
2475
- // no-op
2374
+ if ((binaryVersion === versionToInstall) || !xvfb.util.isSemver(versionToInstall)) {
2375
+ // our version matches, tell the user this is a noop
2376
+ alreadyInstalledMsg();
2377
+ return false;
2476
2378
  }
2477
- if (showSize) {
2478
- const binaryDir = xvfb.stateModule.getBinaryDir(binary.version);
2479
- const size = yield getSize(binaryDir);
2480
- binaries.push(Object.assign(Object.assign({}, binary), { size }));
2379
+ return true;
2380
+ };
2381
+ // noop if we've been told not to download
2382
+ if (!shouldInstall()) {
2383
+ return debug$2('Not downloading or installing binary');
2384
+ }
2385
+ if (envVarVersion) {
2386
+ xvfb.loggerModule.log(chalk.yellow(commonTags.stripIndent `
2387
+ ${logSymbols.warning} Warning: Forcing a binary version different than the default.
2388
+
2389
+ The CLI expected to install version: ${chalk.green(pkgVersion)}
2390
+
2391
+ Instead we will install version: ${chalk.green(versionToInstall)}
2392
+
2393
+ These versions may not work properly together.
2394
+ `));
2395
+ xvfb.loggerModule.log();
2396
+ }
2397
+ const getLocalFilePath = () => xvfb.__awaiter(void 0, void 0, void 0, function* () {
2398
+ // see if version supplied is a path to a binary
2399
+ if (yield fs.pathExists(versionToInstall)) {
2400
+ return path.extname(versionToInstall) === '.zip' ? versionToInstall : false;
2481
2401
  }
2482
- else {
2483
- binaries.push(binary);
2402
+ const possibleFile = xvfb.util.formAbsolutePath(versionToInstall);
2403
+ debug$2('checking local file', possibleFile, 'cwd', process.cwd());
2404
+ // if this exists return the path to it
2405
+ // else false
2406
+ if ((yield fs.pathExists(possibleFile)) && path.extname(possibleFile) === '.zip') {
2407
+ return possibleFile;
2484
2408
  }
2409
+ return false;
2410
+ });
2411
+ const pathToLocalFile = yield getLocalFilePath();
2412
+ const tasks = pathToLocalFile ?
2413
+ installFromLocal(pathToLocalFile, installDir) :
2414
+ installFromRemote(versionToInstall, installDir);
2415
+ if (options.force) {
2416
+ debug$2('Cypress already installed at', installDir);
2417
+ debug$2('but the installation was forced');
2485
2418
  }
2486
- return binaries;
2419
+ // let the user know what version of cypress we're downloading!
2420
+ xvfb.loggerModule.log(`Installing Cypress ${chalk.gray(`(version: ${versionToInstall})`)}`);
2421
+ xvfb.loggerModule.log();
2422
+ const taskRunner = new listr2.Listr(tasks, Object.assign(Object.assign({
2423
+ // In CI we want timestamped, line-per-event output. Locally,
2424
+ // the default in-place spinner is the better experience.
2425
+ renderer: xvfb.util.isCi() ? 'verbose' : 'default' }, (xvfb.util.isCi() && { rendererOptions: { timestamp: listr2.PRESET_TIMESTAMP } })), { silentRendererCondition: () => xvfb.loggerModule.logLevel() === 'silent' }));
2426
+ yield taskRunner.run();
2427
+ // delay 1 sec for UX, unless we are testing
2428
+ yield timers.setTimeout(1000);
2429
+ displayCompletionMsg();
2487
2430
  });
2488
- const cacheModule = {
2489
- path: logCachePath,
2490
- clear,
2491
- prune,
2492
- list,
2493
- getCachedVersions,
2431
+ function downloadArchive(version, downloadDestination) {
2432
+ const inProgressTitle = 'Downloading Cypress';
2433
+ const completedTitle = chalk.green('Downloaded Cypress');
2434
+ return {
2435
+ title: xvfb.util.titleize(inProgressTitle),
2436
+ task: (ctx, task) => xvfb.__awaiter(this, void 0, void 0, function* () {
2437
+ yield downloadModule.start({
2438
+ version,
2439
+ downloadDestination,
2440
+ progress: {
2441
+ throttle: 100,
2442
+ onProgress: (percentComplete, remaining) => {
2443
+ task.title = progressTitle(inProgressTitle, percentComplete, remaining);
2444
+ },
2445
+ },
2446
+ });
2447
+ debug$2(`finished downloading file: ${downloadDestination}`);
2448
+ task.title = xvfb.util.titleize(completedTitle);
2449
+ }),
2450
+ };
2451
+ }
2452
+ function installFromLocal(pathToLocalFile, installDir) {
2453
+ const zipFilePath = path.resolve(pathToLocalFile);
2454
+ debug$2('found local file at', zipFilePath);
2455
+ debug$2('skipping download');
2456
+ return [
2457
+ unzipArchive(zipFilePath, installDir),
2458
+ ];
2459
+ }
2460
+ function installFromRemote(version, installDir) {
2461
+ const downloadDestination = path.join(os.tmpdir(), `cypress-${process.pid}.zip`);
2462
+ debug$2('preparing to download and unzip version ', version, 'to path', installDir);
2463
+ return [
2464
+ downloadArchive(version, downloadDestination),
2465
+ unzipArchive(downloadDestination, installDir),
2466
+ cleanup(downloadDestination, installDir),
2467
+ ];
2468
+ }
2469
+ function unzipArchive(zipFilePath, installDir) {
2470
+ const inProgressTitle = 'Unzipping Cypress';
2471
+ const completedTitle = chalk.green('Unzipped Cypress');
2472
+ return {
2473
+ title: xvfb.util.titleize(inProgressTitle),
2474
+ task: (ctx, task) => xvfb.__awaiter(this, void 0, void 0, function* () {
2475
+ yield unzipModule.start({
2476
+ zipFilePath,
2477
+ installDir,
2478
+ progress: {
2479
+ onProgress: (percentComplete, remaining) => {
2480
+ task.title = progressTitle(inProgressTitle, percentComplete, remaining);
2481
+ },
2482
+ },
2483
+ });
2484
+ task.title = xvfb.util.titleize(completedTitle);
2485
+ }),
2486
+ };
2487
+ }
2488
+ function cleanup(archiveLocation, installDir) {
2489
+ return {
2490
+ title: xvfb.util.titleize('Finishing Installation'),
2491
+ task: (ctx, task) => xvfb.__awaiter(this, void 0, void 0, function* () {
2492
+ debug$2('removing zip file %s', archiveLocation);
2493
+ yield fs.remove(archiveLocation);
2494
+ debug$2('finished installation in', installDir);
2495
+ task.title = xvfb.util.titleize(chalk.green('Finished Installation'), chalk.gray(installDir));
2496
+ }),
2497
+ };
2498
+ }
2499
+ function progressTitle(title, percentComplete, remaining) {
2500
+ return xvfb.util.titleize(title, chalk.white(` ${percentComplete}%`), chalk.gray(`${remaining}s`));
2501
+ }
2502
+ var installModule = {
2503
+ start,
2504
+ _getBinaryUrlFromBuildInfo,
2494
2505
  };
2495
2506
 
2496
2507
  const debug$1 = Debug('cypress:cli');
@@ -2546,58 +2557,14 @@ const g = chalk.green;
2546
2557
  // color for paths
2547
2558
  const p = chalk.cyan;
2548
2559
  const red = chalk.red;
2549
- // urls
2550
- const link = chalk.blue.underline;
2551
2560
  // to be exported
2552
2561
  const methods = {};
2553
- methods.findProxyEnvironmentVariables = () => {
2554
- return _.pick(process.env, ['HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY']);
2555
- };
2556
- const maskSensitiveVariables = (obj) => {
2557
- const masked = Object.assign({}, obj);
2558
- if (masked.CYPRESS_RECORD_KEY) {
2559
- masked.CYPRESS_RECORD_KEY = '<redacted>';
2560
- }
2561
- return masked;
2562
- };
2563
- methods.findCypressEnvironmentVariables = () => {
2564
- const isCyVariable = (val, key) => key.startsWith('CYPRESS_');
2565
- return _.pickBy(process.env, isCyVariable);
2566
- };
2567
- const formatCypressVariables = () => {
2568
- const vars = methods.findCypressEnvironmentVariables();
2569
- return maskSensitiveVariables(vars);
2570
- };
2571
2562
  methods.start = (...args_1) => xvfb.__awaiter(void 0, [...args_1], void 0, function* (options = {}) {
2572
2563
  const args = ['--mode=info'];
2573
2564
  yield spawn.start$1(args, {
2574
2565
  dev: options.dev,
2575
2566
  });
2576
2567
  console.log();
2577
- const proxyVars = methods.findProxyEnvironmentVariables();
2578
- if (_.isEmpty(proxyVars)) {
2579
- console.log('Proxy Settings: none detected');
2580
- }
2581
- else {
2582
- console.log('Proxy Settings:');
2583
- _.forEach(proxyVars, (value, key) => {
2584
- console.log('%s: %s', key, g(value));
2585
- });
2586
- console.log();
2587
- console.log('Learn More: %s', link('https://on.cypress.io/proxy-configuration'));
2588
- console.log();
2589
- }
2590
- const cyVars = formatCypressVariables();
2591
- if (_.isEmpty(cyVars)) {
2592
- console.log('Environment Variables: none detected');
2593
- }
2594
- else {
2595
- console.log('Environment Variables:');
2596
- _.forEach(cyVars, (value, key) => {
2597
- console.log('%s: %s', key, g(value));
2598
- });
2599
- }
2600
- console.log();
2601
2568
  console.log('Application Data:', p(xvfb.util.getApplicationDataFolder()));
2602
2569
  console.log('Browser Profiles:', p(xvfb.util.getApplicationDataFolder('browsers')));
2603
2570
  console.log('Binary Caches: %s', p(xvfb.stateModule.getCacheDir()));
@@ -3121,7 +3088,7 @@ const cliModule = {
3121
3088
  .action(function (opts, args) {
3122
3089
  return xvfb.__awaiter(this, void 0, void 0, function* () {
3123
3090
  try {
3124
- const { default: tapModule } = yield Promise.resolve().then(function () { return require('./tap-BkTD6Abc.js'); });
3091
+ const { default: tapModule } = yield Promise.resolve().then(function () { return require('./tap-tSM1ve9x.js'); });
3125
3092
  const code = yield tapModule.start(args || [], _.pick(opts, ['session', 'json', 'timeout']));
3126
3093
  process.exit(code);
3127
3094
  }