The big idea here is:
1. `desiredCapConstraints` should always contain the constraints _for that driver_; it no longer contains the "base" constraints. This is the only thing that really required any logic changes (though they are somewhat spread out). The "merged" constraints object is available via `_desiredCapConstraints`.
2. `Capabilities` and W3C-style `W3CCapabilities` are now _derived from_ constraints. This absolves us (and extension authors) from needing to create an interface describing their capabilities.
3. Changes to caps and constraints now reflect in `DriverOpts` (`this.opts`) in drivers
4. `caps` and `originalCaps` declarations have moved from `Core` to `Driver`
5. TypeScript compiles the JS output from `@appium/types` (which is just a static "constraints" object)
Resolves#17518
This change makes Appium recognize the existence of an extension in the current (or parent) dir, and automatically adds it to the extension manifest.
Resolution of Appium (e.g., _how_ Appium was installed) is no longer taken into account when determining if we're "in" a package that depends upon Appium. So you can easily `npm link appium` in an extension working copy if you wish, and this will not impact Appium's behavior.
To support this change, `hasAppiumDependency()` in `@appium/support`'s `env` has changed to be a wrapper around the new `findAppiumDependency()`. The strategy here is to use `npm list --long` to get detailed information about the current directory. The result will have an object with a `path` property, and that property points to the current workspace root (or current project root). We can then read the `package.json` in the `path` property--or just the CWD--to make the determination about an `appium` dependency (or lack thereof). For extension working copies, this will return `true` because `appium` will be present in `devDependencies`, `peerDependencies`, or both. For any other project, it will usually just be in `devDependencies`. If there's no `package.json` in the CWD and we're not in a workspace, Appium continues to use the default `APPIUM_HOME`.
One side effect of this is that running `appium` from the monorepo itself treats the dir hierarchy as a package which has an `appium` dependency (because it does!). To that end, the default `APPIUM_HOME` will _not_ be used when running `appium` from _anywhere in the monorepo_ unless you explicitly set `APPIUM_HOME` in an environment variable. Generally, our tests prescribe their own temporary `APPIUM_HOME` directories, so this should have no effect on them (in the general case). It _will_ have an effect on running `appium` manually in a development environment, as the workspace root will be treated as `APPIUM_HOME`; running `node ./packages/appium/index.js driver install uiautomator2` (or `npm start -- driver install uiautomator2`) will install that driver as a dev dependency (and save it). I could add a special case _just for the monorepo_ if we don't want this to happen, but it may introduce complexity we/I don't want to maintain, so I left it out. Furthermore, `npm start -- driver list` will show the `fake` driver, and `npm start -- plugin list` will show all of the plugins in `packages/`.
Regardless, I view this as a solid win for extension developers.
Bonus:
While I don't have benchmarks, this should considerably improve performance when detecting locally-installed (`node_modules/some-extension`) or extension working copies. Instead of using `klaw` to walk the filesystem (in serial!) we can just use `glob` to look for relevant `package.json` files. Instead of waiting until `glob` is completely finished searching, we can listen for its `match` evens and begin processing `package.json` files as soon as they are found. Because `@appium/support` only exposes a promisifed `glob()`--with no access to the `EventEmitter` it returns--`appium` depends on `glob` directly.
This is handy for drivers which need to find executables in the filesystem.
Also fixes possible false-positives in `fs.exists()`/`fs.hasAccess()` on Windows.