- we want to reference this file from the documentation, and the easiest way to do so is just to add it to vcs. we can change where it lives later, if necessary.
- modify gulpfile to handle this
- revert changes to default boolean values
- fix types w/r/t the object returned by `parseArgs()`
- `.d.ts` files _and_ `.js` files can now be checked via `tsc` (given typescript Version 4.6.0-dev.20211102 or newer); remove `jsconfig.json` in lieu of `tsconfig.json`. js files will only be checked if `// @ts-check` pragma is present
- add more comments explaining various types
- fix some types in `lib/main.js`
- rename `args` to `parsedArgs` to be more obvious about what's what
- `parsedArgs` is now correctly typed! I think.
- fix `NormalizedAppiumConfiguration` (and rename to `NormalizedAppiumConfig`) which was essentially incorrect, since it did not respect `appiumCliDest` (e.g., `tmp` would be `tmpDir` in the config normalized by the code, but the type would not reflect this)
- add type for `driverConfig` prop to `AppiumDriver`
- remove a bunch of `default: false` nonsense from schema
- fix `tmp`, which is now stored in `tmpDir` as it should be
Note that any source file with a `// @ts-check` in it which operates on the parsed arguments object will have type support available. we're not linting the types (yet), but when we do, it will save us some potential headaches like incorrect usage or unknown arguments
When combining extension schemas with the base schema, the keywords weren't quite right (the extension name was not being recognized in any config files).
refactored a few things to support this, but the nut of it is in `AppiumSchema#finalize`, where we no longer use `anyOf`. I added a couple more functions to `ArgSpec` which generate various IDs and references in order to avoid having mysterious calls to `_.kebabCase()` everywhere.
in addition:
- no longer breaks if the exact same extension tries to register itself twice. this can happen if one happens to, say, instantiate multiple `DriverConfig`s. this happens in the tests. it _should_ not happen, since it's really unnecessary, so instead of this, we have a `DriverConfig.getInstance()` (and for `PluginConfig`) which keeps track of singletons linked to `APPIUM_HOME` dirs. at runtime, we're unlikely to ever need to instantiate multiple instances of these classes _anyway_, so it's on a per-appium-home basis mainly for testing purposes. the constructors are still usable directly, but discouraged (now marked `@private`).
- updated tests to reflect this change. in at least one place in the tests, we're trying to get at the actual extension class constructor, and were instantiating an extra `DriverConfig` to do that--but now we do not need to, as `DriverConfig.getInstance()` just grabs the one already created when we called `runExtensionCommand()`.
- removed unused `AppiumSchema#getAllSchemas`
- renamed "ID" to "ref" to be more clear about which identifier we're talking about--a schema reference, as one would put in a `$ref` field
- renamed arg `<extension-type>s` to `use-<extension-type>s` wherever necessary, since that arg was renamed; fix a bunch of fixtures
- restore default value of `[]` to `use-plugins`, since the code expects it to be defined
the `configure()` function in the spawn watcher could fail in some circumstances because because the task created by the call to `this.gulp.watch('./gulpfile.js')` does not actually signal completion, which can cause gulp to fail with a "did you forget to signal async completion?" error.
instead of trying to watch gulpfiles, we can just ignore them. not ideal, but at least it won't break.
previously, we'd create a new schema for every single argument. I felt this was probably wrong... and I was right. `ajv` allows getting the schema (and thus validation) by reference path! so `appium.json#/properties/server/properties/address` would be the schema for `address`.
this necessitated storing the reference path for each argument, so `ArgSpec` was updated to reflect that. further, we add references (`$ref`) to each schema added by each extension under an `anyOf` prop of the `driver` and `plugin` config attributes.
- in addition, added some more tests to more thoroughly cover branches and define behavior (especially in `toParserArgs()`)
- moved some tests into `test/schema/`
- added a couple tests for `ArgSpec`
- renamed `--plugins` to `--use-plugins` and `--drivers` to `--use-drivers`
- schema updated to nest `driver` and `plugin` under `server`. `server` is now currently the only top-level prop.
- removed all hard-coded `$id` fields from arguments, as they aren't needed anyway (and maintaining them is likely going to be a source of bugs)
- `ArgumentDefinition` type becomes `ArgumentDefinitions` and is now a `Map`, as it's basically a devolved `Map` anyway.
- fixed problem with extensions using `appiumCliAliases`, which could allow conflicts with base appium server args.
- add some missing `appiumCliDescription` fields
- `lib/schema/schema.js` now exports bound methods from the `AppiumSchema` instance
- add some specific error types
- expose `ArgSpec.toId` which is helpful if we have all of the information about an argument, but don't have its unique ID handy
- much docstrings/types
- see `schema/arg-spec.js`
- avoids problems with weirdly-named extensions
- make arg handling easier to reason about
- significantly improve speed of default value checks
- remove unused `warnNodeDeprecations`
- move extension schema reading to `extension-config`
- add a `ReadonlyMap` util class
- fixed issue with incorrectly-derived CLI argument
- some reformatting + import organization
- renamed some unclear variables
- created more constants
- remove schema migration code
- remove log-fiddling in `config-file-e2e-specs.js`
`lint-staged` doesn't want to do it because reasons. I don't think it necessarily supports the use case where if file _A_ is staged, then file _B_ should be staged--it only acts upon file _A_.
also replace the `lint:fix` script with use of `eslint` proper, since using `gulp` misses files _not_ in `packages/`.
This now consumes Ajv directly to validate CLI args. Since a JSON schema is made of JSON schemas, we can simply validate an argument against _the schema for that argument_.
- Replaced parser transformation functions (`parseSecurityFeatures()`, `parseJsonStringOrFile()`) with custom transforms which are bound to keyword `appiumCliTransformer`. e.g., any option that can now accept a JSON string or filepath on the command line may use `appiumCliTransformer: 'json'`. This is the default behavior for `array` and `object` -type options, which cannot easily be expressed on the CLI. The config file does _not_ support this behavior, so the value must be whatever the schema `type` says (e.g., an `object` instead of a JSON string or filepath). To this end, removed `parser-helpers.js`
- Further, we now disallow union types in the schema. A prop must have a single type.
- Added custom keywords `appiumCliDest`, `appiumCliDescription`, and `appiumCliTransformer`. Previously (if existed), this could have been any value--these are now typed by the schema--e.g., a number for `appiumCliDest` will fail.
- Added more types and TS checks around
- Fixed misspelling of "successfully" in various places
- Removed `@oclifg/errors` because we can now use `better-ajv-errors` to display them, as intended. It looks great!
- Moved all schema-related stuff to `lib/schema/` because I had too many modules
- Added more constants to please @mykola-mokhnach 😄
- Handles untyped properties or `null`-typed properties
- `getSchema()`, `validate()` etc now accept a schema ID. The schema ID is derived from the argument name. This is how we handle validation for each argument individually. The config file is still validated against the entire schema at once.
- Fixed problem where only `driverConfigs` were ever used to call extension commands!
- Added some semaphore-like things to `ExtConfigIO` to avoid multiple reads/writes at once
- Fixed display of metavars in `--help`
- More tests; removed tests for removed code
- flip if/else conditions in various places
- add export `SERVER_SUBCOMMAND` constant to `parser.js` (which is the string `server`)
- add `SERVER_PROP` constant to `schema.js` (which is also the string `server` but means a difft thing)
- more constants in `ext-config-io.js`
- add some more types in `ArgParser` constructor; simplify
- add constant regexp in `schema-args.js`
- use more lodash in `grid-register.js`
- simplify `_.includes()` call in `main.js`
- simplify error-handling in `readExtensionSchema()` of `schema.js`; fix docstring; remove associated tests
- fix a sanity check in `schema-specs.js`
Summary: This PR adds support for configuration files (e.g., `.appiumrc.json`) and schema definitions. Command-line options _and_ configuration is now derived from the schema(s), for Appium _and_ its extensions (both plugins and drivers).
## Dependencies
### Development
- Added `through2` for a custom gulp task to convert `appium/lib/appium-config-schema.js` to a JSON file
- Added `json-schema-to-typescript` for converting aforementioned JSON file to a `.d.ts` file (which gets published)
### Production (applies to `appium` only)
- Added `libconfig` for multi-format config file loading, which is a smaller drop-in replacement for `cosmiconfig`
- Added `ajv` for schema support and validation
- Added `ajv-formats` to understand optional value formats (e.g., `ipv4`)
- Added `@sidvind/better-ajv-errors` which provides nice error output on the CLI if the config file is invalid
- Added `@oclif/errors` for better error output on the CLI if _arguments_ are invalid
## New Modules of Note
- `lib/schema.js` consumes `ajv` to create and modify a schema
- It exposes a handful of public API functions, so that consumers can query the schemas without actually running Appium.
- The flow is basically this: read the base schema (`appium-config-schema.js`). When extensions load and have schemas, add those to a pot. When `finalizeSchema()` is called, throw the base schema into the pot as well, stir, and add to singleton `ajv` instance. Now we can validate against the stew.
- The schema for an extension is read and registered via `readExtensionSchema()`
- Provides a way to get at default (for showing those "non-default" options) and `flattenSchema()` which squishes the finalized schema into a single `Record` key/value object, for use by the CLI. This may make sense to move into `schema-args.js`
- Provides access to ajv-and-JSON-schema-defined _formatters_ (which are basically just validation functions). For example, if we want any string, it's just a `string`. But if we want the string to match a regexp, that's a formatter. (e.g., the `hostname` type)
- lib/config-file.js` consumes `lilconfig` to read config files, validate them against the schema, and provide the result as CLI args
- Handles conversion between kebab-case and camelCase
- Contains `formatErrors()` which will be called after validation of a config file fails, which shows _exactly_ where the config file is wrong (usually).
- `lib/cli/schema-args.js` which serves as an adapter between the schema and `argparse`
- Translates between JSON schema and `argparse`, which...well, I did what I could. It's pretty close.
- Wraps the formatters as mentioned above, and provides custom ones.
- We cannot, unfortunately, wholly use schema validation against CLI args. The flag names are different, the structure is different, there's no actual _file_ to compare against, and `argparse` does not help. We _can_ use anything `argparse` natively provides, in addition to `ajv` formatters, and any custom ones (e.g., allowable number ranges).
- Will likely end up adding more formatters corresponding to various JSON schema features later. We _may_ be able to use _more_ of Ajv to do this (e.g., reuse its internal validators, if they are public APIs).
- `lib/appium-config-schema.js` which is the root schema. Extensions add to this before it is registered & compiled by `ajv`.
- The basic shape is that there are three top-level props, `server`, `driver`, and `plugin`. `server` contains all of the server options as you'd expect, and `driver` and `plugin` will be filled in by extensions. Each will have properties corresponding to the extension name. You can find examples in `sample-code` and the test fixtures
- There's a gulp task that takes this file, rewrites it as JSON, then pipes it into another tool which outputs a `.d.ts` file, nicely described using all of the metadata from the schema. This will be useful for mainly contributors, extension authors, and consumers of Appium (though not end-users).
- `lib/ext-config-io.js`
- A new module which handles reading/writing of YAML extension config files only.
- This class is a singleton.
- Some logic pulled out of `extension-config.js` and into here
- Previously, we would read/write this file multiple times (once per subclass of `ExtensionConfig`)
- This module uses a naive dirty-tracker to persist the extension data automatically.
- Subclasses of `ExtensionConfig` only have access to their "portion" of the config file; drivers can't see plugins and vice-versa.
- `DRIVER_TYPE` and `PLUGIN_TYPE` originate here, mainly to avoid a circular dependency
## Modifications
- The single source of truth for the CLI _and_ config file options is the schema, with the caveats that:
- Command-line arguments (for `appium server` are effectively a flat list, but the schema/config file is not.
- Extension-specific configuration lives in an extension-type-specific property (`driver` or `plugin`) in the config file
- Under each (`driver` or `plugin`), each extension may have a configuration section corresponding to the extension name
- The shape of the extension-specific options _can_ be defined by a schema (and referenced in the `appium.schema` prop of the extension's `package.json`)
- If an extension does not provide a schema, no validation will occur on any options found here -- TODO needs tests
- This does _not_ affect _CLI commands_ added by extensions; only options for `server`.
- The `--appium-home` flag is now removed, as we can't find extensions without `APPIUM_HOME`, which means we can't parse arguments (because extensions may have added some).
- `--plugin-args` and `--driver-args` are removed. These are replaced by individual flags for each argument, defined by the extension in its schema, e.g., `--driver-fake-host=127.0.0.1`
- Extensions can still define argument "constraints" using `argConstraints`, but this is considered deprecated
- `lib/main.js`
- Now exports a few public APIs
- split into functions `init` and `main`. `init` is intended to be run via a consumer, which does what the old `main` did up to starting the server. This bisection is not perfect, as `init` still does some CLI-related things; it needs to be further broken up.
- `driverConfig.read()` and `pluginConfig.read()` are now called in `lib/cli/parser.js`.
- Merges the CLI args, config file args, and defaults, in that order of precedence.
- `lib/grid-register.js`
- Supports embedded configuration instead of just a path to a JSON file.
- Not clear to me if `nodeconfig` is a thing we need to keep?
- `lib/extension-config.js`
- Given `APPIUM_HOME` is where we keep the extensions, this file now defines `APPIUM_HOME`. It is exported from the `appium` package.
- Add `getSchemaProblems()` method, which will read/validate/register the schema for an extension, if one exists.
- Added many type annotations
- `read()` and `write()` now delegate to the singleton `ExtConfigIO` instnace
- Re-exports `DRIVER_TYPE` and `PLUGIN_TYPE`
- `lib/driver-config.js`
- **Fix** the case where two drivers attempt to use the same `automationName`. It appears this was intended, but did not work correctly. This involved overloading `read()`.
- `lib/plugin-config.js`
- Literally nothing, which is neat.
- `lib/config.js`
- Remove unused validations (now occur via schema validation or are extension-specific)
- Rename `getNonDefaultArgs` to `getNonDefaultServerArgs` to be more specific
- Rewrote `getNonDefaultServerArgs`, which became much more complicated due to adding a config file and schema defaults to the mix. Flexed my lodash-fu, which is still kind of crappy because I'm better with `lodas/fp`.
- `lib/cli/parser.js`
- Created a wrapper for `argparse`: class `ArgParser`, which replaces the monkeypatching we were doing here.
- This class also stores extension-specific args in a namespace. For example, `--driver-foo-bar=baz` would normally be stored in prop `driverFooBar`, but this moves it into `driver: {foo: {bar: 'baz'}}`, which will be provided to its associated extension
- `addExtensionsToParser` renamed to `addExtensionCommandsToParser` which is more specific.
- Removed `getDefaultServerArgs` since those now come from the schema.
- Removed "shared args" stuff
- `getParser` is now async, and constructs an `ArgParser`. By necessity, the extension config YAML is read here (which is still wonky because we have to call `read()` twice; it only actually ends up reading once)
- `getParser` also _finalizes_ the schema, which means "katamari-up the base schema and all extension schemas, then tell ajv about the result"
- `lib/cli/parser-helpers.js`
- Allow security features to just be an array (as allowed in schema)
- TODO: needs test
- `lib/cli/extension.js`
- Use `APPIUM_HOME` exported from `lib/extension-config` instead of `args.appiumHome`
- Avoids re-instantiating `ExtensionConfig` subclasses if not needed
- `lib/cli/args.js`
- Remove all server argument definitions in lieu of schema; leave a few that do not make sense in a config file (like `--config`, which determines the location of the config file!)
- Server args are now pulled from the schema via a call to `toParserArgs()` with overrides for various validation functions (which are confusingly "types" in `argparse` parlance). Overrides can probably be removed or relocated to `schema-args`
- Instantiates the `DriverConfig` and `PluginConfig` instances, which are exported. These might want to move somewhere else
- Tests
- Added unit tests for `extension-config`, `grid-register` which were missing
- Added unit tests for new `schema` and `schema-args` and `config-file` modules
- Added e2e test for config file handling
- Other necessary modifications
- Fix path to `fake-driver` in `driver-e2e-specs`
- Removed stuff from `parser-specs` which no longer applies
## Other
- Added `scripts/generate-schema-declarations.js` which uses `json-schema-to-typescript` to create `appium-config.d.ts`
- Added config file samples in `.json` and `.yaml` formats
- Added custom gulp task to `appium/gulpfile.js` to generate schema in JSON format, since its "single source of truth" is actually just a big JS object
- Moved `appium` CLI-related tests into `test/cli`
- Added some declarations into `packages/appium/types`; the schema-related one is generated by the script mentioend above, but `types.d.ts` is just some stuff that was not expressible in a docstring
- `postTranspile` would only run from certain _other_ tasks (like `test`). This seemed incorrect, so now it runs after `transpile`, regardless of what task (or human) runs `transpile`.
- Added a `dev` task, which is like `watch`, except does not run tests (only transpiles)