UNPKG

@revoloo/cypress6

Version:

Cypress.io end to end testing tool

227 lines (175 loc) 5.56 kB
const _ = require('lodash') const la = require('lazy-ass') const is = require('check-more-types') const cp = require('child_process') const os = require('os') const yauzl = require('yauzl') const debug = require('debug')('cypress:cli:unzip') const extract = require('extract-zip') const Promise = require('bluebird') const readline = require('readline') const { throwFormErrorText, errors } = require('../errors') const fs = require('../fs') const util = require('../util') const unzipTools = { extract, } // expose this function for simple testing const unzip = ({ zipFilePath, installDir, progress }) => { debug('unzipping from %s', zipFilePath) debug('into', installDir) if (!zipFilePath) { throw new Error('Missing zip filename') } const startTime = Date.now() let yauzlDoneTime = 0 return fs.ensureDirAsync(installDir) .then(() => { return new Promise((resolve, reject) => { return yauzl.open(zipFilePath, (err, zipFile) => { yauzlDoneTime = Date.now() if (err) { debug('error using yauzl %s', err.message) return reject(err) } const total = zipFile.entryCount debug('zipFile entries count', total) const started = new Date() let percent = 0 let count = 0 const notify = (percent) => { const elapsed = +new Date() - +started const eta = util.calculateEta(percent, elapsed) progress.onProgress(percent, util.secsRemaining(eta)) } const tick = () => { count += 1 percent = ((count / total) * 100) const displayPercent = percent.toFixed(0) return notify(displayPercent) } const unzipWithNode = () => { debug('unzipping with node.js (slow)') const endFn = (err) => { if (err) { debug('error %s', err.message) return reject(err) } debug('node unzip finished') return resolve() } const opts = { dir: installDir, onEntry: tick, } debug('calling Node extract tool %s %o', zipFilePath, opts) return unzipTools.extract(zipFilePath, opts, endFn) } const unzipFallback = _.once(unzipWithNode) const unzipWithUnzipTool = () => { debug('unzipping via `unzip`') const inflatingRe = /inflating:/ const sp = cp.spawn('unzip', ['-o', zipFilePath, '-d', installDir]) sp.on('error', (err) => { debug('unzip tool error: %s', err.message) unzipFallback() }) sp.on('close', (code) => { debug('unzip tool close with code %d', code) if (code === 0) { percent = 100 notify(percent) return resolve() } debug('`unzip` failed %o', { code }) return unzipFallback() }) sp.stdout.on('data', (data) => { if (inflatingRe.test(data)) { return tick() } }) sp.stderr.on('data', (data) => { debug('`unzip` stderr %s', data) }) } // we attempt to first unzip with the native osx // ditto because its less likely to have problems // with corruption, symlinks, or icons causing failures // and can handle resource forks // http://automatica.com.au/2011/02/unzip-mac-os-x-zip-in-terminal/ const unzipWithOsx = () => { debug('unzipping via `ditto`') const copyingFileRe = /^copying file/ const sp = cp.spawn('ditto', ['-xkV', zipFilePath, installDir]) // f-it just unzip with node sp.on('error', (err) => { debug(err.message) unzipFallback() }) sp.on('close', (code) => { if (code === 0) { // make sure we get to 100% on the progress bar // because reading in lines is not really accurate percent = 100 notify(percent) return resolve() } debug('`ditto` failed %o', { code }) return unzipFallback() }) return readline.createInterface({ input: sp.stderr, }) .on('line', (line) => { if (copyingFileRe.test(line)) { return tick() } }) } switch (os.platform()) { case 'darwin': return unzipWithOsx() case 'linux': return unzipWithUnzipTool() case 'win32': return unzipWithNode() default: return } }) }) .tap(() => { debug('unzip completed %o', { yauzlMs: yauzlDoneTime - startTime, unzipMs: Date.now() - yauzlDoneTime, }) }) }) } const start = ({ zipFilePath, installDir, progress }) => { la(is.unemptyString(installDir), 'missing installDir') if (!progress) { progress = { onProgress: () => { return {} } } } return fs.pathExists(installDir) .then((exists) => { if (exists) { debug('removing existing unzipped binary', installDir) return fs.removeAsync(installDir) } }) .then(() => { return unzip({ zipFilePath, installDir, progress }) }) .catch(throwFormErrorText(errors.failedUnzip)) } module.exports = { start, utils: { unzip, unzipTools, }, }