UNPKG

24.9 kBJavaScriptView Raw
1import fs from 'fs';
2import path from 'path';
3import _ from 'lodash';
4import { ArgumentParser } from 'argparse';
5import { rootDir } from './utils';
6import { DEFAULT_BASE_PATH } from 'appium-base-driver';
7import {
8 StoreDeprecatedAction, StoreDeprecatedTrueAction,
9 StoreDeprecatedDefaultCapabilityAction, StoreDeprecatedDefaultCapabilityTrueAction,
10 DEFAULT_CAPS_ARG,
11} from './argsparse-actions';
12
13
14const args = [
15 [['--shell'], {
16 required: false,
17 default: false,
18 action: 'store_true',
19 help: 'Enter REPL mode',
20 dest: 'shell',
21 }],
22
23 [['--allow-cors'], {
24 required: false,
25 default: false,
26 action: 'store_true',
27 help: 'Whether the Appium server should allow web browser connections from any host',
28 dest: 'allowCors',
29 }],
30
31 [['--reboot'], {
32 default: false,
33 dest: 'reboot',
34 action: 'store_true',
35 required: false,
36 help: '(Android-only) reboot emulator after each session and kill it at the end',
37 }],
38
39 [['--ipa'], {
40 required: false,
41 default: null,
42 help: '(IOS-only) abs path to compiled .ipa file',
43 dest: 'ipa',
44 }],
45
46 [['-a', '--address'], {
47 default: '0.0.0.0',
48 required: false,
49 help: 'IP Address to listen on',
50 dest: 'address',
51 }],
52
53 [['-p', '--port'], {
54 default: 4723,
55 required: false,
56 type: 'int',
57 help: 'port to listen on',
58 dest: 'port',
59 }],
60
61 [['-pa', '--base-path'], {
62 required: false,
63 default: DEFAULT_BASE_PATH,
64 dest: 'basePath',
65 help: 'Base path to use as the prefix for all webdriver routes running' +
66 'on this server'
67 }],
68
69 [['-ka', '--keep-alive-timeout'], {
70 required: false,
71 default: null,
72 dest: 'keepAliveTimeout',
73 type: 'int',
74 help: 'Number of seconds the Appium server should apply as both the keep-alive timeout ' +
75 'and the connection timeout for all requests. Defaults to 600 (10 minutes).'
76 }],
77
78 [['-ca', '--callback-address'], {
79 required: false,
80 dest: 'callbackAddress',
81 default: null,
82 help: 'callback IP Address (default: same as --address)',
83 }],
84
85 [['-cp', '--callback-port'], {
86 required: false,
87 dest: 'callbackPort',
88 default: null,
89 type: 'int',
90 help: 'callback port (default: same as port)',
91 }],
92
93 [['-bp', '--bootstrap-port'], {
94 default: 4724,
95 dest: 'bootstrapPort',
96 required: false,
97 type: 'int',
98 help: '(Android-only) port to use on device to talk to Appium',
99 }],
100
101 [['-r', '--backend-retries'], {
102 default: 3,
103 dest: 'backendRetries',
104 required: false,
105 type: 'int',
106 help: '(iOS-only) How many times to retry launching Instruments ' +
107 'before saying it crashed or timed out',
108 }],
109
110 [['--session-override'], {
111 default: false,
112 dest: 'sessionOverride',
113 action: 'store_true',
114 required: false,
115 help: 'Enables session override (clobbering)',
116 }],
117
118 [['-l', '--pre-launch'], {
119 default: false,
120 dest: 'launch',
121 action: 'store_true',
122 required: false,
123 help: 'Pre-launch the application before allowing the first session ' +
124 '(Requires --app and, for Android, --app-pkg and --app-activity)',
125 }],
126
127 [['-g', '--log'], {
128 default: null,
129 dest: 'logFile',
130 required: false,
131 help: 'Also send log output to this file',
132 }],
133
134 [['--log-level'], {
135 choices: [
136 'info', 'info:debug', 'info:info', 'info:warn', 'info:error',
137 'warn', 'warn:debug', 'warn:info', 'warn:warn', 'warn:error',
138 'error', 'error:debug', 'error:info', 'error:warn', 'error:error',
139 'debug', 'debug:debug', 'debug:info', 'debug:warn', 'debug:error',
140 ],
141 default: 'debug',
142 dest: 'loglevel',
143 required: false,
144 help: 'log level; default (console[:file]): debug[:debug]',
145 }],
146
147 [['--log-timestamp'], {
148 default: false,
149 required: false,
150 help: 'Show timestamps in console output',
151 action: 'store_true',
152 dest: 'logTimestamp',
153 }],
154
155 [['--local-timezone'], {
156 default: false,
157 required: false,
158 help: 'Use local timezone for timestamps',
159 action: 'store_true',
160 dest: 'localTimezone',
161 }],
162
163 [['--log-no-colors'], {
164 default: false,
165 required: false,
166 help: 'Do not use colors in console output',
167 action: 'store_true',
168 dest: 'logNoColors',
169 }],
170
171 [['-G', '--webhook'], {
172 default: null,
173 required: false,
174 dest: 'webhook',
175 help: 'Also send log output to this HTTP listener, for example localhost:9876',
176 }],
177
178 [['--safari'], {
179 default: false,
180 action: 'store_true',
181 dest: 'safari',
182 required: false,
183 help: '(IOS-Only) Use the safari app',
184 }],
185
186 [['--default-device', '-dd'], {
187 dest: 'defaultDevice',
188 default: false,
189 action: 'store_true',
190 required: false,
191 help: '(IOS-Simulator-only) use the default simulator that instruments ' +
192 'launches on its own',
193 }],
194
195 [['--force-iphone'], {
196 default: false,
197 dest: 'forceIphone',
198 action: 'store_true',
199 required: false,
200 help: '(IOS-only) Use the iPhone Simulator no matter what the app wants',
201 }],
202
203 [['--force-ipad'], {
204 default: false,
205 dest: 'forceIpad',
206 action: 'store_true',
207 required: false,
208 help: '(IOS-only) Use the iPad Simulator no matter what the app wants',
209 }],
210
211 [['--tracetemplate'], {
212 default: null,
213 dest: 'automationTraceTemplatePath',
214 required: false,
215 help: '(IOS-only) .tracetemplate file to use with Instruments',
216 }],
217
218 [['--instruments'], {
219 default: null,
220 dest: 'instrumentsPath',
221 required: false,
222 help: '(IOS-only) path to instruments binary',
223 }],
224
225 [['--nodeconfig'], {
226 required: false,
227 default: null,
228 dest: 'nodeconfig',
229 help: 'Configuration JSON file to register appium with selenium grid',
230 }],
231
232 [['-ra', '--robot-address'], {
233 default: '0.0.0.0',
234 dest: 'robotAddress',
235 required: false,
236 help: 'IP Address of robot',
237 }],
238
239 [['-rp', '--robot-port'], {
240 default: -1,
241 dest: 'robotPort',
242 required: false,
243 type: 'int',
244 help: 'port for robot',
245 }],
246
247 [['--chromedriver-executable'], {
248 default: null,
249 dest: 'chromedriverExecutable',
250 required: false,
251 help: 'ChromeDriver executable full path',
252 }],
253
254 [['--show-config'], {
255 default: false,
256 dest: 'showConfig',
257 action: 'store_true',
258 required: false,
259 help: 'Show info about the appium server configuration and exit',
260 }],
261
262 [['--no-perms-check'], {
263 default: false,
264 dest: 'noPermsCheck',
265 action: 'store_true',
266 required: false,
267 help: 'Bypass Appium\'s checks to ensure we can read/write necessary files',
268 }],
269
270 [['--strict-caps'], {
271 default: false,
272 dest: 'enforceStrictCaps',
273 action: 'store_true',
274 required: false,
275 help: 'Cause sessions to fail if desired caps are sent in that Appium ' +
276 'does not recognize as valid for the selected device',
277 }],
278
279 [['--isolate-sim-device'], {
280 default: false,
281 dest: 'isolateSimDevice',
282 action: 'store_true',
283 required: false,
284 help: 'Xcode 6 has a bug on some platforms where a certain simulator ' +
285 'can only be launched without error if all other simulator devices ' +
286 'are first deleted. This option causes Appium to delete all ' +
287 'devices other than the one being used by Appium. Note that this ' +
288 'is a permanent deletion, and you are responsible for using simctl ' +
289 'or xcode to manage the categories of devices used with Appium.',
290 }],
291
292 [['--tmp'], {
293 default: null,
294 dest: 'tmpDir',
295 required: false,
296 help: 'Absolute path to directory Appium can use to manage temporary ' +
297 'files, like built-in iOS apps it needs to move around. On *nix/Mac ' +
298 'defaults to /tmp, on Windows defaults to C:\\Windows\\Temp',
299 }],
300
301 [['--trace-dir'], {
302 default: null,
303 dest: 'traceDir',
304 required: false,
305 help: 'Absolute path to directory Appium use to save ios instruments ' +
306 'traces, defaults to <tmp dir>/appium-instruments',
307 }],
308
309 [['--debug-log-spacing'], {
310 dest: 'debugLogSpacing',
311 default: false,
312 action: 'store_true',
313 required: false,
314 help: 'Add exaggerated spacing in logs to help with visual inspection',
315 }],
316
317 [['--suppress-adb-kill-server'], {
318 dest: 'suppressKillServer',
319 default: false,
320 action: 'store_true',
321 required: false,
322 help: '(Android-only) If set, prevents Appium from killing the adb server instance',
323 }],
324
325 [['--long-stacktrace'], {
326 dest: 'longStacktrace',
327 default: false,
328 required: false,
329 action: 'store_true',
330 help: 'Add long stack traces to log entries. Recommended for debugging only.',
331 }],
332
333 [['--webkit-debug-proxy-port'], {
334 default: 27753,
335 dest: 'webkitDebugProxyPort',
336 required: false,
337 type: 'int',
338 help: '(IOS-only) Local port used for communication with ios-webkit-debug-proxy'
339 }],
340
341 [['--webdriveragent-port'], {
342 default: 8100,
343 dest: 'wdaLocalPort',
344 required: false,
345 type: 'int',
346 help: '(IOS-only, XCUITest-only) Local port used for communication with WebDriverAgent'
347 }],
348
349 [['-dc', DEFAULT_CAPS_ARG], {
350 dest: 'defaultCapabilities',
351 default: {},
352 type: parseDefaultCaps,
353 required: false,
354 help: 'Set the default desired capabilities, which will be set on each ' +
355 'session unless overridden by received capabilities. For example: ' +
356 '[ \'{"app": "myapp.app", "deviceName": "iPhone Simulator"}\' ' +
357 '| /path/to/caps.json ]'
358 }],
359
360 [['--relaxed-security'], {
361 default: false,
362 dest: 'relaxedSecurityEnabled',
363 action: 'store_true',
364 required: false,
365 help: 'Disable additional security checks, so it is possible to use some advanced features, provided ' +
366 'by drivers supporting this option. Only enable it if all the ' +
367 'clients are in the trusted network and it\'s not the case if a client could potentially ' +
368 'break out of the session sandbox. Specific features can be overridden by ' +
369 'using the --deny-insecure flag',
370 }],
371
372 [['--allow-insecure'], {
373 dest: 'allowInsecure',
374 default: [],
375 type: parseSecurityFeatures,
376 required: false,
377 help: 'Set which insecure features are allowed to run in this server\'s sessions. ' +
378 'Features are defined on a driver level; see documentation for more details. ' +
379 'This should be either a comma-separated list of feature names, or a path to ' +
380 'a file where each feature name is on a line. Note that features defined via ' +
381 '--deny-insecure will be disabled, even if also listed here. For example: ' +
382 'execute_driver_script,adb_shell',
383 }],
384
385 [['--deny-insecure'], {
386 dest: 'denyInsecure',
387 default: [],
388 type: parseSecurityFeatures,
389 required: false,
390 help: 'Set which insecure features are not allowed to run in this server\'s sessions. ' +
391 'Features are defined on a driver level; see documentation for more details. ' +
392 'This should be either a comma-separated list of feature names, or a path to ' +
393 'a file where each feature name is on a line. Features listed here will not be ' +
394 'enabled even if also listed in --allow-insecure, and even if --relaxed-security ' +
395 'is turned on. For example: execute_driver_script,adb_shell',
396 }],
397
398 [['--command-timeout'], {
399 default: 60,
400 dest: 'defaultCommandTimeout',
401 type: 'int',
402 required: false,
403 deprecated_for: 'newCommandTimeout capability',
404 action: StoreDeprecatedAction,
405 help: 'No effect. This used to be the default command ' +
406 'timeout for the server to use for all sessions (in seconds and ' +
407 'should be less than 2147483). Use newCommandTimeout cap instead'
408 }],
409
410 [['-k', '--keep-artifacts'], {
411 default: false,
412 dest: 'keepArtifacts',
413 action: StoreDeprecatedTrueAction,
414 required: false,
415 help: 'No effect, trace is now in tmp dir by default and is ' +
416 'cleared before each run. Please also refer to the --trace-dir flag.',
417 }],
418
419 [['--platform-name'], {
420 dest: 'platformName',
421 default: null,
422 required: false,
423 action: StoreDeprecatedDefaultCapabilityAction,
424 help: 'Name of the mobile platform: iOS, Android, or FirefoxOS',
425 }],
426
427 [['--platform-version'], {
428 dest: 'platformVersion',
429 default: null,
430 required: false,
431 action: StoreDeprecatedDefaultCapabilityAction,
432 help: 'Version of the mobile platform',
433 }],
434
435 [['--automation-name'], {
436 dest: 'automationName',
437 default: null,
438 required: false,
439 action: StoreDeprecatedDefaultCapabilityAction,
440 help: 'Name of the automation tool: Appium, XCUITest, etc.',
441 }],
442
443 [['--device-name'], {
444 dest: 'deviceName',
445 default: null,
446 required: false,
447 action: StoreDeprecatedDefaultCapabilityAction,
448 help: 'Name of the mobile device to use, for example: ' +
449 'iPhone Retina (4-inch), Android Emulator',
450 }],
451
452 [['--browser-name'], {
453 dest: 'browserName',
454 default: null,
455 required: false,
456 action: StoreDeprecatedDefaultCapabilityAction,
457 help: 'Name of the mobile browser: Safari or Chrome',
458 }],
459
460 [['--app'], {
461 dest: 'app',
462 required: false,
463 default: null,
464 action: StoreDeprecatedDefaultCapabilityAction,
465 help: 'IOS: abs path to simulator-compiled .app file or the ' +
466 'bundle_id of the desired target on device; Android: abs path to .apk file',
467 }],
468
469 [['-lt', '--launch-timeout'], {
470 default: 90000,
471 dest: 'launchTimeout',
472 type: 'int',
473 required: false,
474 action: StoreDeprecatedDefaultCapabilityAction,
475 help: '(iOS-only) how long in ms to wait for Instruments to launch',
476 }],
477
478 [['--language'], {
479 default: null,
480 dest: 'language',
481 required: false,
482 action: StoreDeprecatedDefaultCapabilityAction,
483 help: 'Language for the iOS simulator / Android Emulator, like: en, es',
484 }],
485
486 [['--locale'], {
487 default: null,
488 dest: 'locale',
489 required: false,
490 action: StoreDeprecatedDefaultCapabilityAction,
491 help: 'Locale for the iOS simulator / Android Emulator, like en_US, de_DE',
492 }],
493
494 [['-U', '--udid'], {
495 dest: 'udid',
496 required: false,
497 default: null,
498 action: StoreDeprecatedDefaultCapabilityAction,
499 help: 'Unique device identifier of the connected physical device',
500 }],
501
502 [['--orientation'], {
503 dest: 'orientation',
504 default: null,
505 required: false,
506 action: StoreDeprecatedDefaultCapabilityAction,
507 help: '(IOS-only) use LANDSCAPE or PORTRAIT to initialize all requests ' +
508 'to this orientation',
509 }],
510
511 [['--no-reset'], {
512 default: false,
513 dest: 'noReset',
514 action: StoreDeprecatedDefaultCapabilityTrueAction,
515 required: false,
516 help: 'Do not reset app state between sessions (IOS: do not delete app ' +
517 'plist files; Android: do not uninstall app before new session)',
518 }],
519
520 [['--full-reset'], {
521 default: false,
522 dest: 'fullReset',
523 action: StoreDeprecatedDefaultCapabilityTrueAction,
524 required: false,
525 help: '(iOS) Delete the entire simulator folder. (Android) Reset app ' +
526 'state by uninstalling app instead of clearing app data. On ' +
527 'Android, this will also remove the app after the session is complete.',
528 }],
529
530 [['--app-pkg'], {
531 dest: 'appPackage',
532 default: null,
533 required: false,
534 action: StoreDeprecatedDefaultCapabilityAction,
535 help: '(Android-only) Java package of the Android app you want to run ' +
536 '(e.g., com.example.android.myApp)',
537 }],
538
539 [['--app-activity'], {
540 dest: 'appActivity',
541 default: null,
542 required: false,
543 action: StoreDeprecatedDefaultCapabilityAction,
544 help: '(Android-only) Activity name for the Android activity you want ' +
545 'to launch from your package (e.g., MainActivity)',
546 }],
547
548 [['--app-wait-package'], {
549 dest: 'appWaitPackage',
550 default: false,
551 required: false,
552 action: StoreDeprecatedDefaultCapabilityAction,
553 help: '(Android-only) Package name for the Android activity you want ' +
554 'to wait for (e.g., com.example.android.myApp)',
555 }],
556
557 [['--app-wait-activity'], {
558 dest: 'appWaitActivity',
559 default: false,
560 required: false,
561 action: StoreDeprecatedDefaultCapabilityAction,
562 help: '(Android-only) Activity name for the Android activity you want ' +
563 'to wait for (e.g., SplashActivity)',
564 }],
565
566 [['--device-ready-timeout'], {
567 dest: 'deviceReadyTimeout',
568 default: 5,
569 required: false,
570 type: 'int',
571 action: StoreDeprecatedDefaultCapabilityAction,
572 help: '(Android-only) Timeout in seconds while waiting for device to become ready',
573 }],
574
575 [['--android-coverage'], {
576 dest: 'androidCoverage',
577 default: false,
578 required: false,
579 action: StoreDeprecatedDefaultCapabilityAction,
580 help: '(Android-only) Fully qualified instrumentation class. Passed to -w in ' +
581 'adb shell am instrument -e coverage true -w ' +
582 '(e.g. com.my.Pkg/com.my.Pkg.instrumentation.MyInstrumentation)',
583 }],
584
585 [['--avd'], {
586 dest: 'avd',
587 default: null,
588 required: false,
589 action: StoreDeprecatedDefaultCapabilityAction,
590 help: '(Android-only) Name of the avd to launch (e.g. @Nexus_5)',
591 }],
592
593 [['--avd-args'], {
594 dest: 'avdArgs',
595 default: null,
596 required: false,
597 action: StoreDeprecatedDefaultCapabilityAction,
598 help: '(Android-only) Additional emulator arguments to launch the avd (e.g. -no-snapshot-load)',
599 }],
600
601 [['--use-keystore'], {
602 default: false,
603 dest: 'useKeystore',
604 action: StoreDeprecatedDefaultCapabilityTrueAction,
605 required: false,
606 help: '(Android-only) When set the keystore will be used to sign apks.',
607 }],
608
609 [['--keystore-path'], {
610 default: path.resolve(process.env.HOME || process.env.USERPROFILE || '', '.android', 'debug.keystore'),
611 dest: 'keystorePath',
612 required: false,
613 action: StoreDeprecatedDefaultCapabilityAction,
614 help: '(Android-only) Path to keystore',
615 }],
616
617 [['--keystore-password'], {
618 default: 'android',
619 dest: 'keystorePassword',
620 required: false,
621 action: StoreDeprecatedDefaultCapabilityAction,
622 help: '(Android-only) Password to keystore',
623 }],
624
625 [['--key-alias'], {
626 default: 'androiddebugkey',
627 dest: 'keyAlias',
628 required: false,
629 action: StoreDeprecatedDefaultCapabilityAction,
630 help: '(Android-only) Key alias',
631 }],
632
633 [['--key-password'], {
634 default: 'android',
635 dest: 'keyPassword',
636 required: false,
637 action: StoreDeprecatedDefaultCapabilityAction,
638 help: '(Android-only) Key password',
639 }],
640
641 [['--intent-action'], {
642 dest: 'intentAction',
643 default: 'android.intent.action.MAIN',
644 required: false,
645 action: StoreDeprecatedDefaultCapabilityAction,
646 help: '(Android-only) Intent action which will be used to start activity (e.g. android.intent.action.MAIN)',
647 }],
648
649 [['--intent-category'], {
650 dest: 'intentCategory',
651 default: 'android.intent.category.LAUNCHER',
652 required: false,
653 action: StoreDeprecatedDefaultCapabilityAction,
654 help: '(Android-only) Intent category which will be used to start activity ' +
655 '(e.g. android.intent.category.APP_CONTACTS)',
656 }],
657
658 [['--intent-flags'], {
659 dest: 'intentFlags',
660 default: '0x10200000',
661 required: false,
662 action: StoreDeprecatedDefaultCapabilityAction,
663 help: '(Android-only) Flags that will be used to start activity (e.g. 0x10200000)',
664 }],
665
666 [['--intent-args'], {
667 dest: 'optionalIntentArguments',
668 default: null,
669 required: false,
670 action: StoreDeprecatedDefaultCapabilityAction,
671 help: '(Android-only) Additional intent arguments that will be used to start activity (e.g. 0x10200000)',
672 }],
673
674 [['--dont-stop-app-on-reset'], {
675 dest: 'dontStopAppOnReset',
676 default: false,
677 required: false,
678 action: StoreDeprecatedDefaultCapabilityTrueAction,
679 help: '(Android-only) When included, refrains from stopping the app before restart',
680 }],
681
682 [['--calendar-format'], {
683 default: null,
684 dest: 'calendarFormat',
685 required: false,
686 action: StoreDeprecatedDefaultCapabilityAction,
687 help: '(IOS-only) calendar format for the iOS simulator (e.g. gregorian)',
688 }],
689
690 [['--native-instruments-lib'], {
691 default: false,
692 dest: 'nativeInstrumentsLib',
693 action: StoreDeprecatedDefaultCapabilityTrueAction,
694 required: false,
695 help: '(IOS-only) IOS has a weird built-in unavoidable ' +
696 'delay. We patch this in appium. If you do not want it patched, pass in this flag.',
697 }],
698
699 [['--keep-keychains'], {
700 default: false,
701 dest: 'keepKeyChains',
702 action: StoreDeprecatedDefaultCapabilityTrueAction,
703 required: false,
704 help: '(iOS-only) Whether to keep keychains ' +
705 '(Library/Keychains) when reset app between sessions',
706 }],
707
708 [['--localizable-strings-dir'], {
709 required: false,
710 dest: 'localizableStringsDir',
711 default: 'en.lproj',
712 action: StoreDeprecatedDefaultCapabilityAction,
713 help: '(IOS-only) the relative path of the dir where Localizable.strings file resides (e.g. en.lproj)',
714 }],
715
716 [['--show-ios-log'], {
717 default: false,
718 dest: 'showIOSLog',
719 action: StoreDeprecatedDefaultCapabilityTrueAction,
720 required: false,
721 help: '(IOS-only) if set, the iOS system log will be written to the console',
722 }],
723
724 [['--async-trace'], {
725 dest: 'longStacktrace',
726 default: false,
727 required: false,
728 action: StoreDeprecatedDefaultCapabilityTrueAction,
729 help: 'Add long stack traces to log entries. Recommended for debugging only.',
730 }],
731
732 [['--chromedriver-port'], {
733 default: null,
734 dest: 'chromedriverPort',
735 required: false,
736 type: 'int',
737 action: StoreDeprecatedDefaultCapabilityAction,
738 help: 'Port upon which ChromeDriver will run. If not given, ' +
739 'Android driver will pick a random available port.',
740 }],
741
742 [['--log-filters'], {
743 dest: 'logFilters',
744 default: null,
745 required: false,
746 help: 'Set the full path to a JSON file containing one or more log filtering rules',
747 }],
748];
749
750function parseSecurityFeatures (features) {
751 const splitter = (splitOn, str) => `${str}`.split(splitOn)
752 .map((s) => s.trim())
753 .filter(Boolean);
754 let parsedFeatures;
755 try {
756 parsedFeatures = splitter(',', features);
757 } catch (err) {
758 throw new TypeError('Could not parse value of --allow/deny-insecure. Should be ' +
759 'a list of strings separated by commas, or a path to a file ' +
760 'listing one feature name per line.');
761 }
762
763 if (parsedFeatures.length === 1 && fs.existsSync(parsedFeatures[0])) {
764 // we might have a file which is a list of features
765 try {
766 const fileFeatures = fs.readFileSync(parsedFeatures[0], 'utf8');
767 parsedFeatures = splitter('\n', fileFeatures);
768 } catch (err) {
769 throw new TypeError(`Attempted to read --allow/deny-insecure feature names ` +
770 `from file ${parsedFeatures[0]} but got error: ${err.message}`);
771 }
772 }
773
774 return parsedFeatures;
775}
776
777function parseDefaultCaps (capsOrPath) {
778 let caps = capsOrPath;
779 let loadedFromFile = false;
780 try {
781 // use synchronous file access, as `argparse` provides no way of either
782 // awaiting or using callbacks. This step happens in startup, in what is
783 // effectively command-line code, so nothing is blocked in terms of
784 // sessions, so holding up the event loop does not incur the usual
785 // drawbacks.
786 if (_.isString(capsOrPath) && fs.statSync(capsOrPath).isFile()) {
787 caps = fs.readFileSync(capsOrPath, 'utf8');
788 loadedFromFile = true;
789 }
790 } catch (err) {
791 // not a file, or not readable
792 }
793 try {
794 const result = JSON.parse(caps);
795 if (!_.isPlainObject(result)) {
796 throw new Error(`'${_.truncate(result, {length: 100})}' is not an object`);
797 }
798 return result;
799 } catch (e) {
800 const msg = loadedFromFile
801 ? `Default capabilities in '${capsOrPath}' must be a valid JSON`
802 : `Default capabilities must be a valid JSON`;
803 throw new TypeError(`${msg}. Original error: ${e.message}`);
804 }
805}
806
807function getParser () {
808 const parser = new ArgumentParser({
809 add_help: true,
810 description: 'A webdriver-compatible server for use with native and hybrid iOS and Android applications.',
811 prog: process.argv[1] || 'Appium'
812 });
813 parser.rawArgs = args;
814 for (const [flagsOrNames, options] of args) {
815 parser.add_argument(...flagsOrNames, options);
816 }
817 parser.add_argument('-v', '--version', {
818 action: 'version',
819 version: require(path.resolve(rootDir, 'package.json')).version,
820 });
821 return parser;
822}
823
824function getDefaultArgs () {
825 return args.reduce((acc, [, {dest, default: defaultValue}]) => {
826 acc[dest] = defaultValue;
827 return acc;
828 }, {});
829}
830
831export default getParser;
832export { getDefaultArgs, getParser };