appium 3.2.1 → 3.3.0

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 (122) hide show
  1. package/build/lib/cli/args.d.ts +16 -12
  2. package/build/lib/cli/args.d.ts.map +1 -1
  3. package/build/lib/cli/args.js +15 -35
  4. package/build/lib/cli/args.js.map +1 -1
  5. package/build/lib/cli/driver-command.d.ts +51 -93
  6. package/build/lib/cli/driver-command.d.ts.map +1 -1
  7. package/build/lib/cli/driver-command.js +11 -66
  8. package/build/lib/cli/driver-command.js.map +1 -1
  9. package/build/lib/cli/extension-command.d.ts +211 -415
  10. package/build/lib/cli/extension-command.d.ts.map +1 -1
  11. package/build/lib/cli/extension-command.js +384 -653
  12. package/build/lib/cli/extension-command.js.map +1 -1
  13. package/build/lib/cli/extension.d.ts +11 -16
  14. package/build/lib/cli/extension.d.ts.map +1 -1
  15. package/build/lib/cli/extension.js +10 -28
  16. package/build/lib/cli/extension.js.map +1 -1
  17. package/build/lib/cli/parser.d.ts +40 -69
  18. package/build/lib/cli/parser.d.ts.map +1 -1
  19. package/build/lib/cli/parser.js +24 -59
  20. package/build/lib/cli/parser.js.map +1 -1
  21. package/build/lib/cli/plugin-command.d.ts +50 -90
  22. package/build/lib/cli/plugin-command.d.ts.map +1 -1
  23. package/build/lib/cli/plugin-command.js +11 -63
  24. package/build/lib/cli/plugin-command.js.map +1 -1
  25. package/build/lib/cli/setup-command.d.ts +21 -26
  26. package/build/lib/cli/setup-command.d.ts.map +1 -1
  27. package/build/lib/cli/setup-command.js +13 -55
  28. package/build/lib/cli/setup-command.js.map +1 -1
  29. package/build/lib/cli/utils.d.ts +27 -29
  30. package/build/lib/cli/utils.d.ts.map +1 -1
  31. package/build/lib/cli/utils.js +29 -31
  32. package/build/lib/cli/utils.js.map +1 -1
  33. package/build/lib/config-file.d.ts +24 -67
  34. package/build/lib/config-file.d.ts.map +1 -1
  35. package/build/lib/config-file.js +56 -115
  36. package/build/lib/config-file.js.map +1 -1
  37. package/build/lib/config.d.ts +42 -44
  38. package/build/lib/config.d.ts.map +1 -1
  39. package/build/lib/config.js +75 -107
  40. package/build/lib/config.js.map +1 -1
  41. package/build/lib/constants.d.ts +23 -23
  42. package/build/lib/constants.d.ts.map +1 -1
  43. package/build/lib/constants.js +10 -15
  44. package/build/lib/constants.js.map +1 -1
  45. package/build/lib/doctor/doctor.d.ts +40 -57
  46. package/build/lib/doctor/doctor.d.ts.map +1 -1
  47. package/build/lib/doctor/doctor.js +29 -60
  48. package/build/lib/doctor/doctor.js.map +1 -1
  49. package/build/lib/grid-register.d.ts +32 -7
  50. package/build/lib/grid-register.d.ts.map +1 -1
  51. package/build/lib/grid-register.js +84 -48
  52. package/build/lib/grid-register.js.map +1 -1
  53. package/build/lib/logsink.d.ts +13 -22
  54. package/build/lib/logsink.d.ts.map +1 -1
  55. package/build/lib/logsink.js +48 -103
  56. package/build/lib/logsink.js.map +1 -1
  57. package/build/lib/main.js +1 -1
  58. package/build/lib/main.js.map +1 -1
  59. package/build/lib/schema/arg-spec.d.ts +32 -107
  60. package/build/lib/schema/arg-spec.d.ts.map +1 -1
  61. package/build/lib/schema/arg-spec.js +11 -107
  62. package/build/lib/schema/arg-spec.js.map +1 -1
  63. package/build/lib/schema/cli-args.d.ts +3 -15
  64. package/build/lib/schema/cli-args.d.ts.map +1 -1
  65. package/build/lib/schema/cli-args.js +15 -105
  66. package/build/lib/schema/cli-args.js.map +1 -1
  67. package/build/lib/schema/cli-transformers.d.ts +15 -12
  68. package/build/lib/schema/cli-transformers.d.ts.map +1 -1
  69. package/build/lib/schema/cli-transformers.js +15 -45
  70. package/build/lib/schema/cli-transformers.js.map +1 -1
  71. package/build/lib/schema/index.d.ts +2 -2
  72. package/build/lib/schema/index.d.ts.map +1 -1
  73. package/build/lib/schema/index.js.map +1 -1
  74. package/build/lib/schema/keywords.d.ts +12 -20
  75. package/build/lib/schema/keywords.d.ts.map +1 -1
  76. package/build/lib/schema/keywords.js +6 -51
  77. package/build/lib/schema/keywords.js.map +1 -1
  78. package/build/lib/schema/schema.d.ts +106 -231
  79. package/build/lib/schema/schema.d.ts.map +1 -1
  80. package/build/lib/schema/schema.js +75 -345
  81. package/build/lib/schema/schema.js.map +1 -1
  82. package/build/lib/utils.d.ts +59 -238
  83. package/build/lib/utils.d.ts.map +1 -1
  84. package/build/lib/utils.js +55 -207
  85. package/build/lib/utils.js.map +1 -1
  86. package/lib/cli/{args.js → args.ts} +40 -51
  87. package/lib/cli/driver-command.ts +122 -0
  88. package/lib/cli/{extension-command.js → extension-command.ts} +610 -689
  89. package/lib/cli/extension.ts +65 -0
  90. package/lib/cli/{parser.js → parser.ts} +48 -71
  91. package/lib/cli/plugin-command.ts +117 -0
  92. package/lib/cli/{setup-command.js → setup-command.ts} +57 -72
  93. package/lib/cli/utils.ts +97 -0
  94. package/lib/config-file.ts +212 -0
  95. package/lib/{config.js → config.ts} +129 -141
  96. package/lib/{constants.js → constants.ts} +30 -41
  97. package/lib/doctor/{doctor.js → doctor.ts} +81 -91
  98. package/lib/grid-register.ts +250 -0
  99. package/lib/{logsink.js → logsink.ts} +91 -137
  100. package/lib/main.js +1 -1
  101. package/lib/schema/arg-spec.ts +131 -0
  102. package/lib/schema/cli-args.ts +171 -0
  103. package/lib/schema/cli-transformers.ts +83 -0
  104. package/lib/schema/keywords.ts +96 -0
  105. package/lib/schema/schema.ts +449 -0
  106. package/lib/utils.ts +404 -0
  107. package/package.json +19 -20
  108. package/tsconfig.json +1 -1
  109. package/build/package.json +0 -99
  110. package/lib/cli/driver-command.js +0 -174
  111. package/lib/cli/extension.js +0 -74
  112. package/lib/cli/plugin-command.js +0 -164
  113. package/lib/cli/utils.js +0 -91
  114. package/lib/config-file.js +0 -228
  115. package/lib/grid-register.js +0 -146
  116. package/lib/schema/arg-spec.js +0 -229
  117. package/lib/schema/cli-args.js +0 -254
  118. package/lib/schema/cli-transformers.js +0 -113
  119. package/lib/schema/keywords.js +0 -136
  120. package/lib/schema/schema.js +0 -725
  121. package/lib/utils.js +0 -512
  122. /package/lib/schema/{index.js → index.ts} +0 -0
@@ -1,228 +0,0 @@
1
- import betterAjvErrors from '@sidvind/better-ajv-errors';
2
- import {lilconfig} from 'lilconfig';
3
- import _ from 'lodash';
4
- import * as yaml from 'yaml';
5
- import {getSchema, validate} from './schema/schema';
6
-
7
- /**
8
- * lilconfig loader to handle `.yaml` files
9
- * @type {import('lilconfig').LoaderSync}
10
- */
11
- function yamlLoader(filepath, content) {
12
- try {
13
- return yaml.parse(content);
14
- } catch (e) {
15
- throw new Error(`The YAML config at '${filepath}' cannot be loaded. Original error: ${e.message}`);
16
- }
17
- }
18
-
19
- /**
20
- * A cache of the raw config file (a JSON string) at a filepath.
21
- * This is used for better error reporting.
22
- * Note that config files needn't be JSON, but it helps if they are.
23
- * @type {Map<string,RawJson>}
24
- */
25
- const rawConfig = new Map();
26
-
27
- /**
28
- * Custom JSON loader that caches the raw config file (for use with `better-ajv-errors`).
29
- * If it weren't for this cache, this would be unnecessary.
30
- * @type {import('lilconfig').LoaderSync}
31
- */
32
- function jsonLoader(filepath, content) {
33
- rawConfig.set(filepath, content);
34
- try {
35
- return JSON.parse(content);
36
- } catch (e) {
37
- throw new Error(`The JSON config at '${filepath}' cannot be loaded. Original error: ${e.message}`);
38
- }
39
- }
40
-
41
- /**
42
- * Loads a config file from an explicit path
43
- * @param {LilconfigAsyncSearcher} lc - lilconfig instance
44
- * @param {string} filepath - Path to config file
45
- * @returns {Promise<import('lilconfig').LilconfigResult>}
46
- */
47
- async function loadConfigFile(lc, filepath) {
48
- try {
49
- // removing "await" will cause any rejection to _not_ be caught in this block!
50
- return await lc.load(filepath);
51
- } catch (/** @type {unknown} */ err) {
52
- if (/** @type {NodeJS.ErrnoException} */ (err).code === 'ENOENT') {
53
- /** @type {NodeJS.ErrnoException} */ (
54
- err
55
- ).message = `Config file not found at user-provided path: ${filepath}`;
56
- throw err;
57
- } else if (err instanceof SyntaxError) {
58
- // generally invalid JSON
59
- err.message = `Config file at user-provided path ${filepath} is invalid:\n${err.message}`;
60
- throw err;
61
- }
62
- throw err;
63
- }
64
- }
65
-
66
- /**
67
- * Searches for a config file
68
- * @param {LilconfigAsyncSearcher} lc - lilconfig instance
69
- * @returns {Promise<import('lilconfig').LilconfigResult>}
70
- */
71
- async function searchConfigFile(lc) {
72
- return await lc.search();
73
- }
74
-
75
- /**
76
- * Given an array of errors and the result of loading a config file, generate a
77
- * helpful string for the user.
78
- *
79
- * - If `opts` contains a `json` property, this should be the original JSON
80
- * _string_ of the config file. This is only applicable if the config file
81
- * was in JSON format. If present, it will associate line numbers with errors.
82
- * - If `errors` happens to be empty, this will throw.
83
- * @param {import('ajv').ErrorObject[]} errors - Non-empty array of errors. Required.
84
- * @param {ReadConfigFileResult['config']|any} [config] -
85
- * Configuration & metadata
86
- * @param {FormatConfigErrorsOptions} [opts]
87
- * @throws {TypeError} If `errors` is empty
88
- * @returns {string}
89
- */
90
- export function formatErrors(errors = [], config = {}, opts = {}) {
91
- if (errors && !errors.length) {
92
- throw new TypeError('Array of errors must be non-empty');
93
- }
94
- return betterAjvErrors(getSchema(opts.schemaId), config, errors, {
95
- json: opts.json,
96
- format: 'cli',
97
- });
98
- }
99
-
100
- /**
101
- * Given an optional path, read a config file. Validates the config file.
102
- *
103
- * Call {@link validate} if you already have a config object.
104
- * @param {string} [filepath] - Path to config file, if we have one
105
- * @param {ReadConfigFileOptions} [opts] - Options
106
- * @public
107
- * @returns {Promise<ReadConfigFileResult>} Contains config and filepath, if found, and any errors
108
- */
109
- export async function readConfigFile(filepath, opts = {}) {
110
- const lc = lilconfig('appium', {
111
- loaders: {
112
- '.yaml': yamlLoader,
113
- '.yml': yamlLoader,
114
- '.json': jsonLoader,
115
- noExt: jsonLoader,
116
- },
117
- packageProp: 'appiumConfig',
118
- });
119
-
120
- const result = filepath ? await loadConfigFile(lc, filepath) : await searchConfigFile(lc);
121
-
122
- if (result?.filepath && !result?.isEmpty) {
123
- const {pretty = true} = opts;
124
- try {
125
- let configResult;
126
- const errors = validate(result.config);
127
- if (_.isEmpty(errors)) {
128
- configResult = {...result, errors};
129
- } else {
130
- const reason = formatErrors(errors, result.config, {
131
- json: rawConfig.get(result.filepath),
132
- pretty,
133
- });
134
- configResult = reason ? {...result, errors, reason} : {...result, errors};
135
- }
136
-
137
- // normalize (to camel case) all top-level property names of the config file
138
- configResult.config = normalizeConfig(/** @type {AppiumConfig} */ (configResult.config));
139
-
140
- return configResult;
141
- } finally {
142
- // clean up the raw config file cache, which is only kept to better report errors.
143
- rawConfig.delete(result.filepath);
144
- }
145
- }
146
- return result ?? {};
147
- }
148
-
149
- /**
150
- * Convert schema property names to either a) the value of the `appiumCliDest` property, if any; or b) camel-case
151
- * @param {AppiumConfig} config - Configuration object
152
- * @returns {NormalizedAppiumConfig} New object with camel-cased keys (or `dest` keys).
153
- */
154
- export function normalizeConfig(config) {
155
- const schema = getSchema();
156
- /**
157
- * @param {AppiumConfig} config
158
- * @param {string} [section] - Keypath (lodash `_.get()` style) to section of config. If omitted, assume root Appium config schema
159
- * @todo Rewrite as a loop
160
- * @returns Normalized section of config
161
- */
162
- const normalize = (config, section) => {
163
- const obj = _.isUndefined(section) ? config : _.get(config, section, config);
164
-
165
- const mappedObj = _.mapKeys(obj, (__, prop) =>
166
- _.get(schema, `properties.server.properties[${prop}].appiumCliDest`, _.camelCase(prop))
167
- );
168
-
169
- return _.mapValues(mappedObj, (value, property) => {
170
- const nextSection = section ? `${section}.${property}` : property;
171
- return isSchemaTypeObject(schema.properties?.[property])
172
- ? normalize(config, nextSection)
173
- : value;
174
- });
175
- };
176
-
177
- /**
178
- * Returns `true` if the schema prop references an object, or if it's an object itself
179
- * @param {import('ajv').SchemaObject|object} schema - Referencing schema object
180
- */
181
- const isSchemaTypeObject = (schema) => Boolean(schema?.properties || schema?.type === 'object');
182
-
183
- return normalize(config);
184
- }
185
-
186
- /**
187
- * Result of calling {@link readConfigFile}.
188
- * @typedef ReadConfigFileResult
189
- * @property {import('ajv').ErrorObject[]} [errors] - Validation errors
190
- * @property {string} [filepath] - The path to the config file, if found
191
- * @property {boolean} [isEmpty] - If `true`, the config file exists but is empty
192
- * @property {NormalizedAppiumConfig} [config] - The parsed configuration
193
- * @property {string|import('@sidvind/better-ajv-errors').IOutputError[]} [reason] - Human-readable error messages and suggestions. If the `pretty` option is `true`, this will be a nice string to print.
194
- */
195
-
196
- /**
197
- * Options for {@link readConfigFile}.
198
- * @typedef ReadConfigFileOptions
199
- * @property {boolean} [pretty=true] If `false`, do not use color and fancy formatting in the `reason` property of the {@link ReadConfigFileResult}. The value of `reason` is then suitable for machine-reading.
200
- */
201
-
202
- /**
203
- * This is an `AsyncSearcher` which is inexplicably _not_ exported by the `lilconfig` type definition.
204
- * @typedef {ReturnType<import('lilconfig')["lilconfig"]>} LilconfigAsyncSearcher
205
- */
206
-
207
- /**
208
- * The contents of an Appium config file. Generated from schema
209
- * @typedef {import('@appium/types').AppiumConfig} AppiumConfig
210
- */
211
-
212
- /**
213
- * The contents of an Appium config file with camelcased property names (and using `appiumCliDest` value if present). Generated from {@link AppiumConfig}
214
- * @typedef {import('@appium/types').NormalizedAppiumConfig} NormalizedAppiumConfig
215
- */
216
-
217
- /**
218
- * The string should be a raw JSON string.
219
- * @typedef {string} RawJson
220
- */
221
-
222
- /**
223
- * Options for {@link formatErrors}.
224
- * @typedef FormatConfigErrorsOptions
225
- * @property {import('./config-file').RawJson} [json] - Raw JSON config (as string)
226
- * @property {boolean} [pretty=true] - Whether to format errors as a CLI-friendly string
227
- * @property {string} [schemaId] - Specific ID of a prop; otherwise entire schema
228
- */
@@ -1,146 +0,0 @@
1
- import axios from 'axios';
2
- import {fs} from '@appium/support';
3
- import logger from './logger';
4
- import _ from 'lodash';
5
-
6
- const hubUri = (config) => {
7
- const protocol = config.hubProtocol || 'http';
8
- return `${protocol}://${config.hubHost}:${config.hubPort}`;
9
- };
10
-
11
- /**
12
- * Registers a new node with a selenium grid
13
- * @param {string|object} data - Path or object representing selenium grid node config file. If a `string`, all subsequent arguments are required!
14
- * @param {string} [addr] - Bind to this address
15
- * @param {number} [port] - Bind to this port
16
- * @param {string} [basePath] - Base path for the grid
17
- */
18
- async function registerNode(data, addr, port, basePath) {
19
- let configFilePath;
20
- if (_.isString(data)) {
21
- configFilePath = data;
22
- try {
23
- data = await fs.readFile(data, 'utf-8');
24
- } catch (err) {
25
- logger.error(
26
- `Unable to load node configuration file ${configFilePath} to register with grid: ${err.message}`
27
- );
28
- return;
29
- }
30
- try {
31
- data = JSON.parse(data);
32
- } catch (err) {
33
- throw logger.errorWithException(
34
- `Syntax error in node configuration file ${configFilePath}: ${err.message}`
35
- );
36
- }
37
- }
38
-
39
- postRequest(data, addr, port, basePath);
40
- }
41
-
42
- async function registerToGrid(postOptions, configHolder) {
43
- try {
44
- const {status} = await axios(postOptions);
45
- if (status !== 200) {
46
- throw new Error(`Request failed with code ${status}`);
47
- }
48
- logger.debug(
49
- `Appium successfully registered with the the grid on ` + hubUri(configHolder.configuration)
50
- );
51
- } catch (err) {
52
- logger.error(`An attempt to register with the grid was unsuccessful: ${err.message}`);
53
- }
54
- }
55
-
56
- function postRequest(configHolder, addr, port, basePath) {
57
- // Move Selenium 3 configuration properties to configuration object
58
- if (!_.has(configHolder, 'configuration')) {
59
- let configuration = {};
60
- for (const property in /** @type {import('@appium/types').StringRecord} */ (configHolder)) {
61
- if (_.has(configHolder, property) && property !== 'capabilities') {
62
- configuration[property] = configHolder[property];
63
- delete configHolder[property];
64
- }
65
- }
66
- /** @type {import('@appium/types').StringRecord} */ (configHolder).configuration = configuration;
67
- }
68
-
69
- // if the node config does not have the appium/webdriver url, host, and port,
70
- // automatically add it based on how appium was initialized
71
- // otherwise, we will take whatever the user setup
72
- // because we will always set localhost/127.0.0.1. this won't work if your
73
- // node and grid aren't in the same place
74
- if (
75
- !configHolder.configuration.url ||
76
- !configHolder.configuration.host ||
77
- !configHolder.configuration.port
78
- ) {
79
- configHolder.configuration.url = `http://${addr}:${port}${basePath}`;
80
- configHolder.configuration.host = addr;
81
- configHolder.configuration.port = port;
82
- }
83
- // if the node config does not have id automatically add it
84
- if (!configHolder.configuration.id) {
85
- configHolder.configuration.id = `http://${configHolder.configuration.host}:${configHolder.configuration.port}`;
86
- }
87
-
88
- // the post options
89
- const regRequest = {
90
- url: `${hubUri(configHolder.configuration)}/grid/register`,
91
- method: 'POST',
92
- data: configHolder,
93
- };
94
-
95
- if (configHolder.configuration.register !== true) {
96
- logger.debug(`No registration sent (${configHolder.configuration.register} = false)`);
97
- return;
98
- }
99
-
100
- const registerCycleInterval = configHolder.configuration.registerCycle;
101
- if (isNaN(registerCycleInterval) || registerCycleInterval <= 0) {
102
- logger.warn(
103
- `'registerCycle' is not a valid positive number. ` +
104
- `No registration request will be sent to the grid.`
105
- );
106
- return;
107
- }
108
- // initiate a new Thread
109
- let first = true;
110
- logger.debug(
111
- `Starting auto register thread for the grid. ` +
112
- `Will try to register every ${registerCycleInterval} ms.`
113
- );
114
- setInterval(async function registerRetry() {
115
- if (first) {
116
- first = false;
117
- await registerToGrid(regRequest, configHolder);
118
- } else if (!(await isAlreadyRegistered(configHolder))) {
119
- // make the http POST to the grid for registration
120
- await registerToGrid(regRequest, configHolder);
121
- }
122
- }, registerCycleInterval);
123
- }
124
-
125
- async function isAlreadyRegistered(configHolder) {
126
- //check if node is already registered
127
- const id = configHolder.configuration.id;
128
- try {
129
- const {data, status} = await axios({
130
- url: `${hubUri(configHolder.configuration)}/grid/api/proxy?id=${id}`,
131
- timeout: 10000,
132
- });
133
- if (status !== 200) {
134
- throw new Error(`Request failed with code ${status}`);
135
- }
136
- if (!data.success) {
137
- // if register fail, print the debug msg
138
- logger.debug(`Grid registration error: ${data.msg}`);
139
- }
140
- return data.success;
141
- } catch (err) {
142
- logger.debug(`Hub down or not responding: ${err.message}`);
143
- }
144
- }
145
-
146
- export default registerNode;
@@ -1,229 +0,0 @@
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
- */