You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							438 lines
						
					
					
						
							14 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							438 lines
						
					
					
						
							14 KiB
						
					
					
				
								// Copyright 2012 The Obvious Corporation.
							 | 
						|
								
							 | 
						|
								/*
							 | 
						|
								 * This simply fetches the right version of phantom for the current platform.
							 | 
						|
								 */
							 | 
						|
								
							 | 
						|
								'use strict'
							 | 
						|
								
							 | 
						|
								var requestProgress = require('request-progress')
							 | 
						|
								var progress = require('progress')
							 | 
						|
								var extractZip = require('extract-zip')
							 | 
						|
								var cp = require('child_process')
							 | 
						|
								var fs = require('fs-extra')
							 | 
						|
								var helper = require('./lib/phantomjs')
							 | 
						|
								var kew = require('kew')
							 | 
						|
								var path = require('path')
							 | 
						|
								var request = require('request')
							 | 
						|
								var url = require('url')
							 | 
						|
								var util = require('./lib/util')
							 | 
						|
								var which = require('which')
							 | 
						|
								var os = require('os')
							 | 
						|
								
							 | 
						|
								var originalPath = process.env.PATH
							 | 
						|
								
							 | 
						|
								var checkPhantomjsVersion = util.checkPhantomjsVersion
							 | 
						|
								var getTargetPlatform = util.getTargetPlatform
							 | 
						|
								var getTargetArch = util.getTargetArch
							 | 
						|
								var getDownloadSpec = util.getDownloadSpec
							 | 
						|
								var findValidPhantomJsBinary = util.findValidPhantomJsBinary
							 | 
						|
								var verifyChecksum = util.verifyChecksum
							 | 
						|
								var writeLocationFile = util.writeLocationFile
							 | 
						|
								
							 | 
						|
								// If the process exits without going through exit(), then we did not complete.
							 | 
						|
								var validExit = false
							 | 
						|
								
							 | 
						|
								process.on('exit', function () {
							 | 
						|
								  if (!validExit) {
							 | 
						|
								    console.log('Install exited unexpectedly')
							 | 
						|
								    exit(1)
							 | 
						|
								  }
							 | 
						|
								})
							 | 
						|
								
							 | 
						|
								// NPM adds bin directories to the path, which will cause `which` to find the
							 | 
						|
								// bin for this package not the actual phantomjs bin.  Also help out people who
							 | 
						|
								// put ./bin on their path
							 | 
						|
								process.env.PATH = helper.cleanPath(originalPath)
							 | 
						|
								
							 | 
						|
								var libPath = path.join(__dirname, 'lib')
							 | 
						|
								var pkgPath = path.join(libPath, 'phantom')
							 | 
						|
								var phantomPath = null
							 | 
						|
								
							 | 
						|
								// If the user manually installed PhantomJS, we want
							 | 
						|
								// to use the existing version.
							 | 
						|
								//
							 | 
						|
								// Do not re-use a manually-installed PhantomJS with
							 | 
						|
								// a different version.
							 | 
						|
								//
							 | 
						|
								// Do not re-use an npm-installed PhantomJS, because
							 | 
						|
								// that can lead to weird circular dependencies between
							 | 
						|
								// local versions and global versions.
							 | 
						|
								// https://github.com/Obvious/phantomjs/issues/85
							 | 
						|
								// https://github.com/Medium/phantomjs/pull/184
							 | 
						|
								kew.resolve(true)
							 | 
						|
								  .then(tryPhantomjsInLib)
							 | 
						|
								  .then(tryPhantomjsOnPath)
							 | 
						|
								  .then(downloadPhantomjs)
							 | 
						|
								  .then(extractDownload)
							 | 
						|
								  .then(function (extractedPath) {
							 | 
						|
								    return copyIntoPlace(extractedPath, pkgPath)
							 | 
						|
								  })
							 | 
						|
								  .then(function () {
							 | 
						|
								    var location = getTargetPlatform() === 'win32' ?
							 | 
						|
								        path.join(pkgPath, 'bin', 'phantomjs.exe') :
							 | 
						|
								        path.join(pkgPath, 'bin' ,'phantomjs')
							 | 
						|
								
							 | 
						|
								    try {
							 | 
						|
								      // Ensure executable is executable by all users
							 | 
						|
								      fs.chmodSync(location, '755')
							 | 
						|
								    } catch (err) {
							 | 
						|
								      if (err.code == 'ENOENT') {
							 | 
						|
								        console.error('chmod failed: phantomjs was not successfully copied to', location)
							 | 
						|
								        exit(1)
							 | 
						|
								      }
							 | 
						|
								      throw err
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    var relativeLocation = path.relative(libPath, location)
							 | 
						|
								    writeLocationFile(relativeLocation)
							 | 
						|
								
							 | 
						|
								    console.log('Done. Phantomjs binary available at', location)
							 | 
						|
								    exit(0)
							 | 
						|
								  })
							 | 
						|
								  .fail(function (err) {
							 | 
						|
								    console.error('Phantom installation failed', err, err.stack)
							 | 
						|
								    exit(1)
							 | 
						|
								  })
							 | 
						|
								
							 | 
						|
								function exit(code) {
							 | 
						|
								  validExit = true
							 | 
						|
								  process.env.PATH = originalPath
							 | 
						|
								  process.exit(code || 0)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								function findSuitableTempDirectory() {
							 | 
						|
								  var now = Date.now()
							 | 
						|
								  var candidateTmpDirs = [
							 | 
						|
								    process.env.npm_config_tmp,
							 | 
						|
								    os.tmpdir(),
							 | 
						|
								    path.join(process.cwd(), 'tmp')
							 | 
						|
								  ]
							 | 
						|
								
							 | 
						|
								  for (var i = 0; i < candidateTmpDirs.length; i++) {
							 | 
						|
								    var candidatePath = candidateTmpDirs[i]
							 | 
						|
								    if (!candidatePath) continue
							 | 
						|
								
							 | 
						|
								    try {
							 | 
						|
								      candidatePath = path.join(path.resolve(candidatePath), 'phantomjs')
							 | 
						|
								      fs.mkdirsSync(candidatePath, '0777')
							 | 
						|
								      // Make double sure we have 0777 permissions; some operating systems
							 | 
						|
								      // default umask does not allow write by default.
							 | 
						|
								      fs.chmodSync(candidatePath, '0777')
							 | 
						|
								      var testFile = path.join(candidatePath, now + '.tmp')
							 | 
						|
								      fs.writeFileSync(testFile, 'test')
							 | 
						|
								      fs.unlinkSync(testFile)
							 | 
						|
								      return candidatePath
							 | 
						|
								    } catch (e) {
							 | 
						|
								      console.log(candidatePath, 'is not writable:', e.message)
							 | 
						|
								    }
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  console.error('Can not find a writable tmp directory, please report issue ' +
							 | 
						|
								      'on https://github.com/Medium/phantomjs/issues with as much ' +
							 | 
						|
								      'information as possible.')
							 | 
						|
								  exit(1)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								function getRequestOptions() {
							 | 
						|
								  var strictSSL = !!process.env.npm_config_strict_ssl
							 | 
						|
								  if (process.version == 'v0.10.34') {
							 | 
						|
								    console.log('Node v0.10.34 detected, turning off strict ssl due to https://github.com/joyent/node/issues/8894')
							 | 
						|
								    strictSSL = false
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  var options = {
							 | 
						|
								    uri: getDownloadUrl(),
							 | 
						|
								    encoding: null, // Get response as a buffer
							 | 
						|
								    followRedirect: true, // The default download path redirects to a CDN URL.
							 | 
						|
								    headers: {},
							 | 
						|
								    strictSSL: strictSSL
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  var proxyUrl = process.env.npm_config_https_proxy ||
							 | 
						|
								      process.env.npm_config_http_proxy ||
							 | 
						|
								      process.env.npm_config_proxy
							 | 
						|
								  if (proxyUrl) {
							 | 
						|
								
							 | 
						|
								    // Print using proxy
							 | 
						|
								    var proxy = url.parse(proxyUrl)
							 | 
						|
								    if (proxy.auth) {
							 | 
						|
								      // Mask password
							 | 
						|
								      proxy.auth = proxy.auth.replace(/:.*$/, ':******')
							 | 
						|
								    }
							 | 
						|
								    console.log('Using proxy ' + url.format(proxy))
							 | 
						|
								
							 | 
						|
								    // Enable proxy
							 | 
						|
								    options.proxy = proxyUrl
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  // Use the user-agent string from the npm config
							 | 
						|
								  options.headers['User-Agent'] = process.env.npm_config_user_agent
							 | 
						|
								
							 | 
						|
								  // Use certificate authority settings from npm
							 | 
						|
								  var ca = process.env.npm_config_ca
							 | 
						|
								  if (!ca && process.env.npm_config_cafile) {
							 | 
						|
								    try {
							 | 
						|
								      ca = fs.readFileSync(process.env.npm_config_cafile, {encoding: 'utf8'})
							 | 
						|
								        .split(/\n(?=-----BEGIN CERTIFICATE-----)/g)
							 | 
						|
								
							 | 
						|
								      // Comments at the beginning of the file result in the first
							 | 
						|
								      // item not containing a certificate - in this case the
							 | 
						|
								      // download will fail
							 | 
						|
								      if (ca.length > 0 && !/-----BEGIN CERTIFICATE-----/.test(ca[0])) {
							 | 
						|
								        ca.shift()
							 | 
						|
								      }
							 | 
						|
								
							 | 
						|
								    } catch (e) {
							 | 
						|
								      console.error('Could not read cafile', process.env.npm_config_cafile, e)
							 | 
						|
								    }
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  if (ca) {
							 | 
						|
								    console.log('Using npmconf ca')
							 | 
						|
								    options.agentOptions = {
							 | 
						|
								      ca: ca
							 | 
						|
								    }
							 | 
						|
								    options.ca = ca
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  return options
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								function handleRequestError(error) {
							 | 
						|
								  if (error && error.stack && error.stack.indexOf('SELF_SIGNED_CERT_IN_CHAIN') != -1) {
							 | 
						|
								      console.error('Error making request, SELF_SIGNED_CERT_IN_CHAIN. ' +
							 | 
						|
								          'Please read https://github.com/Medium/phantomjs#i-am-behind-a-corporate-proxy-that-uses-self-signed-ssl-certificates-to-intercept-encrypted-traffic')
							 | 
						|
								      exit(1)
							 | 
						|
								  } else if (error) {
							 | 
						|
								    console.error('Error making request.\n' + error.stack + '\n\n' +
							 | 
						|
								        'Please report this full log at https://github.com/Medium/phantomjs')
							 | 
						|
								    exit(1)
							 | 
						|
								  } else {
							 | 
						|
								    console.error('Something unexpected happened, please report this full ' +
							 | 
						|
								        'log at https://github.com/Medium/phantomjs')
							 | 
						|
								    exit(1)
							 | 
						|
								  }
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								function requestBinary(requestOptions, filePath) {
							 | 
						|
								  var deferred = kew.defer()
							 | 
						|
								
							 | 
						|
								  var writePath = filePath + '-download-' + Date.now()
							 | 
						|
								
							 | 
						|
								  console.log('Receiving...')
							 | 
						|
								  var bar = null
							 | 
						|
								  requestProgress(request(requestOptions, function (error, response, body) {
							 | 
						|
								    console.log('')
							 | 
						|
								    if (!error && response.statusCode === 200) {
							 | 
						|
								      fs.writeFileSync(writePath, body)
							 | 
						|
								      console.log('Received ' + Math.floor(body.length / 1024) + 'K total.')
							 | 
						|
								      fs.renameSync(writePath, filePath)
							 | 
						|
								      deferred.resolve(filePath)
							 | 
						|
								
							 | 
						|
								    } else if (response) {
							 | 
						|
								      console.error('Error requesting archive.\n' +
							 | 
						|
								          'Status: ' + response.statusCode + '\n' +
							 | 
						|
								          'Request options: ' + JSON.stringify(requestOptions, null, 2) + '\n' +
							 | 
						|
								          'Response headers: ' + JSON.stringify(response.headers, null, 2) + '\n' +
							 | 
						|
								          'Make sure your network and proxy settings are correct.\n\n' +
							 | 
						|
								          'If you continue to have issues, please report this full log at ' +
							 | 
						|
								          'https://github.com/Medium/phantomjs')
							 | 
						|
								      exit(1)
							 | 
						|
								    } else {
							 | 
						|
								      handleRequestError(error)
							 | 
						|
								    }
							 | 
						|
								  })).on('progress', function (state) {
							 | 
						|
								    try {
							 | 
						|
								      if (!bar) {
							 | 
						|
								        bar = new progress('  [:bar] :percent', {total: state.size.total, width: 40})
							 | 
						|
								      }
							 | 
						|
								      bar.curr = state.size.transferred
							 | 
						|
								      bar.tick()
							 | 
						|
								    } catch (e) {
							 | 
						|
								      // It doesn't really matter if the progress bar doesn't update.
							 | 
						|
								    }
							 | 
						|
								  })
							 | 
						|
								  .on('error', handleRequestError)
							 | 
						|
								
							 | 
						|
								  return deferred.promise
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								function extractDownload(filePath) {
							 | 
						|
								  var deferred = kew.defer()
							 | 
						|
								  // extract to a unique directory in case multiple processes are
							 | 
						|
								  // installing and extracting at once
							 | 
						|
								  var extractedPath = filePath + '-extract-' + Date.now()
							 | 
						|
								  var options = {cwd: extractedPath}
							 | 
						|
								
							 | 
						|
								  fs.mkdirsSync(extractedPath, '0777')
							 | 
						|
								  // Make double sure we have 0777 permissions; some operating systems
							 | 
						|
								  // default umask does not allow write by default.
							 | 
						|
								  fs.chmodSync(extractedPath, '0777')
							 | 
						|
								
							 | 
						|
								  if (filePath.substr(-4) === '.zip') {
							 | 
						|
								    console.log('Extracting zip contents')
							 | 
						|
								    extractZip(path.resolve(filePath), {dir: extractedPath}, function(err) {
							 | 
						|
								      if (err) {
							 | 
						|
								        console.error('Error extracting zip')
							 | 
						|
								        deferred.reject(err)
							 | 
						|
								      } else {
							 | 
						|
								        deferred.resolve(extractedPath)
							 | 
						|
								      }
							 | 
						|
								    })
							 | 
						|
								
							 | 
						|
								  } else {
							 | 
						|
								    console.log('Extracting tar contents (via spawned process)')
							 | 
						|
								    cp.execFile('tar', ['jxf', path.resolve(filePath)], options, function (err) {
							 | 
						|
								      if (err) {
							 | 
						|
								        console.error('Error extracting archive')
							 | 
						|
								        deferred.reject(err)
							 | 
						|
								      } else {
							 | 
						|
								        deferred.resolve(extractedPath)
							 | 
						|
								      }
							 | 
						|
								    })
							 | 
						|
								  }
							 | 
						|
								  return deferred.promise
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								function copyIntoPlace(extractedPath, targetPath) {
							 | 
						|
								  console.log('Removing', targetPath)
							 | 
						|
								  return kew.nfcall(fs.remove, targetPath).then(function () {
							 | 
						|
								    // Look for the extracted directory, so we can rename it.
							 | 
						|
								    var files = fs.readdirSync(extractedPath)
							 | 
						|
								    for (var i = 0; i < files.length; i++) {
							 | 
						|
								      var file = path.join(extractedPath, files[i])
							 | 
						|
								      if (fs.statSync(file).isDirectory() && file.indexOf(helper.version) != -1) {
							 | 
						|
								        console.log('Copying extracted folder', file, '->', targetPath)
							 | 
						|
								        return kew.nfcall(fs.move, file, targetPath)
							 | 
						|
								      }
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    console.log('Could not find extracted file', files)
							 | 
						|
								    throw new Error('Could not find extracted file')
							 | 
						|
								  })
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Check to see if the binary in lib is OK to use. If successful, exit the process.
							 | 
						|
								 */
							 | 
						|
								function tryPhantomjsInLib() {
							 | 
						|
								  return kew.fcall(function () {
							 | 
						|
								    return findValidPhantomJsBinary(path.resolve(__dirname, './lib/location.js'))
							 | 
						|
								  }).then(function (binaryLocation) {
							 | 
						|
								    if (binaryLocation) {
							 | 
						|
								      console.log('PhantomJS is previously installed at', binaryLocation)
							 | 
						|
								      exit(0)
							 | 
						|
								    }
							 | 
						|
								  }).fail(function () {
							 | 
						|
								    // silently swallow any errors
							 | 
						|
								  })
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Check to see if the binary on PATH is OK to use. If successful, exit the process.
							 | 
						|
								 */
							 | 
						|
								function tryPhantomjsOnPath() {
							 | 
						|
								  if (getTargetPlatform() != process.platform || getTargetArch() != process.arch) {
							 | 
						|
								    console.log('Building for target platform ' + getTargetPlatform() + '/' + getTargetArch() +
							 | 
						|
								                '. Skipping PATH search')
							 | 
						|
								    return kew.resolve(false)
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  return kew.nfcall(which, 'phantomjs')
							 | 
						|
								  .then(function (result) {
							 | 
						|
								    phantomPath = result
							 | 
						|
								    console.log('Considering PhantomJS found at', phantomPath)
							 | 
						|
								
							 | 
						|
								    // Horrible hack to avoid problems during global install. We check to see if
							 | 
						|
								    // the file `which` found is our own bin script.
							 | 
						|
								    if (phantomPath.indexOf(path.join('npm', 'phantomjs')) !== -1) {
							 | 
						|
								      console.log('Looks like an `npm install -g` on windows; skipping installed version.')
							 | 
						|
								      return
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    var contents = fs.readFileSync(phantomPath, 'utf8')
							 | 
						|
								    if (/NPM_INSTALL_MARKER/.test(contents)) {
							 | 
						|
								      console.log('Looks like an `npm install -g`')
							 | 
						|
								
							 | 
						|
								      var phantomLibPath = path.resolve(fs.realpathSync(phantomPath), '../../lib/location')
							 | 
						|
								      return findValidPhantomJsBinary(phantomLibPath)
							 | 
						|
								      .then(function (binaryLocation) {
							 | 
						|
								        if (binaryLocation) {
							 | 
						|
								          writeLocationFile(binaryLocation)
							 | 
						|
								          console.log('PhantomJS linked at', phantomLibPath)
							 | 
						|
								          exit(0)
							 | 
						|
								        }
							 | 
						|
								        console.log('Could not link global install, skipping...')
							 | 
						|
								      })
							 | 
						|
								    } else {
							 | 
						|
								      return checkPhantomjsVersion(phantomPath).then(function (matches) {
							 | 
						|
								        if (matches) {
							 | 
						|
								          writeLocationFile(phantomPath)
							 | 
						|
								          console.log('PhantomJS is already installed on PATH at', phantomPath)
							 | 
						|
								          exit(0)
							 | 
						|
								        }
							 | 
						|
								      })
							 | 
						|
								    }
							 | 
						|
								  }, function () {
							 | 
						|
								    console.log('PhantomJS not found on PATH')
							 | 
						|
								  })
							 | 
						|
								  .fail(function (err) {
							 | 
						|
								    console.error('Error checking path, continuing', err)
							 | 
						|
								    return false
							 | 
						|
								  })
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * @return {?string} Get the download URL for phantomjs.
							 | 
						|
								 *     May return null if no download url exists.
							 | 
						|
								 */
							 | 
						|
								function getDownloadUrl() {
							 | 
						|
								  var spec = getDownloadSpec()
							 | 
						|
								  return spec && spec.url
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Download phantomjs, reusing the existing copy on disk if available.
							 | 
						|
								 * Exits immediately if there is no binary to download.
							 | 
						|
								 * @return {Promise.<string>} The path to the downloaded file.
							 | 
						|
								 */
							 | 
						|
								function downloadPhantomjs() {
							 | 
						|
								  var downloadSpec = getDownloadSpec()
							 | 
						|
								  if (!downloadSpec) {
							 | 
						|
								    console.error(
							 | 
						|
								        'Unexpected platform or architecture: ' + getTargetPlatform() + '/' + getTargetArch() + '\n' +
							 | 
						|
								        'It seems there is no binary available for your platform/architecture\n' +
							 | 
						|
								        'Try to install PhantomJS globally')
							 | 
						|
								    exit(1)
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  var downloadUrl = downloadSpec.url
							 | 
						|
								  var downloadedFile
							 | 
						|
								
							 | 
						|
								  return kew.fcall(function () {
							 | 
						|
								    // Can't use a global version so start a download.
							 | 
						|
								    var tmpPath = findSuitableTempDirectory()
							 | 
						|
								    var fileName = downloadUrl.split('/').pop()
							 | 
						|
								    downloadedFile = path.join(tmpPath, fileName)
							 | 
						|
								
							 | 
						|
								    if (fs.existsSync(downloadedFile)) {
							 | 
						|
								      console.log('Download already available at', downloadedFile)
							 | 
						|
								      return verifyChecksum(downloadedFile, downloadSpec.checksum)
							 | 
						|
								    }
							 | 
						|
								    return false
							 | 
						|
								  }).then(function (verified) {
							 | 
						|
								    if (verified) {
							 | 
						|
								      return downloadedFile
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    // Start the install.
							 | 
						|
								    console.log('Downloading', downloadUrl)
							 | 
						|
								    console.log('Saving to', downloadedFile)
							 | 
						|
								    return requestBinary(getRequestOptions(), downloadedFile)
							 | 
						|
								  })
							 | 
						|
								}
							 | 
						|
								
							 |