Pathologic was missing because of a .git folder inside.
[yaffs-website] / node_modules / phantomjs-prebuilt / install.js
1 // Copyright 2012 The Obvious Corporation.
2
3 /*
4  * This simply fetches the right version of phantom for the current platform.
5  */
6
7 'use strict'
8
9 var requestProgress = require('request-progress')
10 var progress = require('progress')
11 var extractZip = require('extract-zip')
12 var cp = require('child_process')
13 var fs = require('fs-extra')
14 var helper = require('./lib/phantomjs')
15 var kew = require('kew')
16 var path = require('path')
17 var request = require('request')
18 var url = require('url')
19 var util = require('./lib/util')
20 var which = require('which')
21 var os = require('os')
22
23 var originalPath = process.env.PATH
24
25 var checkPhantomjsVersion = util.checkPhantomjsVersion
26 var getTargetPlatform = util.getTargetPlatform
27 var getTargetArch = util.getTargetArch
28 var getDownloadSpec = util.getDownloadSpec
29 var findValidPhantomJsBinary = util.findValidPhantomJsBinary
30 var verifyChecksum = util.verifyChecksum
31 var writeLocationFile = util.writeLocationFile
32
33 // If the process exits without going through exit(), then we did not complete.
34 var validExit = false
35
36 process.on('exit', function () {
37   if (!validExit) {
38     console.log('Install exited unexpectedly')
39     exit(1)
40   }
41 })
42
43 // NPM adds bin directories to the path, which will cause `which` to find the
44 // bin for this package not the actual phantomjs bin.  Also help out people who
45 // put ./bin on their path
46 process.env.PATH = helper.cleanPath(originalPath)
47
48 var libPath = path.join(__dirname, 'lib')
49 var pkgPath = path.join(libPath, 'phantom')
50 var phantomPath = null
51
52 // If the user manually installed PhantomJS, we want
53 // to use the existing version.
54 //
55 // Do not re-use a manually-installed PhantomJS with
56 // a different version.
57 //
58 // Do not re-use an npm-installed PhantomJS, because
59 // that can lead to weird circular dependencies between
60 // local versions and global versions.
61 // https://github.com/Obvious/phantomjs/issues/85
62 // https://github.com/Medium/phantomjs/pull/184
63 kew.resolve(true)
64   .then(tryPhantomjsInLib)
65   .then(tryPhantomjsOnPath)
66   .then(downloadPhantomjs)
67   .then(extractDownload)
68   .then(function (extractedPath) {
69     return copyIntoPlace(extractedPath, pkgPath)
70   })
71   .then(function () {
72     var location = getTargetPlatform() === 'win32' ?
73         path.join(pkgPath, 'bin', 'phantomjs.exe') :
74         path.join(pkgPath, 'bin' ,'phantomjs')
75
76     try {
77       // Ensure executable is executable by all users
78       fs.chmodSync(location, '755')
79     } catch (err) {
80       if (err.code == 'ENOENT') {
81         console.error('chmod failed: phantomjs was not successfully copied to', location)
82         exit(1)
83       }
84       throw err
85     }
86
87     var relativeLocation = path.relative(libPath, location)
88     writeLocationFile(relativeLocation)
89
90     console.log('Done. Phantomjs binary available at', location)
91     exit(0)
92   })
93   .fail(function (err) {
94     console.error('Phantom installation failed', err, err.stack)
95     exit(1)
96   })
97
98 function exit(code) {
99   validExit = true
100   process.env.PATH = originalPath
101   process.exit(code || 0)
102 }
103
104
105 function findSuitableTempDirectory() {
106   var now = Date.now()
107   var candidateTmpDirs = [
108     process.env.npm_config_tmp,
109     os.tmpdir(),
110     path.join(process.cwd(), 'tmp')
111   ]
112
113   for (var i = 0; i < candidateTmpDirs.length; i++) {
114     var candidatePath = candidateTmpDirs[i]
115     if (!candidatePath) continue
116
117     try {
118       candidatePath = path.join(path.resolve(candidatePath), 'phantomjs')
119       fs.mkdirsSync(candidatePath, '0777')
120       // Make double sure we have 0777 permissions; some operating systems
121       // default umask does not allow write by default.
122       fs.chmodSync(candidatePath, '0777')
123       var testFile = path.join(candidatePath, now + '.tmp')
124       fs.writeFileSync(testFile, 'test')
125       fs.unlinkSync(testFile)
126       return candidatePath
127     } catch (e) {
128       console.log(candidatePath, 'is not writable:', e.message)
129     }
130   }
131
132   console.error('Can not find a writable tmp directory, please report issue ' +
133       'on https://github.com/Medium/phantomjs/issues with as much ' +
134       'information as possible.')
135   exit(1)
136 }
137
138
139 function getRequestOptions() {
140   var strictSSL = !!process.env.npm_config_strict_ssl
141   if (process.version == 'v0.10.34') {
142     console.log('Node v0.10.34 detected, turning off strict ssl due to https://github.com/joyent/node/issues/8894')
143     strictSSL = false
144   }
145
146   var options = {
147     uri: getDownloadUrl(),
148     encoding: null, // Get response as a buffer
149     followRedirect: true, // The default download path redirects to a CDN URL.
150     headers: {},
151     strictSSL: strictSSL
152   }
153
154   var proxyUrl = process.env.npm_config_https_proxy ||
155       process.env.npm_config_http_proxy ||
156       process.env.npm_config_proxy
157   if (proxyUrl) {
158
159     // Print using proxy
160     var proxy = url.parse(proxyUrl)
161     if (proxy.auth) {
162       // Mask password
163       proxy.auth = proxy.auth.replace(/:.*$/, ':******')
164     }
165     console.log('Using proxy ' + url.format(proxy))
166
167     // Enable proxy
168     options.proxy = proxyUrl
169   }
170
171   // Use the user-agent string from the npm config
172   options.headers['User-Agent'] = process.env.npm_config_user_agent
173
174   // Use certificate authority settings from npm
175   var ca = process.env.npm_config_ca
176   if (!ca && process.env.npm_config_cafile) {
177     try {
178       ca = fs.readFileSync(process.env.npm_config_cafile, {encoding: 'utf8'})
179         .split(/\n(?=-----BEGIN CERTIFICATE-----)/g)
180
181       // Comments at the beginning of the file result in the first
182       // item not containing a certificate - in this case the
183       // download will fail
184       if (ca.length > 0 && !/-----BEGIN CERTIFICATE-----/.test(ca[0])) {
185         ca.shift()
186       }
187
188     } catch (e) {
189       console.error('Could not read cafile', process.env.npm_config_cafile, e)
190     }
191   }
192
193   if (ca) {
194     console.log('Using npmconf ca')
195     options.agentOptions = {
196       ca: ca
197     }
198     options.ca = ca
199   }
200
201   return options
202 }
203
204 function handleRequestError(error) {
205   if (error && error.stack && error.stack.indexOf('SELF_SIGNED_CERT_IN_CHAIN') != -1) {
206       console.error('Error making request, SELF_SIGNED_CERT_IN_CHAIN. ' +
207           'Please read https://github.com/Medium/phantomjs#i-am-behind-a-corporate-proxy-that-uses-self-signed-ssl-certificates-to-intercept-encrypted-traffic')
208       exit(1)
209   } else if (error) {
210     console.error('Error making request.\n' + error.stack + '\n\n' +
211         'Please report this full log at https://github.com/Medium/phantomjs')
212     exit(1)
213   } else {
214     console.error('Something unexpected happened, please report this full ' +
215         'log at https://github.com/Medium/phantomjs')
216     exit(1)
217   }
218 }
219
220 function requestBinary(requestOptions, filePath) {
221   var deferred = kew.defer()
222
223   var writePath = filePath + '-download-' + Date.now()
224
225   console.log('Receiving...')
226   var bar = null
227   requestProgress(request(requestOptions, function (error, response, body) {
228     console.log('')
229     if (!error && response.statusCode === 200) {
230       fs.writeFileSync(writePath, body)
231       console.log('Received ' + Math.floor(body.length / 1024) + 'K total.')
232       fs.renameSync(writePath, filePath)
233       deferred.resolve(filePath)
234
235     } else if (response) {
236       console.error('Error requesting archive.\n' +
237           'Status: ' + response.statusCode + '\n' +
238           'Request options: ' + JSON.stringify(requestOptions, null, 2) + '\n' +
239           'Response headers: ' + JSON.stringify(response.headers, null, 2) + '\n' +
240           'Make sure your network and proxy settings are correct.\n\n' +
241           'If you continue to have issues, please report this full log at ' +
242           'https://github.com/Medium/phantomjs')
243       exit(1)
244     } else {
245       handleRequestError(error)
246     }
247   })).on('progress', function (state) {
248     try {
249       if (!bar) {
250         bar = new progress('  [:bar] :percent', {total: state.size.total, width: 40})
251       }
252       bar.curr = state.size.transferred
253       bar.tick()
254     } catch (e) {
255       // It doesn't really matter if the progress bar doesn't update.
256     }
257   })
258   .on('error', handleRequestError)
259
260   return deferred.promise
261 }
262
263
264 function extractDownload(filePath) {
265   var deferred = kew.defer()
266   // extract to a unique directory in case multiple processes are
267   // installing and extracting at once
268   var extractedPath = filePath + '-extract-' + Date.now()
269   var options = {cwd: extractedPath}
270
271   fs.mkdirsSync(extractedPath, '0777')
272   // Make double sure we have 0777 permissions; some operating systems
273   // default umask does not allow write by default.
274   fs.chmodSync(extractedPath, '0777')
275
276   if (filePath.substr(-4) === '.zip') {
277     console.log('Extracting zip contents')
278     extractZip(path.resolve(filePath), {dir: extractedPath}, function(err) {
279       if (err) {
280         console.error('Error extracting zip')
281         deferred.reject(err)
282       } else {
283         deferred.resolve(extractedPath)
284       }
285     })
286
287   } else {
288     console.log('Extracting tar contents (via spawned process)')
289     cp.execFile('tar', ['jxf', path.resolve(filePath)], options, function (err) {
290       if (err) {
291         console.error('Error extracting archive')
292         deferred.reject(err)
293       } else {
294         deferred.resolve(extractedPath)
295       }
296     })
297   }
298   return deferred.promise
299 }
300
301
302 function copyIntoPlace(extractedPath, targetPath) {
303   console.log('Removing', targetPath)
304   return kew.nfcall(fs.remove, targetPath).then(function () {
305     // Look for the extracted directory, so we can rename it.
306     var files = fs.readdirSync(extractedPath)
307     for (var i = 0; i < files.length; i++) {
308       var file = path.join(extractedPath, files[i])
309       if (fs.statSync(file).isDirectory() && file.indexOf(helper.version) != -1) {
310         console.log('Copying extracted folder', file, '->', targetPath)
311         return kew.nfcall(fs.move, file, targetPath)
312       }
313     }
314
315     console.log('Could not find extracted file', files)
316     throw new Error('Could not find extracted file')
317   })
318 }
319
320 /**
321  * Check to see if the binary in lib is OK to use. If successful, exit the process.
322  */
323 function tryPhantomjsInLib() {
324   return kew.fcall(function () {
325     return findValidPhantomJsBinary(path.resolve(__dirname, './lib/location.js'))
326   }).then(function (binaryLocation) {
327     if (binaryLocation) {
328       console.log('PhantomJS is previously installed at', binaryLocation)
329       exit(0)
330     }
331   }).fail(function () {
332     // silently swallow any errors
333   })
334 }
335
336 /**
337  * Check to see if the binary on PATH is OK to use. If successful, exit the process.
338  */
339 function tryPhantomjsOnPath() {
340   if (getTargetPlatform() != process.platform || getTargetArch() != process.arch) {
341     console.log('Building for target platform ' + getTargetPlatform() + '/' + getTargetArch() +
342                 '. Skipping PATH search')
343     return kew.resolve(false)
344   }
345
346   return kew.nfcall(which, 'phantomjs')
347   .then(function (result) {
348     phantomPath = result
349     console.log('Considering PhantomJS found at', phantomPath)
350
351     // Horrible hack to avoid problems during global install. We check to see if
352     // the file `which` found is our own bin script.
353     if (phantomPath.indexOf(path.join('npm', 'phantomjs')) !== -1) {
354       console.log('Looks like an `npm install -g` on windows; skipping installed version.')
355       return
356     }
357
358     var contents = fs.readFileSync(phantomPath, 'utf8')
359     if (/NPM_INSTALL_MARKER/.test(contents)) {
360       console.log('Looks like an `npm install -g`')
361
362       var phantomLibPath = path.resolve(fs.realpathSync(phantomPath), '../../lib/location')
363       return findValidPhantomJsBinary(phantomLibPath)
364       .then(function (binaryLocation) {
365         if (binaryLocation) {
366           writeLocationFile(binaryLocation)
367           console.log('PhantomJS linked at', phantomLibPath)
368           exit(0)
369         }
370         console.log('Could not link global install, skipping...')
371       })
372     } else {
373       return checkPhantomjsVersion(phantomPath).then(function (matches) {
374         if (matches) {
375           writeLocationFile(phantomPath)
376           console.log('PhantomJS is already installed on PATH at', phantomPath)
377           exit(0)
378         }
379       })
380     }
381   }, function () {
382     console.log('PhantomJS not found on PATH')
383   })
384   .fail(function (err) {
385     console.error('Error checking path, continuing', err)
386     return false
387   })
388 }
389
390 /**
391  * @return {?string} Get the download URL for phantomjs.
392  *     May return null if no download url exists.
393  */
394 function getDownloadUrl() {
395   var spec = getDownloadSpec()
396   return spec && spec.url
397 }
398
399 /**
400  * Download phantomjs, reusing the existing copy on disk if available.
401  * Exits immediately if there is no binary to download.
402  * @return {Promise.<string>} The path to the downloaded file.
403  */
404 function downloadPhantomjs() {
405   var downloadSpec = getDownloadSpec()
406   if (!downloadSpec) {
407     console.error(
408         'Unexpected platform or architecture: ' + getTargetPlatform() + '/' + getTargetArch() + '\n' +
409         'It seems there is no binary available for your platform/architecture\n' +
410         'Try to install PhantomJS globally')
411     exit(1)
412   }
413
414   var downloadUrl = downloadSpec.url
415   var downloadedFile
416
417   return kew.fcall(function () {
418     // Can't use a global version so start a download.
419     var tmpPath = findSuitableTempDirectory()
420     var fileName = downloadUrl.split('/').pop()
421     downloadedFile = path.join(tmpPath, fileName)
422
423     if (fs.existsSync(downloadedFile)) {
424       console.log('Download already available at', downloadedFile)
425       return verifyChecksum(downloadedFile, downloadSpec.checksum)
426     }
427     return false
428   }).then(function (verified) {
429     if (verified) {
430       return downloadedFile
431     }
432
433     // Start the install.
434     console.log('Downloading', downloadUrl)
435     console.log('Saving to', downloadedFile)
436     return requestBinary(getRequestOptions(), downloadedFile)
437   })
438 }