appium 2.0.0-beta.9 → 2.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +149 -58
  3. package/build/lib/appium.d.ts +229 -0
  4. package/build/lib/appium.d.ts.map +1 -0
  5. package/build/lib/appium.js +677 -449
  6. package/build/lib/appium.js.map +1 -0
  7. package/build/lib/cli/args.d.ts +17 -0
  8. package/build/lib/cli/args.d.ts.map +1 -0
  9. package/build/lib/cli/args.js +263 -300
  10. package/build/lib/cli/args.js.map +1 -0
  11. package/build/lib/cli/driver-command.d.ts +102 -0
  12. package/build/lib/cli/driver-command.d.ts.map +1 -0
  13. package/build/lib/cli/driver-command.js +131 -81
  14. package/build/lib/cli/driver-command.js.map +1 -0
  15. package/build/lib/cli/extension-command.d.ts +402 -0
  16. package/build/lib/cli/extension-command.d.ts.map +1 -0
  17. package/build/lib/cli/extension-command.js +799 -383
  18. package/build/lib/cli/extension-command.js.map +1 -0
  19. package/build/lib/cli/extension.d.ts +23 -0
  20. package/build/lib/cli/extension.d.ts.map +1 -0
  21. package/build/lib/cli/extension.js +70 -68
  22. package/build/lib/cli/extension.js.map +1 -0
  23. package/build/lib/cli/parser.d.ts +84 -0
  24. package/build/lib/cli/parser.d.ts.map +1 -0
  25. package/build/lib/cli/parser.js +252 -148
  26. package/build/lib/cli/parser.js.map +1 -0
  27. package/build/lib/cli/plugin-command.d.ts +99 -0
  28. package/build/lib/cli/plugin-command.d.ts.map +1 -0
  29. package/build/lib/cli/plugin-command.js +125 -81
  30. package/build/lib/cli/plugin-command.js.map +1 -0
  31. package/build/lib/cli/utils.d.ts +29 -0
  32. package/build/lib/cli/utils.d.ts.map +1 -0
  33. package/build/lib/cli/utils.js +72 -51
  34. package/build/lib/cli/utils.js.map +1 -0
  35. package/build/lib/config-file.d.ts +100 -0
  36. package/build/lib/config-file.d.ts.map +1 -0
  37. package/build/lib/config-file.js +207 -0
  38. package/build/lib/config-file.js.map +1 -0
  39. package/build/lib/config.d.ts +49 -0
  40. package/build/lib/config.d.ts.map +1 -0
  41. package/build/lib/config.js +262 -223
  42. package/build/lib/config.js.map +1 -0
  43. package/build/lib/constants.d.ts +56 -0
  44. package/build/lib/constants.d.ts.map +1 -0
  45. package/build/lib/constants.js +73 -0
  46. package/build/lib/constants.js.map +1 -0
  47. package/build/lib/extension/driver-config.d.ts +82 -0
  48. package/build/lib/extension/driver-config.d.ts.map +1 -0
  49. package/build/lib/extension/driver-config.js +210 -0
  50. package/build/lib/extension/driver-config.js.map +1 -0
  51. package/build/lib/extension/extension-config.d.ts +270 -0
  52. package/build/lib/extension/extension-config.d.ts.map +1 -0
  53. package/build/lib/extension/extension-config.js +601 -0
  54. package/build/lib/extension/extension-config.js.map +1 -0
  55. package/build/lib/extension/index.d.ts +48 -0
  56. package/build/lib/extension/index.d.ts.map +1 -0
  57. package/build/lib/extension/index.js +105 -0
  58. package/build/lib/extension/index.js.map +1 -0
  59. package/build/lib/extension/manifest-migrations.d.ts +27 -0
  60. package/build/lib/extension/manifest-migrations.d.ts.map +1 -0
  61. package/build/lib/extension/manifest-migrations.js +134 -0
  62. package/build/lib/extension/manifest-migrations.js.map +1 -0
  63. package/build/lib/extension/manifest.d.ts +145 -0
  64. package/build/lib/extension/manifest.d.ts.map +1 -0
  65. package/build/lib/extension/manifest.js +528 -0
  66. package/build/lib/extension/manifest.js.map +1 -0
  67. package/build/lib/extension/package-changed.d.ts +11 -0
  68. package/build/lib/extension/package-changed.d.ts.map +1 -0
  69. package/build/lib/extension/package-changed.js +62 -0
  70. package/build/lib/extension/package-changed.js.map +1 -0
  71. package/build/lib/extension/plugin-config.d.ts +56 -0
  72. package/build/lib/extension/plugin-config.d.ts.map +1 -0
  73. package/build/lib/extension/plugin-config.js +102 -0
  74. package/build/lib/extension/plugin-config.js.map +1 -0
  75. package/build/lib/grid-register.d.ts +10 -0
  76. package/build/lib/grid-register.d.ts.map +1 -0
  77. package/build/lib/grid-register.js +122 -144
  78. package/build/lib/grid-register.js.map +1 -0
  79. package/build/lib/logger.d.ts +3 -0
  80. package/build/lib/logger.d.ts.map +1 -0
  81. package/build/lib/logger.js +5 -17
  82. package/build/lib/logger.js.map +1 -0
  83. package/build/lib/logsink.d.ts +4 -0
  84. package/build/lib/logsink.d.ts.map +1 -0
  85. package/build/lib/logsink.js +189 -184
  86. package/build/lib/logsink.js.map +1 -0
  87. package/build/lib/main.d.ts +62 -0
  88. package/build/lib/main.d.ts.map +1 -0
  89. package/build/lib/main.js +406 -234
  90. package/build/lib/main.js.map +1 -0
  91. package/build/lib/schema/arg-spec.d.ts +143 -0
  92. package/build/lib/schema/arg-spec.d.ts.map +1 -0
  93. package/build/lib/schema/arg-spec.js +164 -0
  94. package/build/lib/schema/arg-spec.js.map +1 -0
  95. package/build/lib/schema/cli-args.d.ts +19 -0
  96. package/build/lib/schema/cli-args.d.ts.map +1 -0
  97. package/build/lib/schema/cli-args.js +220 -0
  98. package/build/lib/schema/cli-args.js.map +1 -0
  99. package/build/lib/schema/cli-transformers.d.ts +5 -0
  100. package/build/lib/schema/cli-transformers.d.ts.map +1 -0
  101. package/build/lib/schema/cli-transformers.js +124 -0
  102. package/build/lib/schema/cli-transformers.js.map +1 -0
  103. package/build/lib/schema/index.d.ts +3 -0
  104. package/build/lib/schema/index.d.ts.map +1 -0
  105. package/build/lib/schema/index.js +19 -0
  106. package/build/lib/schema/index.js.map +1 -0
  107. package/build/lib/schema/keywords.d.ts +24 -0
  108. package/build/lib/schema/keywords.d.ts.map +1 -0
  109. package/build/lib/schema/keywords.js +128 -0
  110. package/build/lib/schema/keywords.js.map +1 -0
  111. package/build/lib/schema/schema.d.ts +260 -0
  112. package/build/lib/schema/schema.d.ts.map +1 -0
  113. package/build/lib/schema/schema.js +640 -0
  114. package/build/lib/schema/schema.js.map +1 -0
  115. package/build/lib/utils.d.ts +276 -0
  116. package/build/lib/utils.d.ts.map +1 -0
  117. package/build/lib/utils.js +372 -192
  118. package/build/lib/utils.js.map +1 -0
  119. package/build/types/cli.d.ts +134 -0
  120. package/build/types/cli.d.ts.map +1 -0
  121. package/build/types/cli.js +3 -0
  122. package/build/types/cli.js.map +1 -0
  123. package/build/types/index.d.ts +15 -0
  124. package/build/types/index.d.ts.map +1 -0
  125. package/build/types/index.js +19 -0
  126. package/build/types/index.js.map +1 -0
  127. package/build/types/manifest/base.d.ts +135 -0
  128. package/build/types/manifest/base.d.ts.map +1 -0
  129. package/build/types/manifest/base.js +3 -0
  130. package/build/types/manifest/base.js.map +1 -0
  131. package/build/types/manifest/index.d.ts +21 -0
  132. package/build/types/manifest/index.d.ts.map +1 -0
  133. package/build/types/manifest/index.js +42 -0
  134. package/build/types/manifest/index.js.map +1 -0
  135. package/build/types/manifest/v3.d.ts +139 -0
  136. package/build/types/manifest/v3.d.ts.map +1 -0
  137. package/build/types/manifest/v3.js +3 -0
  138. package/build/types/manifest/v3.js.map +1 -0
  139. package/build/types/manifest/v4.d.ts +139 -0
  140. package/build/types/manifest/v4.d.ts.map +1 -0
  141. package/build/types/manifest/v4.js +3 -0
  142. package/build/types/manifest/v4.js.map +1 -0
  143. package/driver.d.ts +1 -0
  144. package/driver.js +14 -0
  145. package/index.js +11 -0
  146. package/lib/appium.js +545 -188
  147. package/lib/cli/args.js +275 -407
  148. package/lib/cli/driver-command.js +132 -24
  149. package/lib/cli/extension-command.js +751 -272
  150. package/lib/cli/extension.js +38 -19
  151. package/lib/cli/parser.js +267 -95
  152. package/lib/cli/plugin-command.js +122 -22
  153. package/lib/cli/utils.js +24 -10
  154. package/lib/config-file.js +220 -0
  155. package/lib/config.js +243 -132
  156. package/lib/constants.js +79 -0
  157. package/lib/extension/driver-config.js +247 -0
  158. package/lib/extension/extension-config.js +709 -0
  159. package/lib/extension/index.js +116 -0
  160. package/lib/extension/manifest-migrations.js +136 -0
  161. package/lib/extension/manifest.js +580 -0
  162. package/lib/extension/package-changed.js +64 -0
  163. package/lib/extension/plugin-config.js +112 -0
  164. package/lib/grid-register.js +49 -35
  165. package/lib/logger.js +1 -2
  166. package/lib/logsink.js +59 -36
  167. package/lib/main.js +392 -104
  168. package/lib/schema/arg-spec.js +229 -0
  169. package/lib/schema/cli-args.js +241 -0
  170. package/lib/schema/cli-transformers.js +119 -0
  171. package/lib/schema/index.js +2 -0
  172. package/lib/schema/keywords.js +136 -0
  173. package/lib/schema/schema.js +725 -0
  174. package/lib/utils.js +310 -89
  175. package/package.json +84 -84
  176. package/plugin.d.ts +1 -0
  177. package/plugin.js +13 -0
  178. package/scripts/autoinstall-extensions.js +243 -0
  179. package/support.d.ts +1 -0
  180. package/support.js +13 -0
  181. package/tsconfig.json +25 -0
  182. package/types/cli.ts +193 -0
  183. package/types/index.ts +20 -0
  184. package/types/manifest/README.md +30 -0
  185. package/types/manifest/base.ts +158 -0
  186. package/types/manifest/index.ts +28 -0
  187. package/types/manifest/v3.ts +161 -0
  188. package/types/manifest/v4.ts +161 -0
  189. package/CHANGELOG.md +0 -3669
  190. package/bin/ios-webkit-debug-proxy-launcher.js +0 -71
  191. package/build/lib/cli/argparse-actions.js +0 -104
  192. package/build/lib/cli/npm.js +0 -207
  193. package/build/lib/cli/parser-helpers.js +0 -93
  194. package/build/lib/driver-config.js +0 -77
  195. package/build/lib/drivers.js +0 -99
  196. package/build/lib/extension-config.js +0 -253
  197. package/build/lib/plugin-config.js +0 -59
  198. package/build/lib/plugins.js +0 -16
  199. package/build/postinstall.js +0 -90
  200. package/lib/cli/argparse-actions.js +0 -77
  201. package/lib/cli/npm.js +0 -183
  202. package/lib/cli/parser-helpers.js +0 -91
  203. package/lib/driver-config.js +0 -46
  204. package/lib/drivers.js +0 -84
  205. package/lib/extension-config.js +0 -209
  206. package/lib/plugin-config.js +0 -34
  207. package/lib/plugins.js +0 -11
  208. package/postinstall.js +0 -71
@@ -0,0 +1,229 @@
1
+ import _ from 'lodash';
2
+
3
+ /**
4
+ * The original ID of the Appium config schema.
5
+ * We use this in the CLI to convert it to `argparse` options.
6
+ */
7
+ export const APPIUM_CONFIG_SCHEMA_ID = 'appium.json';
8
+
9
+ /**
10
+ * The schema prop containing server-related options. Everything in here
11
+ * is "native" to Appium.
12
+ * Used by {@link flattenSchema} for transforming the schema into CLI args.
13
+ */
14
+ export const SERVER_PROP_NAME = 'server';
15
+
16
+ /**
17
+ * Used to parse extension info from a schema ID.
18
+ */
19
+ const SCHEMA_ID_REGEXP = /^(?<extType>.+?)-(?<normalizedExtName>.+)\.json$/;
20
+
21
+ /**
22
+ * Avoid typos by using constants!
23
+ */
24
+ const PROPERTIES = 'properties';
25
+
26
+ /**
27
+ * An `ArgSpec` is a class representing metadata about an argument (or config
28
+ * option) used for cross-referencing.
29
+ *
30
+ * This class has no instance methods, and is basically just a read-only "struct".
31
+ * @template D
32
+ */
33
+ export class ArgSpec {
34
+ /**
35
+ * The canonical name of the argument. Corresponds to key in schema's `properties` prop.
36
+ * @type {string}
37
+ */
38
+ name;
39
+
40
+ /**
41
+ * The `ExtensionType` of the argument. This will be set if the arg came from an extension;
42
+ * otherwise it will be `undefined`.
43
+ * @type {ExtensionType|undefined}
44
+ */
45
+ extType;
46
+
47
+ /**
48
+ * The name of the extension, if this argument came from an extension.
49
+ *
50
+ * Otherwise `undefined`.
51
+ * @type {string|undefined}
52
+ */
53
+ extName;
54
+
55
+ /**
56
+ * The schema ID (`$id`) for the argument. This is automatically determined, and any user-provided `$id`s will be overwritten.
57
+ *
58
+ * @type {string}
59
+ */
60
+ ref;
61
+
62
+ /**
63
+ * The CLI argument, sans leading dashes.
64
+ * @type {string}
65
+ */
66
+ arg;
67
+
68
+ /**
69
+ * The desired keypath for the argument after arguments have been parsed.
70
+ *
71
+ * Typically this is camelCased. If the arg came from an extension, it will be prefixed with
72
+ * `<extType>.<extName>.`
73
+ * @type {string}
74
+ */
75
+ dest;
76
+
77
+ /**
78
+ * The same as {@link ArgSpec.dest} but without the leading `<extType>.<extName>.` prefix.
79
+ */
80
+ rawDest;
81
+
82
+ /**
83
+ * Whatever the default value of this argument is, as specified by the
84
+ * `default` property of the schema.
85
+ * @type {D|undefined}
86
+ */
87
+ defaultValue;
88
+
89
+ /**
90
+ * Builds some computed fields and assigns them to the instance.
91
+ *
92
+ * Undefined properties are not assigned.
93
+ *
94
+ * The _constructor_ is private. Use {@link ArgSpec.create} instead.
95
+ * @private
96
+ * @param {string} name
97
+ * @param {ArgSpecOptions<D>} opts
98
+ */
99
+ constructor(name, {extType, extName, dest, defaultValue} = {}) {
100
+ // we must normalize the extension name to fit into our convention for CLI
101
+ // args.
102
+ const arg = ArgSpec.toArg(name, extType, extName);
103
+
104
+ const ref = ArgSpec.toSchemaRef(name, extType, extName);
105
+
106
+ // if no explicit `dest` provided, just camelCase the name to avoid needing
107
+ // to use bracket syntax when accessing props on the parsed args object.
108
+ const rawDest = _.camelCase(dest ?? name);
109
+
110
+ const destKeypath = extType && extName ? [extType, extName, rawDest].join('.') : rawDest;
111
+
112
+ this.defaultValue = defaultValue;
113
+ this.name = name;
114
+ this.extType = extType;
115
+ this.extName = extName;
116
+ this.arg = arg;
117
+ this.dest = destKeypath;
118
+ this.ref = ref;
119
+ this.rawDest = rawDest;
120
+ }
121
+
122
+ /**
123
+ * Return the schema ID (`$id`) for the **argument** given the parameters.
124
+ *
125
+ * If you need the "root" or "base" schema ID, use {@link ArgSpec.toSchemaBaseRef} instead.
126
+ * @param {string} name - Argument name
127
+ * @param {ExtensionType} [extType] - Extension type
128
+ * @param {string} [extName] - Extension name
129
+ * @returns {string} Schema ID
130
+ */
131
+ static toSchemaRef(name, extType, extName) {
132
+ const baseRef = ArgSpec.toSchemaBaseRef(extType, extName);
133
+ if (extType && extName) {
134
+ return [`${baseRef}#`, PROPERTIES, name].join('/');
135
+ }
136
+ return [`${baseRef}#`, PROPERTIES, SERVER_PROP_NAME, PROPERTIES, name].join('/');
137
+ }
138
+
139
+ /**
140
+ * Return the schema ID for an extension or the base schema ID.
141
+ * @param {ExtensionType} [extType] - Extension type
142
+ * @param {string} [extName] - Extension name
143
+ */
144
+ static toSchemaBaseRef(extType, extName) {
145
+ if (extType && extName) {
146
+ return `${extType}-${ArgSpec.toNormalizedExtName(extName)}.json`;
147
+ }
148
+ return APPIUM_CONFIG_SCHEMA_ID;
149
+ }
150
+
151
+ /**
152
+ * Return the unique ID for the argument given the parameters.
153
+ * @param {string} name - Argument name
154
+ * @param {ExtensionType} [extType] - Extension type
155
+ * @param {string} [extName] - Extension name
156
+ * @returns {string} Unique ID
157
+ */
158
+ static toArg(name, extType, extName) {
159
+ const properName = _.kebabCase(name.replace(/^--?/, ''));
160
+ if (extType && extName) {
161
+ return [extType, _.kebabCase(extName), properName].join('-');
162
+ }
163
+ return properName;
164
+ }
165
+
166
+ /**
167
+ * Normalizes a raw extension name (not including the type).
168
+ * @param {string} extName - Extension name
169
+ * @returns {string} Normalized extension name
170
+ */
171
+ static toNormalizedExtName(extName) {
172
+ return _.kebabCase(extName);
173
+ }
174
+
175
+ /**
176
+ * When given the root ID of a schema for an extension (`<extType>-<normalizedExtName>.json`) Returns an object containing the extension type and the _normalized_ extension name.
177
+ * @param {string} schemaId - Root schema ID
178
+ * @returns { {extType?: ExtensionType, normalizedExtName?: string} }
179
+ */
180
+ static extensionInfoFromRootSchemaId(schemaId) {
181
+ const matches = schemaId.match(SCHEMA_ID_REGEXP);
182
+ if (matches?.groups) {
183
+ const {extType, normalizedExtName} =
184
+ /** @type { {extType: ExtensionType, normalizedExtName: string} } */ (matches.groups);
185
+ return {extType, normalizedExtName};
186
+ }
187
+ return {};
188
+ }
189
+
190
+ /**
191
+ * Creates an `ArgSpec`
192
+ *
193
+ * @param {string} name - The canonical name of the argument. Corresponds to a key in a schema's
194
+ * `properties` property.
195
+ * @template D
196
+ * @param {ArgSpecOptions<D>} [opts] - Options
197
+ * @returns {Readonly<ArgSpec>}
198
+ */
199
+ static create(name, opts) {
200
+ return Object.freeze(new ArgSpec(name, opts));
201
+ }
202
+
203
+ /**
204
+ * String representation, useful for debugging
205
+ * @returns {string}
206
+ */
207
+ /* istanbul ignore next */
208
+ toString() {
209
+ let str = `[ArgSpec] ${this.name} (${this.ref})`;
210
+ if (this.extType && this.extName) {
211
+ str += ` (ext: ${this.extType}/${this.extName})`;
212
+ }
213
+ return str;
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Options for {@link ArgSpec.create}
219
+ * @template D
220
+ * @typedef ArgSpecOptions
221
+ * @property {string} [extName]
222
+ * @property {ExtensionType} [extType]
223
+ * @property {string} [dest]
224
+ * @property {D} [defaultValue]
225
+ */
226
+
227
+ /**
228
+ * @typedef {import('@appium/types').ExtensionType} ExtensionType
229
+ */
@@ -0,0 +1,241 @@
1
+ import {ArgumentTypeError} from 'argparse';
2
+ import _ from 'lodash';
3
+ import {formatErrors as formatErrors} from '../config-file';
4
+ import {flattenSchema, validate} from './schema';
5
+ import {transformers} from './cli-transformers';
6
+
7
+ /**
8
+ * This module concerns functions which convert schema definitions to
9
+ * `argparse`-compatible data structures, for deriving CLI arguments from a
10
+ * schema.
11
+ */
12
+
13
+ /**
14
+ * Lookup of possible values for the `type` field in a JSON schema.
15
+ * @type {Readonly<Record<string, import('json-schema').JSONSchema7TypeName>>}
16
+ */
17
+ const TYPENAMES = Object.freeze({
18
+ ARRAY: 'array',
19
+ OBJECT: 'object',
20
+ BOOLEAN: 'boolean',
21
+ INTEGER: 'integer',
22
+ NUMBER: 'number',
23
+ NULL: 'null',
24
+ STRING: 'string',
25
+ });
26
+
27
+ /**
28
+ * Options with alias lengths less than this will be considered "short" flags.
29
+ */
30
+ const SHORT_ARG_CUTOFF = 3;
31
+
32
+ /**
33
+ * Convert an alias (`foo`) to a flag (`--foo`) or a short flag (`-f`).
34
+ * @param {ArgSpec} argSpec - the argument specification
35
+ * @param {string} [alias] - the alias to convert to a flag
36
+ * @returns {string} the flag
37
+ */
38
+ function aliasToFlag(argSpec, alias) {
39
+ const {extType, extName, name} = argSpec;
40
+ const arg = alias ?? name;
41
+ const isShort = arg.length < SHORT_ARG_CUTOFF;
42
+ if (extType && extName) {
43
+ return isShort
44
+ ? `--${extType}-${_.kebabCase(extName)}-${arg}`
45
+ : `--${extType}-${_.kebabCase(extName)}-${_.kebabCase(arg)}`;
46
+ }
47
+ return isShort ? `-${arg}` : `--${_.kebabCase(arg)}`;
48
+ }
49
+
50
+ /**
51
+ * Converts a string to SCREAMING_SNAKE_CASE
52
+ */
53
+ const screamingSnakeCase = _.flow(_.snakeCase, _.toUpper);
54
+
55
+ /**
56
+ * Given unique property name `name`, return a function which validates a value
57
+ * against a property within the schema.
58
+ * @template Coerced
59
+ * @param {ArgSpec} argSpec - Argument name
60
+ * @param {(value: string) => Coerced} [coerce] - Function to coerce to a different
61
+ * primitive
62
+ * @todo See if we can remove `coerce` by allowing Ajv to coerce in its
63
+ * constructor options
64
+ * @returns
65
+ */
66
+ function getSchemaValidator({ref: schemaId}, coerce = _.identity) {
67
+ /** @param {string} value */
68
+ return (value) => {
69
+ const coerced = coerce(value);
70
+ const errors = validate(coerced, schemaId);
71
+ if (_.isEmpty(errors)) {
72
+ return coerced;
73
+ }
74
+ throw new ArgumentTypeError('\n\n' + formatErrors(errors, value, {schemaId}));
75
+ };
76
+ }
77
+
78
+ /**
79
+ * Determine the description for display on the CLI, given the schema.
80
+ * @param {AppiumJSONSchema} schema
81
+ * @returns {string}
82
+ */
83
+ function makeDescription(schema) {
84
+ const {appiumCliDescription, description = '', appiumDeprecated} = schema;
85
+ let desc = appiumCliDescription ?? description;
86
+ if (appiumDeprecated) {
87
+ desc = `[DEPRECATED] ${desc}`;
88
+ }
89
+ return desc;
90
+ }
91
+
92
+ /**
93
+ * Given arg `name`, a JSON schema `subSchema`, and options, return an argument definition
94
+ * as understood by `argparse`.
95
+ * @param {AppiumJSONSchema} subSchema - JSON schema for the option
96
+ * @param {ArgSpec} argSpec - Argument spec tuple
97
+ * @returns {[[string]|[string, string], import('argparse').ArgumentOptions]} Tuple of flag and options
98
+ */
99
+ function subSchemaToArgDef(subSchema, argSpec) {
100
+ let {type, appiumCliAliases, appiumCliTransformer, enum: enumValues} = subSchema;
101
+
102
+ const {name, arg} = argSpec;
103
+
104
+ const aliases = [
105
+ aliasToFlag(argSpec),
106
+ .../** @type {string[]} */ (appiumCliAliases ?? []).map((alias) => aliasToFlag(argSpec, alias)),
107
+ ];
108
+
109
+ /** @type {import('argparse').ArgumentOptions} */
110
+ let argOpts = {
111
+ required: false,
112
+ help: makeDescription(subSchema),
113
+ };
114
+
115
+ /**
116
+ * Generally we will provide a `type` to `argparse` as a function which
117
+ * validates using ajv (which is much more full-featured than what `argparse`
118
+ * can offer). The exception is `boolean`-type options, which have no
119
+ * `argType`.
120
+ *
121
+ * Not sure if this type is correct, but it's not doing what I want. I want
122
+ * to say "this is a function which returns something of type `T` where `T` is
123
+ * never a `Promise`". This function must be sync.
124
+ * @type {((value: string) => unknown)|undefined}
125
+ */
126
+ let argTypeFunction;
127
+
128
+ // handle special cases for various types
129
+ switch (type) {
130
+ // booleans do not have a type per `ArgumentOptions`, just an "action"
131
+ // NOTE: due to limitations of `argparse`, we cannot provide fancy help text, and must rely on its internal error messaging.
132
+ case TYPENAMES.BOOLEAN: {
133
+ argOpts.action = 'store_const';
134
+ argOpts.const = true;
135
+ break;
136
+ }
137
+
138
+ case TYPENAMES.OBJECT: {
139
+ argTypeFunction = transformers.json;
140
+ break;
141
+ }
142
+
143
+ // arrays are treated as CSVs, because `argparse` doesn't handle array data.
144
+ case TYPENAMES.ARRAY: {
145
+ argTypeFunction = transformers.csv;
146
+ break;
147
+ }
148
+
149
+ // "number" type is coerced to float. `argparse` does this for us if we use `float` type, but
150
+ // we don't.
151
+ case TYPENAMES.NUMBER: {
152
+ argTypeFunction = getSchemaValidator(argSpec, parseFloat);
153
+ break;
154
+ }
155
+
156
+ // "integer" is coerced to an .. integer. again, `argparse` would do this for us if we used `int`.
157
+ case TYPENAMES.INTEGER: {
158
+ argTypeFunction = getSchemaValidator(argSpec, _.parseInt);
159
+ break;
160
+ }
161
+
162
+ // strings (like number and integer) are subject to further validation
163
+ // (e.g., must satisfy a mask or regex or even some custom validation
164
+ // function)
165
+ case TYPENAMES.STRING: {
166
+ argTypeFunction = getSchemaValidator(argSpec);
167
+ break;
168
+ }
169
+
170
+ // TODO: there may be some way to restrict this at the Ajv level --
171
+ // that may involve patching the metaschema.
172
+ case TYPENAMES.NULL:
173
+ // falls through
174
+ default: {
175
+ throw new TypeError(`Schema property "${arg}": \`${type}\` type unknown or disallowed`);
176
+ }
177
+ }
178
+
179
+ // metavar is used in help text. `boolean` cannot have a metavar--it is not
180
+ // displayed--and `argparse` throws if you give it one.
181
+ if (type !== TYPENAMES.BOOLEAN) {
182
+ argOpts.metavar = screamingSnakeCase(name);
183
+ }
184
+
185
+ // the validity of "appiumCliTransformer" should already have been determined
186
+ // by ajv during schema validation in `finalizeSchema()`. the `array` &
187
+ // `object` types have already added a formatter (see above, so we don't do it
188
+ // twice).
189
+ if (type !== TYPENAMES.ARRAY && type !== TYPENAMES.OBJECT && appiumCliTransformer) {
190
+ argTypeFunction = _.flow(argTypeFunction ?? _.identity, transformers[appiumCliTransformer]);
191
+ }
192
+
193
+ if (argTypeFunction) {
194
+ argOpts.type = argTypeFunction;
195
+ }
196
+
197
+ // convert JSON schema `enum` to `choices`. `enum` can contain any JSON type, but `argparse`
198
+ // is limited to a single type per arg (I think). so let's make everything a string.
199
+ // and might as well _require_ the `type: string` while we're at it.
200
+ if (enumValues && !_.isEmpty(enumValues)) {
201
+ if (type === TYPENAMES.STRING) {
202
+ argOpts.choices = enumValues.map(String);
203
+ } else {
204
+ throw new TypeError(
205
+ `Problem with schema for ${arg}; \`enum\` is only supported for \`type: 'string'\``
206
+ );
207
+ }
208
+ }
209
+
210
+ // TODO: argparse only accepts the command name and a single alias; any extra aliases
211
+ // will be silently discarded.
212
+ const finalAliases = /** @type {[string]|[string, string]} */ (aliases);
213
+ return [finalAliases, argOpts];
214
+ }
215
+
216
+ /**
217
+ * Converts the finalized, flattened schema representation into
218
+ * ArgumentDefinitions for handoff to `argparse`.
219
+ *
220
+ * @throws If schema has not been added to ajv (via `finalizeSchema()`)
221
+ * @returns {import('../cli/args').ArgumentDefinitions} A map of arryas of
222
+ * aliases to `argparse` arguments; empty if no schema found
223
+ */
224
+ export function toParserArgs() {
225
+ const flattened = flattenSchema().filter(({schema}) => !schema.appiumCliIgnored);
226
+ return new Map(_.map(flattened, ({schema, argSpec}) => subSchemaToArgDef(schema, argSpec)));
227
+ }
228
+
229
+ /**
230
+ * @template {string|number} T
231
+ * @typedef {import('ajv/dist/types').FormatValidator<T>} FormatValidator<T>
232
+ */
233
+
234
+ /**
235
+ * A JSON 7 schema with our custom keywords.
236
+ * @typedef {import('./keywords').AppiumJSONSchemaKeywords & import('json-schema').JSONSchema7} AppiumJSONSchema
237
+ */
238
+
239
+ /**
240
+ * @typedef {import('./arg-spec').ArgSpec} ArgSpec
241
+ */
@@ -0,0 +1,119 @@
1
+ import {ArgumentTypeError} from 'argparse';
2
+ import {readFileSync, existsSync} from 'fs';
3
+ import _ from 'lodash';
4
+
5
+ /**
6
+ * This module provides custom keywords for Appium schemas, as well as
7
+ * "transformers" (see `argTransformers` below).
8
+ *
9
+ * Custom keywords are just properties that will appear in a schema (e.g.,
10
+ * `appium-config-schema.js`) beyond what the JSON Schema spec offers. These
11
+ * are usable by extensions, as well.
12
+ */
13
+
14
+ /**
15
+ * Splits a CSV string into an array
16
+ * @param {string} value
17
+ * @returns {string[]}
18
+ */
19
+ function parseCsvLine(value) {
20
+ return value
21
+ .split(',')
22
+ .map((v) => v.trim())
23
+ .filter(Boolean);
24
+ }
25
+
26
+ /**
27
+ * Split a file by newline then calls {@link parseCsvLine} on each line.
28
+ * @param {string} value
29
+ * @returns {string[]}
30
+ */
31
+ function parseCsvFile(value) {
32
+ return value
33
+ .split(/\r?\n/)
34
+ .map((v) => v.trim())
35
+ .filter(Boolean)
36
+ .flatMap(parseCsvLine);
37
+ }
38
+
39
+ /**
40
+ * Namespace containing _transformers_ for CLI arguments. "Validators" and
41
+ * "formatters" do not actually modify the value, but these do.
42
+ *
43
+ * Use case is for when the config file can accept e.g., a `string[]`, but the
44
+ * CLI can only take a `string` (as `argparse` seems to be limited in that
45
+ * fashion; it also cannot understand an argument having multiple types).
46
+ *
47
+ * For example, the `csv` transform takes a `string` and returns a `string[]` by
48
+ * splitting it by comma--_or_ if that `string` happens to be a
49
+ * filepath--reading the file as a `.csv`.
50
+ *
51
+ * This contains some copy-pasted code from `lib/cli/parser-helpers.js`, which was
52
+ * obliterated.
53
+ */
54
+ export const transformers = {
55
+ /**
56
+ * Given a CSV-style string or pathname, parse it into an array.
57
+ * The file can also be split on newlines.
58
+ * @param {string} csvOrPath
59
+ * @returns {string[]}
60
+ */
61
+ csv: (csvOrPath) => {
62
+ let csv = csvOrPath;
63
+ let loadedFromFile = false;
64
+ // since this value could be a single string (no commas) _or_ a pathname, we will need
65
+ // to attempt to parse it as a file _first_.
66
+ if (existsSync(csvOrPath)) {
67
+ try {
68
+ csv = readFileSync(csvOrPath, 'utf8');
69
+ } catch (err) {
70
+ throw new ArgumentTypeError(`Could not read file '${csvOrPath}': ${err.message}`);
71
+ }
72
+ loadedFromFile = true;
73
+ }
74
+
75
+ try {
76
+ return loadedFromFile ? parseCsvFile(csv) : parseCsvLine(csv);
77
+ } catch (err) {
78
+ const msg = loadedFromFile
79
+ ? `The provided value of '${csvOrPath}' must be a valid CSV`
80
+ : `Must be a comma-delimited string, e.g., "foo,bar,baz"`;
81
+ throw new TypeError(`${msg}. Original error: ${err.message}`);
82
+ }
83
+ },
84
+
85
+ /**
86
+ * Parse a string which could be a path to a JSON file or a JSON string.
87
+ * @param {string} jsonOrPath
88
+ * @returns {object}
89
+ */
90
+ json: (jsonOrPath) => {
91
+ let json = jsonOrPath;
92
+ let loadedFromFile = false;
93
+ if (existsSync(jsonOrPath)) {
94
+ try {
95
+ // use synchronous file access, as `argparse` provides no way of either
96
+ // awaiting or using callbacks. This step happens in startup, in what is
97
+ // effectively command-line code, so nothing is blocked in terms of
98
+ // sessions, so holding up the event loop does not incur the usual
99
+ // drawbacks.
100
+ json = readFileSync(jsonOrPath, 'utf8');
101
+ } catch (err) {
102
+ throw new ArgumentTypeError(`Could not read file '${jsonOrPath}': ${err.message}`);
103
+ }
104
+ loadedFromFile = true;
105
+ }
106
+ try {
107
+ const result = JSON.parse(json);
108
+ if (!_.isPlainObject(result)) {
109
+ throw new Error(`'${_.truncate(result, {length: 100})}' is not an object`);
110
+ }
111
+ return result;
112
+ } catch (e) {
113
+ const msg = loadedFromFile
114
+ ? `The provided value of '${jsonOrPath}' must be a valid JSON`
115
+ : `The provided value must be a valid JSON`;
116
+ throw new TypeError(`${msg}. Original error: ${e.message}`);
117
+ }
118
+ },
119
+ };
@@ -0,0 +1,2 @@
1
+ export * from './schema';
2
+ export * from './cli-args';