appium 2.0.0-beta.2 → 2.0.0-beta.23

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/README.md +9 -9
  2. package/build/check-npm-pack-files.js +23 -0
  3. package/build/commands-yml/parse.js +319 -0
  4. package/build/commands-yml/validator.js +130 -0
  5. package/build/index.js +19 -0
  6. package/build/lib/appium-config.schema.json +0 -0
  7. package/build/lib/appium.js +160 -53
  8. package/build/lib/cli/args.js +115 -279
  9. package/build/lib/cli/driver-command.js +11 -1
  10. package/build/lib/cli/extension-command.js +60 -8
  11. package/build/lib/cli/extension.js +30 -7
  12. package/build/lib/cli/npm.js +43 -29
  13. package/build/lib/cli/parser.js +156 -89
  14. package/build/lib/cli/plugin-command.js +11 -1
  15. package/build/lib/cli/utils.js +29 -3
  16. package/build/lib/config-file.js +141 -0
  17. package/build/lib/config.js +53 -65
  18. package/build/lib/driver-config.js +42 -19
  19. package/build/lib/drivers.js +8 -4
  20. package/build/lib/ext-config-io.js +165 -0
  21. package/build/lib/extension-config.js +130 -61
  22. package/build/lib/grid-register.js +22 -24
  23. package/build/lib/logger.js +3 -3
  24. package/build/lib/logsink.js +11 -13
  25. package/build/lib/main.js +197 -77
  26. package/build/lib/plugin-config.js +21 -11
  27. package/build/lib/plugins.js +4 -2
  28. package/build/lib/schema/appium-config-schema.js +253 -0
  29. package/build/lib/schema/arg-spec.js +120 -0
  30. package/build/lib/schema/cli-args.js +188 -0
  31. package/build/lib/schema/cli-transformers.js +76 -0
  32. package/build/lib/schema/index.js +36 -0
  33. package/build/lib/schema/keywords.js +72 -0
  34. package/build/lib/schema/schema.js +357 -0
  35. package/build/lib/utils.js +44 -99
  36. package/build/postinstall.js +90 -0
  37. package/build/test/cli/cli-e2e-specs.js +221 -0
  38. package/build/test/cli/cli-helpers.js +86 -0
  39. package/build/test/cli/cli-specs.js +71 -0
  40. package/build/test/cli/fixtures/test-driver/package.json +27 -0
  41. package/build/test/cli/schema-args-specs.js +48 -0
  42. package/build/test/cli/schema-e2e-specs.js +47 -0
  43. package/build/test/config-e2e-specs.js +112 -0
  44. package/build/test/config-file-e2e-specs.js +209 -0
  45. package/build/test/config-file-specs.js +281 -0
  46. package/build/test/config-specs.js +159 -0
  47. package/build/test/driver-e2e-specs.js +435 -0
  48. package/build/test/driver-specs.js +321 -0
  49. package/build/test/ext-config-io-specs.js +181 -0
  50. package/build/test/extension-config-specs.js +365 -0
  51. package/build/test/fixtures/allow-feat.txt +5 -0
  52. package/build/test/fixtures/caps.json +3 -0
  53. package/build/test/fixtures/config/allow-insecure.txt +3 -0
  54. package/build/test/fixtures/config/appium.config.bad-nodeconfig.json +5 -0
  55. package/build/test/fixtures/config/appium.config.bad.json +32 -0
  56. package/build/test/fixtures/config/appium.config.ext-good.json +9 -0
  57. package/build/test/fixtures/config/appium.config.ext-unknown-props.json +10 -0
  58. package/build/test/fixtures/config/appium.config.good.js +40 -0
  59. package/build/test/fixtures/config/appium.config.good.json +33 -0
  60. package/build/test/fixtures/config/appium.config.good.yaml +30 -0
  61. package/build/test/fixtures/config/appium.config.invalid.json +31 -0
  62. package/build/test/fixtures/config/appium.config.security-array.json +5 -0
  63. package/build/test/fixtures/config/appium.config.security-delimited.json +5 -0
  64. package/build/test/fixtures/config/appium.config.security-path.json +5 -0
  65. package/build/test/fixtures/config/driver-fake.config.json +8 -0
  66. package/build/test/fixtures/config/nodeconfig.json +3 -0
  67. package/build/test/fixtures/config/plugin-fake.config.json +0 -0
  68. package/build/test/fixtures/default-args.js +35 -0
  69. package/build/test/fixtures/deny-feat.txt +5 -0
  70. package/build/test/fixtures/driver.schema.js +20 -0
  71. package/build/test/fixtures/extensions.yaml +27 -0
  72. package/build/test/fixtures/flattened-schema.js +504 -0
  73. package/build/test/fixtures/plugin.schema.js +20 -0
  74. package/build/test/fixtures/schema-with-extensions.js +28 -0
  75. package/build/test/grid-register-specs.js +74 -0
  76. package/build/test/helpers.js +75 -0
  77. package/build/test/logger-specs.js +76 -0
  78. package/build/test/npm-specs.js +20 -0
  79. package/build/test/parser-specs.js +314 -0
  80. package/build/test/plugin-e2e-specs.js +316 -0
  81. package/build/test/schema/arg-spec-specs.js +70 -0
  82. package/build/test/schema/cli-args-specs.js +431 -0
  83. package/build/test/schema/schema-specs.js +389 -0
  84. package/build/test/utils-specs.js +266 -0
  85. package/index.js +11 -0
  86. package/lib/appium-config.schema.json +278 -0
  87. package/lib/appium.js +207 -65
  88. package/lib/cli/args.js +174 -375
  89. package/lib/cli/driver-command.js +4 -0
  90. package/lib/cli/extension-command.js +70 -5
  91. package/lib/cli/extension.js +25 -5
  92. package/lib/cli/npm.js +86 -18
  93. package/lib/cli/parser.js +257 -79
  94. package/lib/cli/plugin-command.js +4 -0
  95. package/lib/cli/utils.js +21 -1
  96. package/lib/config-file.js +227 -0
  97. package/lib/config.js +84 -63
  98. package/lib/driver-config.js +66 -11
  99. package/lib/drivers.js +4 -1
  100. package/lib/ext-config-io.js +287 -0
  101. package/lib/extension-config.js +225 -67
  102. package/lib/grid-register.js +27 -24
  103. package/lib/logger.js +1 -1
  104. package/lib/logsink.js +10 -7
  105. package/lib/main.js +214 -77
  106. package/lib/plugin-config.js +35 -6
  107. package/lib/plugins.js +1 -0
  108. package/lib/schema/appium-config-schema.js +287 -0
  109. package/lib/schema/arg-spec.js +222 -0
  110. package/lib/schema/cli-args.js +285 -0
  111. package/lib/schema/cli-transformers.js +123 -0
  112. package/lib/schema/index.js +2 -0
  113. package/lib/schema/keywords.js +135 -0
  114. package/lib/schema/schema.js +577 -0
  115. package/lib/utils.js +42 -88
  116. package/package.json +55 -84
  117. package/postinstall.js +71 -0
  118. package/types/appium-config.d.ts +197 -0
  119. package/types/types.d.ts +201 -0
  120. package/CHANGELOG.md +0 -3515
  121. package/build/lib/cli/parser-helpers.js +0 -82
  122. package/lib/cli/parser-helpers.js +0 -79
@@ -0,0 +1,287 @@
1
+ // @ts-check
2
+
3
+ const schema = /** @type {const} */ ({
4
+ $schema: 'http://json-schema.org/draft-07/schema',
5
+ additionalProperties: false,
6
+ description: 'A schema for Appium configuration files',
7
+ properties: {
8
+ server: {
9
+ additionalProperties: false,
10
+ description: 'Configuration when running Appium as a server',
11
+ properties: {
12
+ address: {
13
+ $comment:
14
+ 'I think hostname covers both DNS and IPv4...could be wrong',
15
+ appiumCliAliases: ['a'],
16
+ default: '0.0.0.0',
17
+ description: 'IP address to listen on',
18
+ format: 'hostname',
19
+ title: 'address config',
20
+ type: 'string',
21
+ },
22
+ 'allow-cors': {
23
+ description:
24
+ 'Whether the Appium server should allow web browser connections from any host',
25
+ title: 'allow-cors config',
26
+ type: 'boolean',
27
+ default: false,
28
+ },
29
+ 'allow-insecure': {
30
+ appiumCliTransformer: 'csv',
31
+ default: [],
32
+ description:
33
+ 'Set which insecure features are allowed to run in this server\'s sessions. Features are defined on a driver level; see documentation for more details. Note that features defined via "deny-insecure" will be disabled, even if also listed here. If string, a path to a text file containing policy or a comma-delimited list.',
34
+ items: {
35
+ type: 'string',
36
+ },
37
+ title: 'allow-insecure config',
38
+ type: 'array',
39
+ uniqueItems: true,
40
+ },
41
+ 'base-path': {
42
+ appiumCliAliases: ['pa'],
43
+ default: '',
44
+ description:
45
+ 'Base path to use as the prefix for all webdriver routes running on the server',
46
+ title: 'base-path config',
47
+ type: 'string',
48
+ },
49
+ 'callback-address': {
50
+ appiumCliAliases: ['ca'],
51
+ description: 'Callback IP address (default: same as "address")',
52
+ title: 'callback-address config',
53
+ type: 'string',
54
+ },
55
+ 'callback-port': {
56
+ appiumCliAliases: ['cp'],
57
+ default: 4723,
58
+ description: 'Callback port (default: same as "port")',
59
+ maximum: 65535,
60
+ minimum: 1,
61
+ title: 'callback-port config',
62
+ type: 'integer',
63
+ },
64
+ 'debug-log-spacing': {
65
+ default: false,
66
+ description:
67
+ 'Add exaggerated spacing in logs to help with visual inspection',
68
+ title: 'debug-log-spacing config',
69
+ type: 'boolean',
70
+ },
71
+ 'default-capabilities': {
72
+ $comment: 'TODO',
73
+ appiumCliAliases: ['dc'],
74
+ description:
75
+ 'Set the default desired capabilities, which will be set on each session unless overridden by received capabilities. If a string, a path to a JSON file containing the capabilities, or raw JSON.',
76
+ title: 'default-capabilities config',
77
+ type: 'object',
78
+ },
79
+ 'deny-insecure': {
80
+ $comment: 'Allowed values are defined by drivers',
81
+ appiumCliTransformer: 'csv',
82
+ default: [],
83
+ description:
84
+ 'Set which insecure features are not allowed to run in this server\'s sessions. Features are defined on a driver level; see documentation for more details. Features listed here will not be enabled even if also listed in "allow-insecure", and even if "relaxed-security" is enabled. If string, a path to a text file containing policy or a comma-delimited list.',
85
+ items: {
86
+ type: 'string',
87
+ },
88
+ title: 'deny-insecure config',
89
+ type: 'array',
90
+ uniqueItems: true,
91
+ },
92
+ driver: {
93
+ description:
94
+ 'Driver-specific configuration. Keys should correspond to driver package names',
95
+ properties: {},
96
+ title: 'driver config',
97
+ type: 'object',
98
+ },
99
+ 'keep-alive-timeout': {
100
+ appiumCliAliases: ['ka'],
101
+ default: 600,
102
+ description:
103
+ 'Number of seconds the Appium server should apply as both the keep-alive timeout and the connection timeout for all requests. A value of 0 disables the timeout.',
104
+ minimum: 0,
105
+ title: 'keep-alive-timeout config',
106
+ type: 'integer',
107
+ },
108
+ 'local-timezone': {
109
+ default: false,
110
+ description: 'Use local timezone for timestamps',
111
+ title: 'local-timezone config',
112
+ type: 'boolean',
113
+ },
114
+ log: {
115
+ appiumCliAliases: ['g'],
116
+ appiumCliDest: 'logFile',
117
+ description: 'Also send log output to this file',
118
+ title: 'log config',
119
+ type: 'string',
120
+ },
121
+ 'log-filters': {
122
+ $comment: 'TODO',
123
+ description: 'One or more log filtering rules',
124
+ items: {
125
+ type: 'string',
126
+ },
127
+ title: 'log-filters config',
128
+ type: 'array',
129
+ },
130
+ 'log-level': {
131
+ appiumCliDest: 'loglevel',
132
+ default: 'debug',
133
+ description: 'Log level (console[:file])',
134
+ enum: [
135
+ 'info',
136
+ 'info:debug',
137
+ 'info:info',
138
+ 'info:warn',
139
+ 'info:error',
140
+ 'warn',
141
+ 'warn:debug',
142
+ 'warn:info',
143
+ 'warn:warn',
144
+ 'warn:error',
145
+ 'error',
146
+ 'error:debug',
147
+ 'error:info',
148
+ 'error:warn',
149
+ 'error:error',
150
+ 'debug',
151
+ 'debug:debug',
152
+ 'debug:info',
153
+ 'debug:warn',
154
+ 'debug:error',
155
+ ],
156
+ title: 'log-level config',
157
+ type: 'string',
158
+ },
159
+ 'log-no-colors': {
160
+ default: false,
161
+ description: 'Do not use color in console output',
162
+ title: 'log-no-colors config',
163
+ type: 'boolean',
164
+ },
165
+ 'log-timestamp': {
166
+ default: false,
167
+ description: 'Show timestamps in console output',
168
+ title: 'log-timestamp config',
169
+ type: 'boolean',
170
+ },
171
+ 'long-stacktrace': {
172
+ default: false,
173
+ description:
174
+ 'Add long stack traces to log entries. Recommended for debugging only.',
175
+ title: 'long-stacktrace config',
176
+ type: 'boolean',
177
+ },
178
+ 'no-perms-check': {
179
+ default: false,
180
+ description:
181
+ 'Do not check that needed files are readable and/or writable',
182
+ title: 'no-perms-check config',
183
+ type: 'boolean',
184
+ },
185
+ nodeconfig: {
186
+ $comment:
187
+ 'Selenium Grid 3 is unmaintained and Selenium Grid 4 no longer supports this file.',
188
+ description:
189
+ 'Path to configuration JSON file to register Appium as a node with Selenium Grid 3; otherwise the configuration itself',
190
+ title: 'nodeconfig config',
191
+ type: 'object',
192
+ },
193
+ plugin: {
194
+ description:
195
+ 'Plugin-specific configuration. Keys should correspond to plugin package names',
196
+ properties: {},
197
+ title: 'plugin config',
198
+ type: 'object',
199
+ },
200
+ port: {
201
+ appiumCliAliases: ['p'],
202
+ default: 4723,
203
+ description: 'Port to listen on',
204
+ maximum: 65535,
205
+ minimum: 1,
206
+ title: 'port config',
207
+ type: 'integer',
208
+ },
209
+ 'relaxed-security': {
210
+ default: false,
211
+ description:
212
+ 'Disable additional security checks, so it is possible to use some advanced features, provided by drivers supporting this option. Only enable it if all the clients are in the trusted network and it\'s not the case if a client could potentially break out of the session sandbox. Specific features can be overridden by using "deny-insecure"',
213
+ title: 'relaxed-security config',
214
+ type: 'boolean',
215
+ appiumCliDest: 'relaxedSecurityEnabled'
216
+ },
217
+ 'session-override': {
218
+ default: false,
219
+ description: 'Enables session override (clobbering)',
220
+ title: 'session-override config',
221
+ type: 'boolean',
222
+ },
223
+ 'strict-caps': {
224
+ default: false,
225
+ description:
226
+ 'Cause sessions to fail if desired caps are sent in that Appium does not recognize as valid for the selected device',
227
+ title: 'strict-caps config',
228
+ type: 'boolean',
229
+ },
230
+ tmp: {
231
+ appiumCliDest: 'tmpDir',
232
+ description:
233
+ 'Absolute path to directory Appium can use to manage temp files. Defaults to C:\\Windows\\Temp on Windows and /tmp otherwise.',
234
+ title: 'tmp config',
235
+ type: 'string',
236
+ },
237
+ 'trace-dir': {
238
+ description:
239
+ 'Absolute path to directory Appium can use to save iOS instrument traces; defaults to <tmp>/appium-instruments',
240
+ title: 'trace-dir config',
241
+ type: 'string',
242
+ },
243
+ 'use-drivers': {
244
+ appiumCliDescription:
245
+ 'A list of drivers to activate. Can be a comma-delimited string or path to CSV file. By default, all installed drivers will be activated.',
246
+ default: [],
247
+ description:
248
+ 'A list of drivers to activate. By default, all installed drivers will be activated.',
249
+ items: {
250
+ type: 'string',
251
+ },
252
+ title: 'use-drivers config',
253
+ type: 'array',
254
+ uniqueItems: true,
255
+ },
256
+ 'use-plugins': {
257
+ appiumCliDescription:
258
+ 'A list of plugins to activate. Can be a comma-delimited string, path to CSV file, or the string "all" to use all installed plugins.',
259
+ default: [],
260
+ description:
261
+ 'A list of plugins to activate. To activate all plugins, the value should be an array with a single item "all".',
262
+ items: {
263
+ type: 'string',
264
+ },
265
+ title: 'use-plugins config',
266
+ type: 'array',
267
+ uniqueItems: true,
268
+ },
269
+ webhook: {
270
+ $comment:
271
+ 'This should probably use a uri-template format to restrict the protocol to http/https',
272
+ appiumCliAliases: ['G'],
273
+ description: 'Also send log output to this http listener',
274
+ format: 'uri',
275
+ title: 'webhook config',
276
+ type: 'string',
277
+ },
278
+ },
279
+ title: 'server config',
280
+ type: 'object',
281
+ },
282
+ },
283
+ title: 'Appium Configuration',
284
+ type: 'object',
285
+ });
286
+
287
+ export default schema;
@@ -0,0 +1,222 @@
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
+ */
32
+ export class ArgSpec {
33
+ /**
34
+ * The canonical name of the argument. Corresponds to key in schema's `properties` prop.
35
+ * @type {string}
36
+ */
37
+ name;
38
+
39
+ /**
40
+ * The `ExtensionType` of the argument. This will be set if the arg came from an extension;
41
+ * otherwise it will be `undefined`.
42
+ * @type {ExtensionType|undefined}
43
+ */
44
+ extType;
45
+
46
+ /**
47
+ * The name of the extension, if this argument came from an extension.
48
+ *
49
+ * Otherwise `undefined`.
50
+ * @type {string|undefined}
51
+ */
52
+ extName;
53
+
54
+ /**
55
+ * The schema ID (`$id`) for the argument. This is automatically determined, and any user-provided `$id`s will be overwritten.
56
+ *
57
+ * @type {string}
58
+ */
59
+ ref;
60
+
61
+ /**
62
+ * The CLI argument, sans leading dashes.
63
+ * @type {string}
64
+ */
65
+ arg;
66
+
67
+ /**
68
+ * The desired keypath for the argument after arguments have been parsed.
69
+ *
70
+ * Typically this is camelCased. If the arg came from an extension, it will be prefixed with
71
+ * `<extType>.<extName>.`
72
+ * @type {string}
73
+ */
74
+ dest;
75
+
76
+ /**
77
+ * Whatever the default value of this argument is, as specified by the
78
+ * `default` property of the schema.
79
+ * @type {D}
80
+ */
81
+ defaultValue;
82
+
83
+ /**
84
+ * Builds some computed fields and assigns them to the instance.
85
+ *
86
+ * Undefined properties are not assigned.
87
+ *
88
+ * The _constructor_ is private. Use {@link ArgSpec.create} instead.
89
+ * @private
90
+ * @template D
91
+ * @param {string} name
92
+ * @param {ArgSpecOptions<D>} [opts]
93
+ */
94
+ constructor (name, {extType, extName, dest, defaultValue} = {}) {
95
+ // we must normalize the extension name to fit into our convention for CLI
96
+ // args.
97
+ const arg = ArgSpec.toArg(name, extType, extName);
98
+
99
+ const ref = ArgSpec.toSchemaRef(name, extType, extName);
100
+
101
+ // if no explicit `dest` provided, just camelCase the name to avoid needing
102
+ // to use bracket syntax when accessing props on the parsed args object.
103
+ const baseDest = _.camelCase(dest ?? name);
104
+
105
+ const destKeypath =
106
+ extType && extName ? [extType, extName, baseDest].join('.') : baseDest;
107
+
108
+ this.defaultValue = defaultValue;
109
+ this.name = name;
110
+ this.extType = extType;
111
+ this.extName = extName;
112
+ this.arg = arg;
113
+ this.dest = destKeypath;
114
+ this.ref = ref;
115
+ }
116
+
117
+ /**
118
+ * Return the schema ID (`$id`) for the **argument** given the parameters.
119
+ *
120
+ * If you need the "root" or "base" schema ID, use {@link ArgSpec.toSchemaBaseRef} instead.
121
+ * @param {string} name - Argument name
122
+ * @param {ExtensionType} [extType] - Extension type
123
+ * @param {string} [extName] - Extension name
124
+ * @returns {string} Schema ID
125
+ */
126
+ static toSchemaRef (name, extType, extName) {
127
+ const baseRef = ArgSpec.toSchemaBaseRef(extType, extName);
128
+ if (extType && extName) {
129
+ return [`${baseRef}#`, PROPERTIES, name].join('/');
130
+ }
131
+ return [`${baseRef}#`, PROPERTIES, SERVER_PROP_NAME, PROPERTIES, name].join('/');
132
+ }
133
+
134
+ /**
135
+ * Return the schema ID for an extension or the base schema ID.
136
+ * @param {ExtensionType} [extType] - Extension type
137
+ * @param {string} [extName] - Extension name
138
+ */
139
+ static toSchemaBaseRef (extType, extName) {
140
+ if (extType && extName) {
141
+ return `${extType}-${ArgSpec.toNormalizedExtName(extName)}.json`;
142
+ }
143
+ return APPIUM_CONFIG_SCHEMA_ID;
144
+ }
145
+
146
+ /**
147
+ * Return the unique ID for the argument given the parameters.
148
+ * @param {string} name - Argument name
149
+ * @param {ExtensionType} [extType] - Extension type
150
+ * @param {string} [extName] - Extension name
151
+ * @returns {string} Unique ID
152
+ */
153
+ static toArg (name, extType, extName) {
154
+ const properName = _.kebabCase(name.replace(/^--?/, ''));
155
+ if (extType && extName) {
156
+ return [extType, _.kebabCase(extName), properName].join('-');
157
+ }
158
+ return properName;
159
+ }
160
+
161
+ /**
162
+ * Normalizes a raw extension name (not including the type).
163
+ * @param {string} extName - Extension name
164
+ * @returns {string} Normalized extension name
165
+ */
166
+ static toNormalizedExtName (extName) {
167
+ return _.kebabCase(extName);
168
+ }
169
+
170
+ /**
171
+ * 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.
172
+ * @param {string} schemaId - Root schema ID
173
+ * @returns {{extType: ExtensionType|undefined, normalizedExtName: string|undefined}}
174
+ */
175
+ static extensionInfoFromRootSchemaId (schemaId) {
176
+ const matches = schemaId.match(SCHEMA_ID_REGEXP);
177
+ if (matches?.groups) {
178
+ const {extType, normalizedExtName} = matches.groups;
179
+ return {extType, normalizedExtName};
180
+ }
181
+ return {};
182
+ }
183
+
184
+ /**
185
+ * Creates an `ArgSpec`
186
+ *
187
+ * @param {string} name - The canonical name of the argument. Corresponds to a key in a schema's
188
+ * `properties` property.
189
+ * @param {ArgSpecOptions} [opts] - Options
190
+ * @returns {Readonly<ArgSpec>}
191
+ */
192
+ static create (name, opts) {
193
+ return Object.freeze(new ArgSpec(name, opts));
194
+ }
195
+
196
+ /**
197
+ * String representation, useful for debugging
198
+ * @returns {string}
199
+ */
200
+ /* istanbul ignore next */
201
+ toString () {
202
+ let str = `[ArgSpec] ${this.name} (${this.ref})`;
203
+ if (this.extType && this.extName) {
204
+ str += ` (ext: ${this.extType}/${this.extName})`;
205
+ }
206
+ return str;
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Options for {@link ArgSpec.create}
212
+ * @template D
213
+ * @typedef {Object} ArgSpecOptions
214
+ * @property {string} [extName]
215
+ * @property {ExtensionType} [extType]
216
+ * @property {string} [dest]
217
+ * @property {D} [defaultValue]
218
+ */
219
+
220
+ /**
221
+ * @typedef {import('../ext-config-io').ExtensionType} ExtensionType
222
+ */