1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | var fs = require('fs'),
|
13 | path = require('path'),
|
14 | urllib = require('url'),
|
15 | download = require('./download'),
|
16 | util = require('./util'),
|
17 | errorlib = require('./error'),
|
18 | zlib = require('zlib'),
|
19 | tar = require('tar'),
|
20 | chalk = require('chalk'),
|
21 | debug = require('debug')('appc:install'),
|
22 | exec = require('child_process').exec;
|
23 |
|
24 |
|
25 |
|
26 |
|
27 | function targz(sourceFile, destination, callback) {
|
28 | debug('targz source=%s, dest=%s',sourceFile,destination);
|
29 | fs.createReadStream(sourceFile)
|
30 | .pipe(zlib.createGunzip())
|
31 | .pipe(tar.Extract({ path: destination }))
|
32 | .on('error', function(err) { callback(err); })
|
33 | .on('end', function() { callback(null); });
|
34 | }
|
35 |
|
36 |
|
37 |
|
38 |
|
39 | function preflight(opts, callback) {
|
40 |
|
41 | var isWindows = util.isWindows();
|
42 | debug('preflight checks, is this windows? %d',isWindows);
|
43 |
|
44 |
|
45 | if (!isWindows && (process.env.USER==='root' || process.getuid()===0)) {
|
46 | if (process.env.SUDO_USER) {
|
47 | debug('sudo user detected %s',process.env.SUDO_USER);
|
48 | return callback(errorlib.createError('com.appcelerator.install.installer.sudo',process.env.SUDO_USER));
|
49 | }
|
50 | debug('root user detected');
|
51 | return callback(errorlib.createError('com.appcelerator.install.installer.user.root'));
|
52 | }
|
53 |
|
54 | else if (!isWindows && (process.env.USERNAME==='root' && process.env.SUDO_USER)) {
|
55 | debug('root user detected %s',process.env.SUDO_USER);
|
56 | return callback(errorlib.createError('com.appcelerator.install.installer.user.sudo.user',process.env.SUDO_USER));
|
57 | }
|
58 |
|
59 |
|
60 | var homedir = util.getHomeDir();
|
61 | debug('home directory located at %s',homedir);
|
62 | if (!fs.existsSync(homedir)) {
|
63 | var envname = process.env.HOME ? 'HOME' : 'USERPROFILE';
|
64 | debug('cannot find the home directory');
|
65 | return callback(errorlib.createError('com.appcelerator.install.installer.missing.homedir',homedir,chalk.yellow('$'+envname)));
|
66 | }
|
67 |
|
68 |
|
69 | var error = util.checkDirectory(homedir,'home');
|
70 | if (error) {
|
71 | debug("home directory isn't writable");
|
72 | return callback(error);
|
73 | }
|
74 |
|
75 |
|
76 | var installDir = util.getInstallDir();
|
77 | error = util.checkDirectory(installDir,'install');
|
78 | if (error) {
|
79 | debug("install directory isn't writable %s",installDir);
|
80 | return callback(error);
|
81 | }
|
82 |
|
83 |
|
84 | error = util.checkDirectory(path.dirname(installDir),'appcelerator');
|
85 | if (error) {
|
86 | debug("install directory isn't writable %s",path.dirname(installDir));
|
87 | return callback(error);
|
88 | }
|
89 |
|
90 | switch (process.platform) {
|
91 | case 'darwin': {
|
92 |
|
93 | return exec("xcode-select -p", function(err,stdout) {
|
94 | var exitCode = err && err.code;
|
95 | if (exitCode===2) {
|
96 | debug('xcode-select says CLI tools not installed');
|
97 |
|
98 |
|
99 | exec("gcc", function (err,stdout){
|
100 | return callback(errorlib.createError('com.appcelerator.install.preflight.missing.xcode.clitools'));
|
101 | });
|
102 | }
|
103 | else {
|
104 | callback();
|
105 | }
|
106 | });
|
107 | }
|
108 | default: break;
|
109 | }
|
110 |
|
111 | callback();
|
112 | }
|
113 |
|
114 |
|
115 |
|
116 |
|
117 | function extract (quiet, filename, dir, callback, attempts) {
|
118 | debug('calling extract on %s, dir=%s', filename,dir);
|
119 | attempts = attempts || 0;
|
120 | if (!quiet) { util.waitMessage('Installing ...'); }
|
121 | util.ensureDir(dir);
|
122 | var error = util.checkDirectory(dir,'install');
|
123 | if (error) {
|
124 | debug('extract error %s',error);
|
125 | return callback(new Error(error));
|
126 | }
|
127 | targz(filename, dir, function(err) {
|
128 |
|
129 |
|
130 | var pkg = path.join(dir, 'package', 'package.json');
|
131 | if (fs.existsSync(pkg)) {
|
132 | util.okMessage();
|
133 | return callback(null, filename, dir);
|
134 | }
|
135 | else {
|
136 | debug('after extraction, package.json not found at %s',pkg);
|
137 | if (attempts < 3) {
|
138 |
|
139 | util.resetLine();
|
140 |
|
141 | util.rmdirSyncRecursive(dir);
|
142 |
|
143 | extract(quiet, filename, dir, callback, attempts + 1);
|
144 | }
|
145 | else {
|
146 | debug('extract failed after %d attempts',attempts);
|
147 | callback(errorlib.createError('com.appcelerator.install.installer.extraction.failed'));
|
148 | }
|
149 | }
|
150 | });
|
151 | }
|
152 |
|
153 |
|
154 |
|
155 |
|
156 |
|
157 |
|
158 |
|
159 | function findAllNativeModules(dir, check){
|
160 | var dirs = [];
|
161 | fs.readdirSync(dir).forEach(function(name){
|
162 | if (name === '.nativecompiled' && dirs.indexOf(dir)===-1 && (!check || check.indexOf(dir)<0)) {
|
163 | dirs.push(dir);
|
164 | }
|
165 | var fn = path.join(dir, name);
|
166 | if (fs.existsSync(fn)) {
|
167 | try {
|
168 | var isDir = fs.statSync(fn).isDirectory();
|
169 | if (isDir) {
|
170 | dirs = dirs.concat(findAllNativeModules(fn,dirs));
|
171 | }
|
172 | }
|
173 | catch (e) {
|
174 |
|
175 |
|
176 | debug('findAllNativeModules encountered a likely symlink issue at %s, error was %o',fn,e);
|
177 | }
|
178 | }
|
179 | });
|
180 | return dirs;
|
181 | }
|
182 |
|
183 |
|
184 |
|
185 |
|
186 |
|
187 | function compileNativeModules(dir, callback) {
|
188 | debug('compileNativeModules %s',dir);
|
189 | process.nextTick(function(){
|
190 | var dirs = findAllNativeModules(dir),
|
191 | finished = 0;
|
192 | if (dirs.length) {
|
193 | util.waitMessage('Compiling platform native modules ...\n');
|
194 |
|
195 | var doNext = function doNext() {
|
196 | var dir = dirs[finished++];
|
197 | if (dir) {
|
198 | var name = path.basename(dir),
|
199 | todir = path.dirname(dir),
|
200 | todirname = path.basename(path.dirname(todir)),
|
201 | installdir = path.join(dir, '..', '..'),
|
202 | version;
|
203 |
|
204 | if (fs.existsSync(dir)) {
|
205 | var pkg = path.join(dir,'package.json');
|
206 | if (fs.existsSync(pkg)) {
|
207 |
|
208 | version = JSON.parse(fs.readFileSync(pkg)).version;
|
209 | debug('found version %s',version);
|
210 | version = '@'+version;
|
211 | }
|
212 | debug('rmdir %s',dir);
|
213 | util.rmdirSyncRecursive(dir);
|
214 | }
|
215 | var cmd = 'npm install '+name+version+' --production';
|
216 | debug('exec: %s in dir %s',cmd,installdir);
|
217 | util.waitMessage('â”” ' + chalk.cyan(todirname+'/'+name) + ' ... ');
|
218 | exec(cmd,{cwd:installdir}, function(err,stdout,stderr){
|
219 | if (err) {
|
220 | debug('error during %s, was: %o',cmd,err);
|
221 | debug('stdout: %s',stdout);
|
222 | debug('stderr: %s',stderr);
|
223 | util.stopSpinner();
|
224 | console.error(stderr||stdout);
|
225 | process.exit(1);
|
226 | }
|
227 | else {
|
228 | util.okMessage();
|
229 | doNext();
|
230 | }
|
231 | });
|
232 | }
|
233 | else {
|
234 | callback();
|
235 | }
|
236 | };
|
237 | doNext();
|
238 | }
|
239 | else {
|
240 | debug('none found');
|
241 | callback();
|
242 | }
|
243 | });
|
244 | }
|
245 |
|
246 |
|
247 |
|
248 |
|
249 | function start (opts, callback) {
|
250 |
|
251 |
|
252 | preflight(opts, function(err){
|
253 |
|
254 |
|
255 | if (err) {
|
256 | console.error(chalk.red("\n"+(err && err.message || String(err))));
|
257 | process.exit(1);
|
258 | }
|
259 |
|
260 | if (!opts.setup && !opts.quiet && (opts.banner===undefined || opts.banner)){
|
261 | util.infoMessage(chalk.blue.underline.bold('Before you can continue, the latest Appcelerator software update needs to be downloaded.'));
|
262 | console.log();
|
263 | }
|
264 |
|
265 | callback(null,true);
|
266 | });
|
267 | }
|
268 |
|
269 |
|
270 |
|
271 |
|
272 | function runSetup(installBin, opts, cb) {
|
273 | var run = require('./index').run,
|
274 | found = util.parseArgs(opts);
|
275 |
|
276 | debug('runSetup called, found is %o',found);
|
277 |
|
278 |
|
279 |
|
280 | if (found.length === 0 || (found[0]==='setup')) {
|
281 | var saved = process.argv.splice(2);
|
282 | process.argv[2] = 'setup';
|
283 | process.argv.length = 3;
|
284 | debug('calling run with %s',installBin);
|
285 | run(installBin,util.mergeOptsToArgs(['--no-banner'],opts));
|
286 | }
|
287 | else {
|
288 |
|
289 |
|
290 | run(installBin,util.mergeOptsToArgs(['--no-banner'],opts));
|
291 | }
|
292 | }
|
293 |
|
294 |
|
295 |
|
296 |
|
297 | function install (installDir, opts, cb) {
|
298 |
|
299 | start(opts, function(err,result){
|
300 | if (!result) {
|
301 | util.stopSpinner();
|
302 | if (!opts.quiet) { console.log('Cancelled!'); }
|
303 | process.exit(1);
|
304 | }
|
305 |
|
306 |
|
307 | var wantVersion = opts.version || '',
|
308 | url = util.makeURL(opts, '/api/appc/install/'+wantVersion),
|
309 | bin = wantVersion && util.getInstallBinary(opts, wantVersion);
|
310 |
|
311 | debug('install, wantVersion: %s, url: %s, bin: %s',wantVersion,url,bin);
|
312 |
|
313 |
|
314 | if (bin && !opts.force) {
|
315 | debug('bin is setup and not force');
|
316 | if (!opts.quiet) { util.infoMessage('Version '+chalk.green(wantVersion)+' already installed.'); }
|
317 | return cb && cb(null, installDir, wantVersion, bin);
|
318 | }
|
319 |
|
320 |
|
321 | download.start(opts.quiet, opts.banner,!!opts.force,url,wantVersion,function(err,filename,version,installBin) {
|
322 | if (err) { util.fail(err); }
|
323 |
|
324 |
|
325 | var failed = true;
|
326 |
|
327 |
|
328 | var installationDir = path.join(installDir,version);
|
329 |
|
330 | var sigIntFn, exitFn, pendingAbort;
|
331 |
|
332 | debug('after download, installationDir %s',installationDir);
|
333 |
|
334 | function createCleanup(name) {
|
335 | return function (exit) {
|
336 | if (failed) {
|
337 | var pkg = path.join(installationDir, 'package', 'package.json');
|
338 | if (fs.existsSync(pkg)) { fs.unlinkSync(pkg); }
|
339 | }
|
340 |
|
341 | if (name==='exit') {
|
342 | try {
|
343 | process.removeListener('exit',exitFn);
|
344 | }
|
345 | catch (e) {
|
346 |
|
347 | }
|
348 | if (pendingAbort) {
|
349 | process.exit(exit);
|
350 | }
|
351 | }
|
352 |
|
353 | else if (failed) {
|
354 | pendingAbort = true;
|
355 | util.abortMessage('Install');
|
356 | }
|
357 | try {
|
358 | process.removeListener('SIGINT',sigIntFn);
|
359 | }
|
360 | catch (e){
|
361 |
|
362 | }
|
363 | };
|
364 | }
|
365 |
|
366 |
|
367 |
|
368 | process.on('SIGINT', (sigIntFn=createCleanup('SIGINT')));
|
369 | process.on('exit', (exitFn=createCleanup('exit')));
|
370 |
|
371 | util.stopSpinner();
|
372 |
|
373 |
|
374 | installDir = util.ensureDir(path.join(installDir,version));
|
375 |
|
376 |
|
377 | if (installBin) {
|
378 | debug('installBin already found, returning %s',installBin);
|
379 | failed = false;
|
380 | createCleanup()();
|
381 | if (!opts.quiet) { util.infoMessage('Version '+chalk.green(version)+' already installed.'); }
|
382 | if (opts.setup){
|
383 | util.writeVersion(version);
|
384 | return runSetup(installBin, opts, cb);
|
385 | }
|
386 | return cb && cb(null, installDir, version, installBin);
|
387 | }
|
388 |
|
389 |
|
390 | var installTag = util.getInstallTag();
|
391 | fs.writeFileSync(installTag, version);
|
392 |
|
393 | function cleanupInstall() {
|
394 | if (fs.existsSync(installTag)) {
|
395 | fs.unlinkSync(installTag);
|
396 | }
|
397 | }
|
398 |
|
399 |
|
400 | extract(opts.quiet, filename, installDir, function(err,filename,dir){
|
401 | if (err) { cleanupInstall(); util.fail(err); }
|
402 |
|
403 |
|
404 | compileNativeModules(dir, function(err){
|
405 |
|
406 | if (err) { cleanupInstall(); util.fail(err); }
|
407 |
|
408 |
|
409 | installBin = util.getInstallBinary(opts,version);
|
410 |
|
411 | debug('after compileNativeModules, installBin is %s',installBin);
|
412 |
|
413 |
|
414 | failed = false;
|
415 |
|
416 |
|
417 | if (opts.setup || opts.use) {
|
418 | util.writeVersion(version);
|
419 | }
|
420 |
|
421 |
|
422 | cleanupInstall();
|
423 |
|
424 |
|
425 | if (opts.setup) {
|
426 | debug('after compileNativeModules, setup is set');
|
427 | return runSetup(installBin, opts, cb);
|
428 | }
|
429 |
|
430 | var launchMessage = ' Launching ... ' + (util.isWindows() ? '' : '🚀');
|
431 |
|
432 | if (!opts.quiet) { util.infoMessage(chalk.green.bold('Installed!!'+ (!opts.use&&!opts.setup?launchMessage:''))); }
|
433 |
|
434 |
|
435 | if (opts.use) { return cb && cb(null, installDir, version, installBin); }
|
436 |
|
437 | debug('running %s',installBin);
|
438 |
|
439 |
|
440 | require('./index').run(installBin,['--no-banner']);
|
441 | });
|
442 | });
|
443 | });
|
444 |
|
445 | });
|
446 |
|
447 | }
|
448 |
|
449 | module.exports = install;
|