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) | |
|   }) | |
| }
 | |
| 
 |