UNPKG

23.2 kBJavaScriptView Raw
1/**
2 * Titanium SDK Library for Node.js
3 * Copyright (c) 2012-2013 by Appcelerator, Inc. All Rights Reserved.
4 * Please see the LICENSE file for information about licensing.
5 */
6'use strict';
7
8const fs = require('fs');
9const path = require('path');
10const async = require('async');
11const spawn = require('child_process').spawn; // eslint-disable-line security/detect-child-process
12const uuid = require('node-uuid');
13const appc = require('node-appc');
14const __ = appc.i18n(__dirname).__;
15const afs = appc.fs;
16const version = appc.version;
17const manifest = appc.pkginfo.manifest(module);
18const platformAliases = {
19 // add additional aliases here for new platforms
20 ipad: 'iphone',
21 ios: 'iphone'
22};
23
24exports.i18n = require('./i18n');
25exports.tiappxml = require('./tiappxml');
26
27exports.manifest = manifest;
28exports.platforms = [].concat(manifest.platforms);
29exports.targetPlatforms = (manifest.platforms || []).map(p => {
30 return p === 'iphone' ? 'ios' : p;
31}).sort();
32exports.availablePlatforms = (manifest.platforms || []).sort();
33exports.availablePlatformsNames = (function (platforms) {
34 Object.keys(platformAliases).forEach(function (alias) {
35 if (platforms.indexOf(platformAliases[alias]) !== -1) {
36 platforms.push(alias);
37 }
38 });
39 return platforms.sort();
40}(manifest.platforms || []));
41exports.allPlatformNames = [ 'android', 'ios', 'iphone', 'ipad', 'mobileweb', 'blackberry', 'windows', 'tizen' ];
42
43exports.commonOptions = function (logger, config) {
44 return {
45 'log-level': {
46 callback: function (value) {
47 Object.prototype.hasOwnProperty.call(logger.levels, value) && logger.setLevel(value);
48 },
49 desc: __('minimum logging level'),
50 default: config.cli.logLevel || 'trace',
51 hint: __('level'),
52 values: logger.getLevels()
53 }
54 };
55};
56
57exports.platformOptions = function (logger, config, cli, commandName, finished) {
58 var result = {},
59 targetPlatform = !cli.argv.help && (cli.argv.platform || cli.argv.p);
60
61 if (!commandName) {
62 finished(result);
63 return;
64 }
65
66 function set(obj, title, platform) {
67 // add the platform and title to the options and flags
68 [ 'options', 'flags' ].forEach(function (type) {
69 if (obj && obj[type]) {
70 result[platform] || (result[platform] = {
71 platform: platform,
72 title: title || platform
73 });
74 result[platform][type] = obj[type];
75 }
76 });
77 }
78
79 // translate the platform name
80 targetPlatform = platformAliases[targetPlatform] || targetPlatform;
81
82 // for each platform, fetch their specific flags/options
83 async.parallel(manifest.platforms.map(function (platform) {
84 return function (callback) {
85
86 // only configure target platform
87 if (targetPlatform && platform !== targetPlatform) {
88 return callback();
89 }
90
91 var platformDir = path.join(path.dirname(module.filename), '..', '..', '..', platform),
92 platformCommand = path.join(platformDir, 'cli', 'commands', '_' + commandName + '.js'),
93 command,
94 conf,
95 title;
96
97 if (!fs.existsSync(platformCommand)) {
98 return callback();
99 }
100
101 command = require(platformCommand);
102 if (!command || !command.config) {
103 return callback();
104 }
105
106 // try to get the platform specific configuration
107 conf = command.config(logger, config, cli);
108
109 try {
110 // try to read a title from the platform's package.json
111 title = JSON.parse(fs.readFileSync(path.join(platformDir, 'package.json'))).title;
112 } catch (e) {}
113
114 if (typeof conf === 'function') {
115 // async callback
116 conf(function (obj) {
117 set(obj, title, platform);
118 callback();
119 });
120 return;
121 }
122
123 set(conf, title, platform);
124 callback();
125 };
126 }), function () {
127 finished(result);
128 });
129};
130
131exports.validateProjectDir = function (logger, cli, argv, name) {
132 const dir = argv[name] || (process.env.SOURCE_ROOT ? path.join(process.env.SOURCE_ROOT, '..', '..') : '.');
133 let projectDir = argv[name] = appc.fs.resolvePath(dir);
134
135 if (!fs.existsSync(projectDir)) {
136 logger.banner();
137 logger.error(__('Project directory does not exist') + '\n');
138 process.exit(1);
139 }
140
141 let tiapp = path.join(projectDir, 'tiapp.xml');
142 while (!fs.existsSync(tiapp) && tiapp.split(path.sep).length > 2) {
143 projectDir = argv[name] = path.dirname(projectDir);
144 tiapp = path.join(projectDir, 'tiapp.xml');
145 }
146
147 if (tiapp.split(path.sep).length === 2) {
148 logger.banner();
149 logger.error(__('Invalid project directory "%s"', dir) + '\n');
150 dir === '.' && logger.log(__('Use the %s property to specify the project\'s directory', '--project-dir'.cyan) + '\n');
151 process.exit(1);
152 }
153
154 // load the tiapp.xml
155 cli.tiapp = new exports.tiappxml(path.join(projectDir, 'tiapp.xml'));
156};
157
158exports.validateTiappXml = function (logger, config, tiapp) {
159 if (!tiapp.id) {
160 logger.error(__('tiapp.xml is missing the <id> element'));
161 logger.error(__('The app id must consist of letters, numbers, and underscores.'));
162 logger.error(__('Note: Android does not allow dashes and iOS does not allow underscores.'));
163 logger.error(__('The first character must be a letter or underscore.'));
164 logger.error(__('Usually the app id is your company\'s reversed Internet domain name. (i.e. com.example.myapp)') + '\n');
165 process.exit(1);
166 }
167
168 if (!tiapp.name) {
169 logger.error(__('tiapp.xml is missing the <name> element'));
170 logger.error(__('The project name must consist of letters, numbers, dashes, and underscores.'));
171 logger.error(__('The first character must be a letter.') + '\n');
172 process.exit(1);
173 }
174
175 if (!tiapp.guid) {
176 logger.error(__('tiapp.xml is missing the <guid> element'));
177 logger.error(__('The guid must be in the format XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX and consist of letters and numbers.') + '\n');
178 logger.log(__('If you need a new guid, below are 5 freshly generated new ones that you can choose from:'));
179 for (let i = 0; i < 5; i++) {
180 logger.log(' ' + uuid.v4().cyan);
181 }
182 logger.log();
183 process.exit(1);
184 }
185
186 if (!/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(tiapp.guid)) {
187 logger.error(__('tiapp.xml contains an invalid guid "%s"', tiapp.guid));
188 logger.error(__('The guid must be in the format XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX and consist of letters and numbers.') + '\n');
189 logger.log(__('If you need a new guid, below are 5 freshly generated new ones that you can choose from:'));
190 for (let i = 0; i < 5; i++) {
191 logger.log(' ' + uuid.v4().cyan);
192 }
193 logger.log();
194 process.exit(1);
195 }
196
197 tiapp.version || (tiapp.version = '1.0');
198
199 if (!config.get('app.skipVersionValidation') && !tiapp.properties['ti.skipVersionValidation']) {
200 if (!/^\d+(\.\d+(\.\d+(\..+)?)?)?$/.test(tiapp.version)) {
201 logger.error(__('tiapp.xml contains an invalid version "%s"', tiapp.version));
202 logger.error(__('The version must consist of three positive integers in the format "X.Y.Z".') + '\n');
203 process.exit(1);
204 }
205
206 if (('' + tiapp.version).charAt(0) == '0') { // eslint-disable-line eqeqeq
207 logger.warn(__('tiapp.xml contains an invalid version "%s"', tiapp.version));
208 logger.warn(__('The app version major number must be greater than zero.'));
209 }
210 }
211};
212
213exports.validAppId = function (id) {
214 const words = {
215 abstract: 1,
216 assert: 1,
217 boolean: 1,
218 break: 1,
219 byte: 1,
220 case: 1,
221 catch: 1,
222 char: 1,
223 class: 1,
224 const: 1,
225 continue: 1,
226 default: 1,
227 do: 1,
228 double: 1,
229 else: 1,
230 enum: 1,
231 extends: 1,
232 false: 1,
233 final: 1,
234 finally: 1,
235 float: 1,
236 for: 1,
237 goto: 1,
238 if: 1,
239 implements: 1,
240 import: 1,
241 instanceof: 1,
242 int: 1,
243 interface: 1,
244 long: 1,
245 native: 1,
246 new: 1,
247 null: 1,
248 package: 1,
249 private: 1,
250 protected: 1,
251 public: 1,
252 return: 1,
253 short: 1,
254 static: 1,
255 strictfp: 1,
256 super: 1,
257 switch: 1,
258 synchronized: 1,
259 this: 1,
260 throw: 1,
261 throws: 1,
262 transient: 1,
263 true: 1,
264 try: 1,
265 void: 1,
266 volatile: 1,
267 while: 1
268 },
269 parts = id.split('.'),
270 l = parts.length;
271
272 for (let i = 0; i < l; i++) {
273 if (words[parts[i]]) {
274 return false;
275 }
276 }
277
278 return true;
279};
280
281exports.loadPlugins = function (logger, config, cli, projectDir, finished, silent, compact) {
282 var searchPaths = {
283 project: [ path.join(projectDir, 'plugins') ],
284 config: [],
285 global: []
286 },
287 confPaths = config.get('paths.plugins'),
288 defaultInstallLocation = cli.env.installPath,
289 sdkLocations = cli.env.os.sdkPaths.map(function (p) { return afs.resolvePath(p); });
290
291 // set our paths from the config file
292 Array.isArray(confPaths) || (confPaths = [ confPaths ]);
293 confPaths.forEach(function (p) {
294 p && fs.existsSync(p = afs.resolvePath(p)) && searchPaths.project.indexOf(p) === -1 && searchPaths.config.indexOf(p) === -1 && (searchPaths.config.push(p));
295 });
296
297 // add any plugins from various sdk locations
298 sdkLocations.indexOf(defaultInstallLocation) === -1 && sdkLocations.push(defaultInstallLocation);
299 cli.sdk && sdkLocations.push(afs.resolvePath(cli.sdk.path, '..', '..', '..'));
300 sdkLocations.forEach(function (p) {
301 fs.existsSync(p = afs.resolvePath(p, 'plugins')) && searchPaths.project.indexOf(p) === -1 && searchPaths.config.indexOf(p) === -1 && searchPaths.global.indexOf(p) === -1 && (searchPaths.global.push(p));
302 });
303
304 // find all hooks for active plugins
305 appc.tiplugin.find(cli.tiapp.plugins, searchPaths, config, logger, function (plugins) {
306 if (plugins.missing.length) {
307 if (logger) {
308 logger.error(__('Could not find all required Titanium plugins:'));
309 plugins.missing.forEach(m => logger.error(' id: ' + m.id + '\t version: ' + m.version));
310 logger.log();
311 }
312 process.exit(1);
313 }
314
315 if (plugins.found.length) {
316 plugins.found.forEach(plugin => cli.scanHooks(afs.resolvePath(plugin.pluginPath, 'hooks')));
317 } else {
318 logger && logger.debug(__('No project level plugins to load'));
319 }
320
321 silent || cli.emit('cli:check-plugins', { compact: compact === undefined ? true : compact });
322
323 finished();
324 });
325};
326
327exports.loadModuleManifest = function (logger, manifestFile) {
328 if (!fs.existsSync(manifestFile)) {
329 logger.error(__('Missing %s', manifestFile));
330 logger.log();
331 process.exit(1);
332 }
333
334 const re = /^(\S+)\s*:\s*(.*)$/;
335 const manifest = {};
336 fs.readFileSync(manifestFile).toString().split(/\r?\n/).forEach(function (line) {
337 const match = line.match(re);
338 if (match) {
339 manifest[match[1].trim()] = match[2].trim();
340 }
341 });
342
343 return manifest;
344};
345
346exports.validateModuleManifest = function (logger, cli, manifest) {
347 const requiredModuleKeys = [
348 'name',
349 'version',
350 'moduleid',
351 'description',
352 'copyright',
353 'license',
354 'copyright',
355 'platform',
356 'minsdk',
357 'architectures'
358 ];
359
360 // check if all the required module keys are in the list
361 requiredModuleKeys.forEach(function (key) {
362 if (!manifest[key]) {
363 logger.error(__('Missing required manifest key "%s"', key));
364 logger.log();
365 process.exit(1);
366 }
367 });
368
369 if (cli.argv.platform !== exports.resolvePlatform(manifest.platform)) {
370 logger.error(__('Unable to find "%s" module', cli.argv.platform));
371 logger.log();
372 process.exit(1);
373 }
374};
375
376exports.validateCorrectSDK = function (logger, config, cli, commandName) {
377 // tiapp.xml should exist by the time we get here
378 var argv = cli.argv,
379 tiapp = cli.tiapp,
380 sdkName = tiapp['sdk-version'],
381 selectedSdk = cli.sdk && cli.sdk.name || manifest.version;
382
383 if (!sdkName) {
384 sdkName = tiapp['sdk-version'] = cli.sdk && cli.sdk.name || Object.keys(cli.env.sdks).sort().pop();
385 }
386
387 if (argv.legacy !== true && (!sdkName || sdkName === selectedSdk)) {
388 return true;
389 }
390
391 // check the project's preferred sdk is even installed
392 if (sdkName === '__global__' || !cli.env.sdks[sdkName]) {
393 logger.banner();
394 logger.error(__('Unable to compile project because the \'sdk-version\' in the tiapp.xml is not installed') + '\n');
395 logger.log(__('The project\'s %s is currently set to %s, which is not installed.', 'sdk-version'.cyan, sdkName.cyan) + '\n');
396 logger.log(__('Update the %s in the tiapp.xml to one of the installed Titaniums SDKs:', 'sdk-version'.cyan));
397 Object.keys(cli.env.sdks).sort().forEach(function (ver) {
398 if (ver !== '__global__') {
399 logger.log(' ' + ver.cyan);
400 }
401 });
402 logger.log(__('or run \'%s\' to download and install Titanium SDK %s', ('titanium sdk install ' + sdkName).cyan, sdkName) + '\n');
403 process.exit(1);
404 }
405
406 var sdkVersion = cli.env.sdks[sdkName].manifest && cli.env.sdks[sdkName].manifest.version || sdkName;
407 if (version.gte(sdkVersion, '3.0.0') && version.lte(sdkVersion, '3.0.2') && version.gte(process.version, '0.9.0')) {
408 logger.banner();
409 logger.error(__('Unable to compile project using Titanium SDK %s with Node.js %s', sdkName, process.version) + '\n');
410 logger.log(__('Titanium SDK %s requires Node.js v0.8. Node.js v0.10 and newer will not work.', sdkName.cyan) + '\n');
411 logger.log(__('Either update your application to Titanium SDK %s or newer or download Node.js %s from %s.', '3.1.0.GA'.cyan, 'v0.8'.cyan, 'http://nodejs.org/dist/'.cyan) + '\n');
412 process.exit(1);
413 }
414
415 // fork or die
416 if (config.cli.failOnWrongSDK) {
417 logger.banner();
418 logger.error(__('Unable to compile a %s project with Titanium SDK %s', sdkName, selectedSdk));
419 logger.error(__('To build this application, set the <sdk-version> in the tiapp.xml to the current Titaniums SDK: %s', selectedSdk) + '\n');
420 process.exit(1);
421 }
422
423 var args = argv.$_,
424 p = args.indexOf('--sdk'),
425 platform = exports.resolvePlatform(argv.platform),
426 cmd = [],
427 cmdSafe = [],
428 cmdRoot,
429 hideBanner = false,
430 delayCmd = false;
431
432 function cmdAdd() {
433 for (var i = 0; i < arguments.length; i++) {
434 cmd.push(arguments[i]);
435 cmdSafe.push(arguments[i]);
436 }
437 }
438
439 function cmdAddSecret(param) {
440 for (var i = 0; i < arguments.length; i++) {
441 cmd.push(arguments[i]);
442 cmdSafe.push('*******');
443 }
444 }
445
446 if (p !== -1) {
447 args.splice(p, 2);
448 }
449
450 if (!argv.legacy) {
451 logger.info(__('tiapp.xml <sdk-version> set to %s, but current Titanium SDK set to %s', sdkName.cyan, selectedSdk.cyan));
452 }
453
454 if (argv.legacy || version.lt(sdkVersion, '2.2.0')) { // technically, there is no 2.2, it was released as 3.0
455 // in 3.2, we renamed --password to --store-password as to not conflict with the
456 // authentication --password option
457 if (argv.platform === 'android' && argv['store-password']) {
458 argv.password = argv['store-password'];
459 }
460
461 cmdRoot = 'python';
462
463 var builderPy = path.join(path.resolve(cli.env.sdks[sdkName].path), platform, 'builder.py');
464 cmdAdd(builderPy);
465
466 switch (platform) {
467 case 'iphone':
468 switch (argv.target) {
469 case 'simulator':
470 if (argv['build-only']) {
471 cmdAdd('build', argv['ios-version'], argv['project-dir'], tiapp.id, tiapp.name, argv['device-family'], argv['sim-type'], argv['debug-host']);
472 } else {
473 cmdAdd('run', argv['project-dir'], argv['ios-version'], '', '', argv['device-family'], argv['sim-type'], argv['debug-host']);
474 }
475 break;
476
477 case 'device':
478 cmdAdd('install', argv['ios-version'], argv['project-dir'], tiapp.id, tiapp.name, argv['pp-uuid'], argv['developer-name'], argv['device-family'], argv.keychain, argv['debug-host']);
479 break;
480
481 case 'dist-appstore':
482 cmdAdd('distribute', argv['ios-version'], argv['project-dir'], tiapp.id, tiapp.name, argv['pp-uuid'], argv['distribution-name'], '.', argv['device-family'], argv.keychain);
483 break;
484
485 case 'dist-adhoc':
486 cmdAdd('adhoc', argv['ios-version'], argv['project-dir'], tiapp.id, tiapp.name, argv['pp-uuid'], argv['distribution-name'], argv['device-family'], argv.keychain, argv['debug-host']);
487 break;
488 }
489 break;
490
491 case 'mobileweb':
492 cmdAdd(argv['project-dir'], argv['deploy-type']);
493 break;
494
495 case 'android':
496 if (argv['build-only']) {
497 cmdAdd('build', tiapp.name, argv['android-sdk'], argv['project-dir'], tiapp.id);
498 } else {
499 if (argv.target === 'emulator') {
500 if (!argv['avd-id']) {
501 logger.error(__('Missing required option "%s"', '--avd-id') + '\n');
502 process.exit(1);
503 }
504 if (!argv['avd-skin']) {
505 logger.error(__('Missing required option "%s"', '--avd-skin') + '\n');
506 process.exit(1);
507 }
508 }
509
510 switch (argv.target) {
511 case 'emulator':
512 cmdAdd('simulator', tiapp.name, argv['android-sdk'], argv['project-dir'], tiapp.id, argv['avd-id'], argv['avd-skin']);
513 delayCmd = true;
514
515 // launch the emulator
516 var emuArgs = [ builderPy, 'emulator', tiapp.name, argv['android-sdk'], argv['project-dir'], tiapp.id, argv['avd-id'], argv['avd-skin'] ];
517 argv['avd-abi'] && emuArgs.push(argv['avd-abi']);
518 logger.info(__('Launching Android emulator: %s', ('"' + cmdRoot + '" "' + emuArgs.join('" "') + '"').cyan));
519 spawn(cmdRoot, emuArgs, {
520 detached: true,
521 stdio: 'ignore'
522 }).on('exit', function (code, signal) {
523 console.log('EMULATOR EXITED', code, signal);
524 });
525 break;
526
527 case 'device':
528 cmdAdd('install', tiapp.name, argv['android-sdk'], argv['project-dir'], tiapp.id, 1);
529 break;
530
531 case 'dist-playstore':
532 cmdAdd('distribute', tiapp.name, argv['android-sdk'], argv['project-dir'], tiapp.id, argv['keystore']);
533 cmdAddSecret(argv['password']);
534 cmdAdd(argv['alias'], argv['output-dir']);
535 break;
536 }
537 }
538
539 // Add debug host if it's defined
540 if (argv['debug-host']) {
541 if (argv.target === 'device') {
542 cmdAdd('');
543 }
544 cmdAdd(argv['debug-host']);
545 }
546 // Add profiler host if it's defined
547 if (argv['profiler-host']) {
548 if (argv.target === 'device') {
549 cmdAdd('');
550 }
551 cmdAdd(argv['profiler-host']);
552 cmdAdd('profiler');
553 }
554 }
555
556 } else {
557
558 // 3.0.0's iOS build does not like it if node has a full path, so we hope they have node in the path
559 cmdRoot = version.gte(sdkVersion, '3.0.2') ? (process.execPath || 'node') : 'node';
560
561 hideBanner = true;
562
563 // If the titanium path has spaces, then we are trying to combine the paths and verify after they were split.
564 var titaniumPath = (function getTitaniumPath (params) {
565 var paramsArray = params.split(' '),
566 pathSegment,
567 prevPath = '';
568 while ((pathSegment = paramsArray.pop())) {
569 if (fs.existsSync(pathSegment + prevPath)) {
570 return pathSegment + prevPath;
571 }
572 prevPath = ' ' + pathSegment;
573 }
574 // fallback to default last segment, if we fail for any reason.
575 return params.split(' ').pop();
576 }(argv.$0));
577
578 cmdAdd(titaniumPath);
579 cmdAdd(commandName, '--sdk', sdkName);
580
581 var flags = {},
582 options = {};
583
584 // mix the command and platform specific options together
585 [ cli.globalContext, cli.command, cli.command.platform ].forEach(function (ctx) {
586 if (ctx && ctx.conf) {
587 ctx.conf.flags && appc.util.mix(flags, ctx.conf.flags);
588 ctx.conf.options && appc.util.mix(options, ctx.conf.options);
589 }
590 });
591
592 Object.keys(flags).forEach(function (name) {
593 var def = Object.prototype.hasOwnProperty.call(flags[name], 'default') ? flags[name].default : false;
594 if (argv[name] !== undefined && def !== argv[name]) {
595 cmdAdd('--' + (argv[name] ? '' : 'no-') + name);
596 }
597 });
598
599 Object.keys(options).forEach(function (name) {
600 if (name !== 'sdk' && argv[name] !== undefined) {
601 // in 3.2, we renamed --password to --store-password as to not conflict with the
602 // authentication --password option
603 var arg = name;
604 if (argv.platform === 'android' && arg === 'store-password' && version.lt(sdkVersion, '3.2.0')) {
605 arg = 'password';
606 }
607
608 cmdAdd('--' + arg);
609 if (options[name].secret) {
610 cmdAddSecret(argv[name]);
611 } else {
612 cmdAdd(argv[name]);
613 }
614 }
615 });
616 }
617
618 // trim off the empty trailing args
619 while (!cmd[cmd.length - 1]) {
620 cmd.pop();
621 cmdSafe.pop();
622 }
623
624 if (argv.legacy) {
625 logger.info(__('Forking legacy SDK command: %s', (cmdRoot + ' "' + cmdSafe.join('" "') + '"').cyan) + '\n');
626 } else {
627 logger.info(__('Forking correct SDK command: %s', ('"' + cmdRoot + '" "' + cmdSafe.join('" "') + '"').cyan) + '\n');
628 }
629
630 hideBanner && cmd.push('--no-banner');
631
632 // when doing a legacy Android build (1.X or 2.X), then we delay the build to
633 // allow the emulator to start because there is a bug where the builder.py
634 // doesn't like to be run concurrently
635 setTimeout(function () {
636 spawn(cmdRoot, cmd, {
637 stdio: 'inherit'
638 }).on('exit', function (code, signal) {
639 code && process.exit(code);
640 });
641 }, delayCmd ? 1000 : 0);
642};
643
644exports.validateAppJsExists = function (projectDir, logger, platformDirs) {
645 if (!fs.existsSync(path.join(projectDir, 'Resources'))) {
646 logger.error(__('"Resources" directory not found'));
647 logger.error(__('Ensure the "Resources" directory exists and contains an "app.js" file.') + '\n');
648 process.exit(1);
649 }
650
651 const files = [
652 path.join(projectDir, 'Resources', 'app.js')
653 ];
654
655 Array.isArray(platformDirs) || (platformDirs = [ platformDirs ]);
656 platformDirs.forEach(function (platformDir) {
657 files.push(path.join(projectDir, 'Resources', platformDir, 'app.js'));
658 });
659
660 if (!files.some(file => fs.existsSync(file))) {
661 logger.error(__('"app.js" not found'));
662 logger.error(__('Ensure the "app.js" file exists in your project\'s "Resources" directory.') + '\n');
663 process.exit(1);
664 }
665};
666
667exports.validatePlatformOptions = function (logger, config, cli, commandName) {
668 const platform = exports.resolvePlatform(cli.argv.platform),
669 platformCommand = path.join(path.dirname(module.filename), '..', '..', '..', manifest.platforms[manifest.platforms.indexOf(platform)], 'cli', 'commands', '_' + commandName + '.js');
670 if (fs.existsSync(platformCommand)) {
671 const command = require(platformCommand);
672 return command && typeof command.validate === 'function' ? command.validate(logger, config, cli) : null;
673 }
674};
675
676exports.scrubPlatforms = function (platforms) {
677 const scrubbed = {}, // distinct list of un-aliased platforms
678 original = {},
679 bad = {};
680
681 platforms.toLowerCase().split(',').forEach(function (platform) {
682 const name = platformAliases[platform] || platform;
683 // if name is falsey, then it's invalid anyways
684 if (name) {
685 if (manifest.platforms.indexOf(name) === -1) {
686 bad[platform] = 1;
687 } else {
688 scrubbed[name] = 1;
689 original[platform] = 1;
690 }
691 }
692 });
693
694 return {
695 scrubbed: Object.keys(scrubbed).sort(), // distinct list of un-aliased platforms
696 original: Object.keys(original).sort(),
697 bad: Object.keys(bad).sort()
698 };
699};
700
701exports.resolvePlatform = function (platform) {
702 return platformAliases[platform] || platform;
703};
704
705exports.filterPlatforms = function (platform) {
706 platform = platformAliases[platform] || platform;
707 return exports.availablePlatformsNames.filter(name => name != platform); // eslint-disable-line eqeqeq
708};
709
710exports.validatePlatform = function (logger, cli, name) {
711 const platform = name ? cli.argv[name] : cli.argv,
712 p = cli.argv[name] = platformAliases[platform] || platform;
713 if (!p || manifest.platforms.indexOf(p) === -1) {
714 logger.banner();
715 logger.error(__('Invalid platform "%s"', platform) + '\n');
716 appc.string.suggest(platform, exports.targetPlatforms, logger.log);
717 logger.log(__('Available platforms for SDK version %s:', cli.sdk && cli.sdk.name || manifest.version));
718 exports.targetPlatforms.forEach(p => logger.log(' ' + p.cyan));
719 logger.log();
720 process.exit(1);
721 }
722};