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