feat(types): add new @appium/types package

This changes the TS configuration to to a) emit declarations for supported packages, and b) build declarations incrementally by package.

To begin, we are targeting `appium`, `@appium/base-driver` and `@appium/support` for declarations; these three are intended to be published with declarations generated via their JS.

Both `@appium/support` and `appium` are fully typechecked (sans implicit `any` types), but `@appium/base-driver` is mostly not.  Regardless, declarations are generated for all three.

Each package will have its own `tsconfig.json` which "inherits" from `config/tsconfig.base.json`.  Each will also need to declare relative paths to dependencies. e.g., the `appium` package must configure its `tsconfig.json` so that it depends on the declarations of `@appium/support` and also where to find `@appium/support`. Essentially, a small dependency tree gets built, and those at the "root" get their declarations built first; then the consumers use those declarations.

Also reorganized some scripts and added `npm-run-all` to help and added a "typecheck" job to CI.

Since `npm install` will build, it's pretty inconvenient to have `npm` abort installation if the build fails.  In that case, if any of the build steps fail (see `build:loose`) the installation will continue.  Added a `prepublishOnly` which runs a build in "strict" mode where everything must pass.

_Note: there's still a strong coupling of "what `BaseDriver` provides" and "what an external driver *may* provide".  It's unclear to me if this is a problem, though there seem to be a handful of methods that external drivers *must* implement.  The easiest way to get around this would probably be to extract these required properties and methods into their own interface (duplication) if we cannot otherwise declare methods as "abstract".  As it stands, the `Driver` is basically just `BaseDriver`._

Also:

- Moved some configuration types out of `appium` and into here, since they are used both by `appium` and `base-driver` via `DriverOpts`
- Split `Driver` type into "the thing that BaseDriver is" and "all the other methods external drivers can implement"
- Added missing `proxyCommand` method
- Better generics for `LogType`
- Remove unused `Constructor` type
- Better types for `getSession`/`getSessions`
- Added util `Class` type
- Added many missing typings from DefinitelyTyped
- Added `ts-node` for `@wdio/types` (I need to send a PR to webdriver to eliminate this dependency)
- Type checks in their CI step
This commit is contained in:
Christopher Hiller
2022-03-16 14:49:00 -07:00
parent 4ff63e2425
commit 72085caa0a
14 changed files with 1112 additions and 29 deletions

125
scripts/generate-schema-json.js Executable file
View File

@@ -0,0 +1,125 @@
#!/usr/bin/env node
/**
* This script takes the JSON schema (which is actually JS) in `appium` and converts it to JSON proper,
* then outputs the result in a couple places. It also copies the JSON schema into `@appium/types`
* to avoid cyclical dependencies in TS.
*/
// @ts-check
/* eslint-disable no-console */
const _ncp = require('ncp');
const path = require('path');
const {writeFile} = require('fs').promises;
const {info, success, error} = require('log-symbols');
const ncp = require('util').promisify(_ncp);
/**
* `appium` package root.
*/
const APPIUM_ROOT = path.join(__dirname, '..', 'packages', 'appium');
/**
* Root of Appium artifacts.
*/
const APPIUM_BUILD_DIR = path.join(APPIUM_ROOT, 'build', 'lib');
/**
* `@appium/types` package root
*/
const TYPES_ROOT = path.join(__dirname, '..', 'packages', 'types');
/**
* Basename of exported `.json` file.
*/
const JSON_FILENAME = 'appium-config.schema.json';
/**
* Path to source schema `.js`.
*
* This is the _build artifact_. Node can use ES modules, but it cannot use them unless one of two things is true:
* - `type: module` in `package.json`
* - file `.mjs` extension
*
* Neither of these things is true, so we have to use the build artifact.
*/
const SRC = path.join(APPIUM_BUILD_DIR, 'schema', 'appium-config-schema.js');
/**
* Destination(s) for `.json` output.
* the one in `types` is for its `generate-schema-declarations` script; the other one is for general consumer usage.
* These paths should be in `.gitignore`
*/
const JSON_DESTS = [
path.join(TYPES_ROOT, 'schema', JSON_FILENAME),
path.join(APPIUM_BUILD_DIR, JSON_FILENAME),
];
/**
* In order to avoid a circular dependency, `@appium/types` needs this file, because it computes types from the constant sources. It is not possible to do this with a `.json` file, unfortunately.
* Note that this is the _source_, not the build artifact. TS understands ESM better than Babel output, unsurprisingly.
* See https://github.com/microsoft/TypeScript/issues/32063 for details
*/
const COPY_SRC = path.join(APPIUM_ROOT, 'lib', 'schema', 'appium-config-schema.js');
/**
* It goes here. These should be in `.gitignore`
*/
const COPY_DESTS = [
path.join(TYPES_ROOT, 'src', 'appium-config-schema.js')
];
async function write () {
/** @type {typeof import('appium/lib/schema/appium-config-schema.js')} */
let schema;
try {
schema = require(SRC).default;
} catch (err) {
throw new Error(
`${error} Failed to read ${SRC}; did you execute \`npm run build\` first?`,
);
}
const json = JSON.stringify(schema, null, 2);
for await (const dest of JSON_DESTS) {
try {
await writeFile(dest, json);
console.log(`${info} Wrote JSON schema to ${dest}`);
} catch (err) {
throw new Error(
`${error} Failed to write JSON schema to ${dest}: ${err.message}`,
);
}
}
for await (const dest of COPY_DESTS) {
try {
await ncp(COPY_SRC, dest);
console.log(`${info} Copied ${SRC} to ${dest}`);
} catch (err) {
throw new Error(`${error} Failed to copy schema module to ${dest}: ${err.message}`);
}
}
}
async function main () {
try {
await write();
} catch (err) {
console.log(err);
process.exitCode = 1;
return;
}
console.log(`${success} Done.`);
}
if (require.main === module) {
main();
}
module.exports = {main, write};