UNPKG

16.6 kBJavaScriptView Raw
1/**
2 Licensed to the Apache Software Foundation (ASF) under one
3 or more contributor license agreements. See the NOTICE file
4 distributed with this work for additional information
5 regarding copyright ownership. The ASF licenses this file
6 to you under the Apache License, Version 2.0 (the
7 "License"); you may not use this file except in compliance
8 with the License. You may obtain a copy of the License at
9
10 http://www.apache.org/licenses/LICENSE-2.0
11
12 Unless required by applicable law or agreed to in writing,
13 software distributed under the License is distributed on an
14 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 KIND, either express or implied. See the License for the
16 specific language governing permissions and limitations
17 under the License.
18*/
19
20/* jshint node:true, bitwise:true, undef:true, trailing:true, quotmark:true,
21 indent:4, unused:vars, latedef:nofunc,
22 laxcomma:true
23*/
24
25
26var path = require('path'),
27 fs = require('fs'),
28 help = require('./help'),
29 nopt,
30 _,
31 updateNotifier,
32 pkg = require('../package.json'),
33 telemetry = require('./telemetry'),
34 Q = require('q');
35
36var cordova_lib = require('cordova-lib'),
37 CordovaError = cordova_lib.CordovaError,
38 cordova = cordova_lib.cordova,
39 events = cordova_lib.events,
40 logger = require('cordova-common').CordovaLogger.get();
41
42
43/*
44 * init
45 *
46 * initializes nopt and underscore
47 * nopt and underscore are require()d in try-catch below to print a nice error
48 * message if one of them is not installed.
49 */
50function init() {
51 try {
52 nopt = require('nopt');
53 _ = require('underscore');
54 updateNotifier = require('update-notifier');
55 } catch (e) {
56 console.error(
57 'Please run npm install from this directory:\n\t' +
58 path.dirname(__dirname)
59 );
60 process.exit(2);
61 }
62}
63
64function checkForUpdates() {
65 try {
66 // Checks for available update and returns an instance
67 var notifier = updateNotifier({
68 pkg: pkg
69 });
70
71 // Notify using the built-in convenience method
72 notifier.notify();
73 } catch (e) {
74 // https://issues.apache.org/jira/browse/CB-10062
75 if (e && e.message && /EACCES/.test(e.message)) {
76 console.log('Update notifier was not able to access the config file.\n' +
77 'You may grant permissions to the file: \'sudo chmod 744 ~/.config/configstore/update-notifier-cordova.json\'');
78 } else {
79 throw e;
80 }
81 }
82}
83
84var shouldCollectTelemetry = false;
85module.exports = function (inputArgs, cb) {
86
87 /**
88 * mainly used for testing.
89 */
90 cb = cb || function(){};
91
92 init();
93
94 // If no inputArgs given, use process.argv.
95 inputArgs = inputArgs || process.argv;
96 var cmd = inputArgs[2]; // e.g: inputArgs= 'node cordova run ios'
97 var subcommand = getSubCommand(inputArgs, cmd);
98 var isTelemetryCmd = (cmd === 'telemetry');
99
100 // ToDO: Move nopt-based parsing of args up here
101 if(cmd === '--version' || cmd === '-v') {
102 cmd = 'version';
103 } else if(!cmd || cmd === '--help' || cmd === 'h') {
104 cmd = 'help';
105 }
106
107 Q().then(function() {
108
109 /**
110 * Skip telemetry prompt if:
111 * - CI environment variable is present
112 * - Command is run with `--no-telemetry` flag
113 * - Command ran is: `cordova telemetry on | off | ...`
114 */
115
116 if(telemetry.isCI(process.env) || telemetry.isNoTelemetryFlag(inputArgs)) {
117 return Q(false);
118 }
119
120 /**
121 * We shouldn't prompt for telemetry if user issues a command of the form: `cordova telemetry on | off | ...x`
122 * Also, if the user has already been prompted and made a decision, use his saved answer
123 */
124 if(isTelemetryCmd) {
125 var isOptedIn = telemetry.isOptedIn();
126 return handleTelemetryCmd(subcommand, isOptedIn);
127 }
128
129 if(telemetry.hasUserOptedInOrOut()) {
130 return Q(telemetry.isOptedIn());
131 }
132
133 /**
134 * Otherwise, prompt user to opt-in or out
135 * Note: the prompt is shown for 30 seconds. If no choice is made by that time, User is considered to have opted out.
136 */
137 return telemetry.showPrompt();
138 }).then(function (collectTelemetry) {
139 shouldCollectTelemetry = collectTelemetry;
140 if(isTelemetryCmd) {
141 return Q();
142 }
143 return cli(inputArgs);
144 }).then(function () {
145 if (shouldCollectTelemetry && !isTelemetryCmd) {
146 telemetry.track(cmd, subcommand, 'successful');
147 }
148 // call cb with error as arg if something failed
149 cb(null);
150 }).fail(function (err) {
151 if (shouldCollectTelemetry && !isTelemetryCmd) {
152 telemetry.track(cmd, subcommand, 'unsuccessful');
153 }
154 // call cb with error as arg if something failed
155 cb(err);
156 throw err;
157 }).done();
158};
159
160function getSubCommand(args, cmd) {
161 if(cmd === 'platform' || cmd === 'platforms' || cmd === 'plugin' || cmd === 'plugins' || cmd === 'telemetry') {
162 return args[3]; // e.g: args='node cordova platform rm ios', 'node cordova telemetry on'
163 }
164 return null;
165}
166
167function handleTelemetryCmd(subcommand, isOptedIn) {
168
169 if (subcommand !== 'on' && subcommand !== 'off') {
170 logger.subscribe(events);
171 return help(['telemetry']);
172 }
173
174 var turnOn = subcommand === 'on' ? true : false;
175 var cmdSuccess = true;
176
177 // turn telemetry on or off
178 try {
179 if (turnOn) {
180 telemetry.turnOn();
181 console.log("Thanks for opting into telemetry to help us improve cordova.");
182 } else {
183 telemetry.turnOff();
184 console.log("You have been opted out of telemetry. To change this, run: cordova telemetry on.");
185 }
186 } catch (ex) {
187 cmdSuccess = false;
188 }
189
190 // track or not track ?, that is the question
191
192 if (!turnOn) {
193 // Always track telemetry opt-outs (whether user opted out or not!)
194 telemetry.track('telemetry', 'off', 'via-cordova-telemetry-cmd', cmdSuccess ? 'successful': 'unsuccessful');
195 return Q();
196 }
197
198 if(isOptedIn) {
199 telemetry.track('telemetry', 'on', 'via-cordova-telemetry-cmd', cmdSuccess ? 'successful' : 'unsuccessful');
200 }
201
202 return Q();
203}
204
205function cli(inputArgs) {
206 // When changing command line arguments, update doc/help.txt accordingly.
207 var knownOpts =
208 { 'verbose' : Boolean
209 , 'version' : Boolean
210 , 'help' : Boolean
211 , 'silent' : Boolean
212 , 'experimental' : Boolean
213 , 'noregistry' : Boolean
214 , 'nohooks': Array
215 , 'shrinkwrap' : Boolean
216 , 'copy-from' : String
217 , 'link-to' : path
218 , 'searchpath' : String
219 , 'variable' : Array
220 , 'link': Boolean
221 , 'force': Boolean
222 // Flags to be passed to `cordova build/run/emulate`
223 , 'debug' : Boolean
224 , 'release' : Boolean
225 , 'archs' : String
226 , 'device' : Boolean
227 , 'emulator': Boolean
228 , 'target' : String
229 , 'browserify': Boolean
230 , 'noprepare': Boolean
231 , 'fetch': Boolean
232 , 'nobuild': Boolean
233 , 'list': Boolean
234 , 'buildConfig' : String
235 , 'template' : String
236 };
237
238 var shortHands =
239 { 'd' : '--verbose'
240 , 'v' : '--version'
241 , 'h' : '--help'
242 , 'src' : '--copy-from'
243 , 't' : '--template'
244 };
245
246 checkForUpdates();
247
248 var args = nopt(knownOpts, shortHands, inputArgs);
249
250 // For CordovaError print only the message without stack trace unless we
251 // are in a verbose mode.
252 process.on('uncaughtException', function(err) {
253 logger.error(err);
254 // Don't send exception details, just send that it happened
255 if(shouldCollectTelemetry) {
256 telemetry.track('uncaughtException');
257 }
258 process.exit(1);
259 });
260
261 logger.subscribe(events);
262
263 if (args.silent) {
264 logger.setLevel('error');
265 }
266
267 if (args.verbose) {
268 logger.setLevel('verbose');
269 }
270
271 var cliVersion = require('../package').version;
272 // TODO: Use semver.prerelease when it gets released
273 var usingPrerelease = /-nightly|-dev$/.exec(cliVersion);
274 if (args.version || usingPrerelease) {
275 var libVersion = require('cordova-lib/package').version;
276 var toPrint = cliVersion;
277 if (cliVersion != libVersion || usingPrerelease) {
278 toPrint += ' (cordova-lib@' + libVersion + ')';
279 }
280
281 if (args.version) {
282 logger.results(toPrint);
283 return Q();
284 } else {
285 // Show a warning and continue
286 logger.warn('Warning: using prerelease version ' + toPrint);
287 }
288 }
289
290 // If there were arguments protected from nopt with a double dash, keep
291 // them in unparsedArgs. For example:
292 // cordova build ios -- --verbose --whatever
293 // In this case "--verbose" is not parsed by nopt and args.vergbose will be
294 // false, the unparsed args after -- are kept in unparsedArgs and can be
295 // passed downstream to some scripts invoked by Cordova.
296 var unparsedArgs = [];
297 var parseStopperIdx = args.argv.original.indexOf('--');
298 if (parseStopperIdx != -1) {
299 unparsedArgs = args.argv.original.slice(parseStopperIdx + 1);
300 }
301
302 // args.argv.remain contains both the undashed args (like platform names)
303 // and whatever unparsed args that were protected by " -- ".
304 // "undashed" stores only the undashed args without those after " -- " .
305 var remain = args.argv.remain;
306 var undashed = remain.slice(0, remain.length - unparsedArgs.length);
307 var cmd = undashed[0];
308 var subcommand;
309 var msg;
310 var known_platforms = Object.keys(cordova_lib.cordova_platforms);
311
312 if ( !cmd || cmd == 'help' || args.help ) {
313 if (!args.help && remain[0] == 'help') {
314 remain.shift();
315 }
316 return help(remain);
317 }
318
319 if ( !cordova.hasOwnProperty(cmd) ) {
320 msg =
321 'Cordova does not know ' + cmd + '; try `' + cordova_lib.binname +
322 ' help` for a list of all the available commands.';
323 throw new CordovaError(msg);
324 }
325
326 var opts = {
327 platforms: [],
328 options: [],
329 verbose: args.verbose || false,
330 silent: args.silent || false,
331 browserify: args.browserify || false,
332 fetch: args.fetch || false,
333 nohooks: args.nohooks || [],
334 searchpath : args.searchpath
335 };
336
337
338 if (cmd == 'emulate' || cmd == 'build' || cmd == 'prepare' || cmd == 'compile' || cmd == 'run' || cmd === 'clean') {
339 // All options without dashes are assumed to be platform names
340 opts.platforms = undashed.slice(1);
341 var badPlatforms = _.difference(opts.platforms, known_platforms);
342 if( !_.isEmpty(badPlatforms) ) {
343 msg = 'Unknown platforms: ' + badPlatforms.join(', ');
344 throw new CordovaError(msg);
345 }
346
347 // Pass nopt-parsed args to PlatformApi through opts.options
348 opts.options = args;
349 opts.options.argv = unparsedArgs;
350
351 if (cmd === 'run' && args.list && cordova.raw.targets) {
352 return cordova.raw.targets.call(null, opts);
353 }
354
355 return cordova.raw[cmd].call(null, opts);
356 } else if (cmd === 'requirements') {
357 // All options without dashes are assumed to be platform names
358 opts.platforms = undashed.slice(1);
359 var badPlatforms = _.difference(opts.platforms, known_platforms);
360 if( !_.isEmpty(badPlatforms) ) {
361 msg = 'Unknown platforms: ' + badPlatforms.join(', ');
362 throw new CordovaError(msg);
363 }
364
365 return cordova.raw[cmd].call(null, opts.platforms)
366 .then(function(platformChecks) {
367
368 var someChecksFailed = Object.keys(platformChecks).map(function(platformName) {
369 events.emit('log', '\nRequirements check results for ' + platformName + ':');
370 var platformCheck = platformChecks[platformName];
371 if (platformCheck instanceof CordovaError) {
372 events.emit('warn', 'Check failed for ' + platformName + ' due to ' + platformCheck);
373 return true;
374 }
375
376 var someChecksFailed = false;
377 platformCheck.forEach(function(checkItem) {
378 var checkSummary = checkItem.name + ': ' +
379 (checkItem.installed ? 'installed ' : 'not installed ') +
380 (checkItem.metadata.version || '');
381 events.emit('log', checkSummary);
382 if (!checkItem.installed) {
383 someChecksFailed = true;
384 events.emit('warn', checkItem.metadata.reason);
385 }
386 });
387
388 return someChecksFailed;
389 }).some(function(isCheckFailedForPlatform) {
390 return isCheckFailedForPlatform;
391 });
392
393 if (someChecksFailed) throw new CordovaError('Some of requirements check failed');
394 });
395 } else if (cmd == 'serve') {
396 var port = undashed[1];
397 return cordova.raw.serve(port);
398 } else if (cmd == 'create') {
399 return create();
400 } else {
401 // platform/plugins add/rm [target(s)]
402 subcommand = undashed[1]; // sub-command like "add", "ls", "rm" etc.
403 var targets = undashed.slice(2); // array of targets, either platforms or plugins
404 var cli_vars = {};
405 if (args.variable) {
406 args.variable.forEach(function (s) {
407 // CB-9171
408 var eq = s.indexOf('=');
409 if (eq == -1)
410 throw new CordovaError("invalid variable format: " + s);
411 var key = s.substr(0, eq).toUpperCase();
412 var val = s.substr(eq + 1, s.length);
413 cli_vars[key] = val;
414 });
415 }
416 var download_opts = { searchpath : args.searchpath
417 , noregistry : args.noregistry
418 , nohooks : args.nohooks
419 , cli_variables : cli_vars
420 , browserify: args.browserify || false
421 , fetch: args.fetch || false
422 , link: args.link || false
423 , save: args.save || false
424 , shrinkwrap: args.shrinkwrap || false
425 , force: args.force || false
426 };
427 return cordova.raw[cmd](subcommand, targets, download_opts);
428 }
429
430 function create() {
431 var cfg; // Create config
432 var customWww; // Template path
433 var wwwCfg; // Template config
434
435 // If we got a fourth parameter, consider it to be JSON to init the config.
436 if (undashed[4])
437 cfg = JSON.parse(undashed[4]);
438 else
439 cfg = {};
440
441 customWww = args['copy-from'] || args['link-to'] || args.template;
442
443 if (customWww) {
444 if ((!args.template || !args['copy-from']) && customWww.indexOf('http') === 0) {
445 throw new CordovaError(
446 'Only local paths for custom www assets are supported.'
447 );
448 }
449
450 // Resolve tilda
451 if (customWww.substr(0,1) === '~')
452 customWww = path.join(process.env.HOME, customWww.substr(1));
453
454 wwwCfg = {
455 url: customWww,
456 template: false
457 };
458
459
460 if (args.template) {
461 wwwCfg.template = true;
462 } else if (args['copy-from']) {
463 logger.warn('Warning: --copy-from option is being deprecated. Consider using --template instead.');
464 wwwCfg.template = true;
465 }
466
467 cfg.lib = cfg.lib || {};
468 cfg.lib.www = wwwCfg;
469 }
470 return cordova.raw.create( undashed[1] // dir to create the project in
471 , undashed[2] // App id
472 , undashed[3] // App name
473 , cfg
474 , args['fetch'] != undefined
475 );
476 }
477}