mirror of
https://github.com/appium/appium.git
synced 2026-11-02 23:33:14 +08:00
Compare commits
63
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b49356322 | ||
|
|
bfc3134968 | ||
|
|
dbaf8d4cae | ||
|
|
eb73be8303 | ||
|
|
998514fcce | ||
|
|
49f157d63e | ||
|
|
43b115bb3d | ||
|
|
56cf504a2b | ||
|
|
2ebb16b077 | ||
|
|
c321441451 | ||
|
|
6be7488eef | ||
|
|
ab5b285714 | ||
|
|
8dc47ae922 | ||
|
|
91831cf82d | ||
|
|
4b10322b80 | ||
|
|
2602016eb3 | ||
|
|
1cb01a5e85 | ||
|
|
e78448dc65 | ||
|
|
34e0b70982 | ||
|
|
ebe9477a3c | ||
|
|
3b82c9f1a1 | ||
|
|
a9476a67ff | ||
|
|
24ad9f8741 | ||
|
|
639d475627 | ||
|
|
2fa6bf1e11 | ||
|
|
d3dc9ce733 | ||
|
|
08a6f3a308 | ||
|
|
24fb515a52 | ||
|
|
893bd888cf | ||
|
|
d54901a9c3 | ||
|
|
70d65b7d62 | ||
|
|
33442bd61b | ||
|
|
4cf8dbc0d3 | ||
|
|
943dce5eec | ||
|
|
04633e3e6d | ||
|
|
d171219938 | ||
|
|
61a0275e29 | ||
|
|
8f9de63e4a | ||
|
|
90c0d36f2a | ||
|
|
988c088873 | ||
|
|
7c2c642ce7 | ||
|
|
6b8eb2cbfe | ||
|
|
242c2ccbdc | ||
|
|
c5dadbbd36 | ||
|
|
74800cdf61 | ||
|
|
70c98ea12f | ||
|
|
592f9adcb1 | ||
|
|
fac31a3d68 | ||
|
|
2bbf42ff6b | ||
|
|
1f65161e56 | ||
|
|
355bf8e5da | ||
|
|
0ca5e7e21b | ||
|
|
11e7ad0cd4 | ||
|
|
c69b602d1b | ||
|
|
4cf13c5bfa | ||
|
|
a4c58cc561 | ||
|
|
64098c5f1a | ||
|
|
b4416c5c0f | ||
|
|
547b8a45c4 | ||
|
|
1bf841736b | ||
|
|
29a5bdac9a | ||
|
|
ab366f3a1f | ||
|
|
5446e5c7a7 |
@@ -0,0 +1,36 @@
|
||||
name: Cache Cleanup
|
||||
|
||||
# from https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||
|
||||
- name: Cleanup
|
||||
run: |
|
||||
gh extension install actions/gh-actions-cache
|
||||
|
||||
REPO=${{ github.repository }}
|
||||
BRANCH="refs/pull/${{ github.event.pull_request.number }}/merge"
|
||||
|
||||
echo "Fetching list of cache keys"
|
||||
cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH | cut -f 1 )
|
||||
|
||||
## Setting this to not fail the workflow while deleting cache keys.
|
||||
set +e
|
||||
echo "Deleting caches..."
|
||||
for cacheKey in $cacheKeysForPR
|
||||
do
|
||||
gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm
|
||||
done
|
||||
echo "Done"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
Vendored
+4
-1
@@ -39,6 +39,7 @@
|
||||
"skipFiles": [
|
||||
"<node_internals>/**",
|
||||
"**/node_modules/mocha/**",
|
||||
"**/node_modules/lodash/**",
|
||||
"**/node_modules/ts-node/**"
|
||||
],
|
||||
"sourceMaps": true,
|
||||
@@ -53,7 +54,9 @@
|
||||
"args": ["--logLevel", "Verbose"],
|
||||
"request": "launch",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
"<node_internals>/**",
|
||||
"**/node_modules/lodash/**",
|
||||
"**/node_modules/**/handlebars/**"
|
||||
],
|
||||
"sourceMaps": true,
|
||||
"type": "node"
|
||||
|
||||
@@ -78,6 +78,12 @@ The current committers are (in addition to the TC members above):
|
||||
* [@ddkjin](https://github.com/ddkjin)
|
||||
* [@stuartbrussell-intuit](https://github.com/stuartbrussell-intuit)
|
||||
* [@vmaxim](https://github.com/vmaxim)
|
||||
* [@Dan-Maor](https://github.com/Dan-Maor)
|
||||
* [@laolubenson](https://github.com/laolubenson)
|
||||
* [@Dor-bl](https://github.com/Dor-bl)
|
||||
* [@boneskull](https://github.com/boneskull)
|
||||
* [@mwakizaka](https://github.com/mwakizaka)
|
||||
* [@rerorero](https://github.com/rerorero)
|
||||
|
||||
### Contributors
|
||||
|
||||
|
||||
Generated
+1958
-2679
File diff suppressed because it is too large
Load Diff
+15
-14
@@ -32,7 +32,7 @@
|
||||
"build:packages": "lerna run build",
|
||||
"clean": "run-s \"clean:artifacts:*\" clean:workspaces",
|
||||
"clean:artifacts": "run-p \"clean:artifacts:*\"",
|
||||
"clean:artifacts:distfiles": "run-s \"build -- --clean\"",
|
||||
"clean:artifacts:distfiles": "npx rimraf \"./packages/*/build\" \"./packages/*/.tsconfig.tsbuildinfo\"",
|
||||
"clean:artifacts:packages": "lerna run clean || true",
|
||||
"clean:workspaces": "lerna clean -y",
|
||||
"ci": "npm ci",
|
||||
@@ -74,14 +74,14 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@colors/colors": "1.5.0",
|
||||
"@commitlint/cli": "17.4.2",
|
||||
"@commitlint/config-conventional": "17.4.2",
|
||||
"@commitlint/cli": "17.4.4",
|
||||
"@commitlint/config-conventional": "17.4.4",
|
||||
"@tsconfig/node14": "1.0.3",
|
||||
"@types/chai": "4.3.4",
|
||||
"@types/chai-as-promised": "7.1.5",
|
||||
"@types/diff": "5.0.2",
|
||||
"@types/mocha": "10.0.1",
|
||||
"@types/node": "18.13.0",
|
||||
"@types/node": "18.14.1",
|
||||
"@types/semver": "7.3.13",
|
||||
"@types/sinon": "10.0.13",
|
||||
"@types/sinon-chai": "3.2.9",
|
||||
@@ -93,7 +93,7 @@
|
||||
"chai-as-promised": "7.1.1",
|
||||
"chai-webdriverio-async": "2.7.0",
|
||||
"cpy-cli": "4.2.0",
|
||||
"eslint": "8.33.0",
|
||||
"eslint": "8.34.0",
|
||||
"eslint-config-prettier": "8.6.0",
|
||||
"eslint-find-rules": "4.1.0",
|
||||
"eslint-plugin-import": "2.27.5",
|
||||
@@ -104,9 +104,9 @@
|
||||
"get-port": "5.1.1",
|
||||
"handlebars": "4.7.7",
|
||||
"husky": "8.0.3",
|
||||
"json-schema-to-typescript": "11.0.3",
|
||||
"lerna": "6.4.1",
|
||||
"lint-staged": "13.1.1",
|
||||
"json-schema-to-typescript": "11.0.5",
|
||||
"lerna": "6.5.1",
|
||||
"lint-staged": "13.1.2",
|
||||
"log-symbols": "4.1.0",
|
||||
"midnight-smoker": "2.0.1",
|
||||
"mjpeg-consumer": "2.0.0",
|
||||
@@ -114,7 +114,7 @@
|
||||
"mocha": "10.2.0",
|
||||
"npm-run-all": "4.1.5",
|
||||
"prettier": "2.8.4",
|
||||
"rewiremock": "3.14.4",
|
||||
"rewiremock": "3.14.5",
|
||||
"rimraf": "3.0.2",
|
||||
"serve-static": "1.15.0",
|
||||
"sinon": "15.0.1",
|
||||
@@ -122,13 +122,13 @@
|
||||
"sync-monorepo-packages": "1.0.2",
|
||||
"through2": "4.0.2",
|
||||
"ts-node": "10.9.1",
|
||||
"typedoc": "0.23.24",
|
||||
"typedoc": "0.23.25",
|
||||
"typedoc-plugin-markdown": "3.14.0",
|
||||
"typedoc-plugin-resolve-crossmodule-references": "0.3.3",
|
||||
"typescript": "4.7.4",
|
||||
"typescript": "4.9.5",
|
||||
"validate.js": "0.13.1",
|
||||
"webdriverio": "7.30.0",
|
||||
"ws": "8.12.0",
|
||||
"webdriverio": "7.30.1",
|
||||
"ws": "8.12.1",
|
||||
"yaml-js": "0.3.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -145,6 +145,7 @@
|
||||
},
|
||||
"ajv-formats": {
|
||||
"ajv": "8.12.0"
|
||||
}
|
||||
},
|
||||
"http-cache-semantics": "4.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,33 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.0.0-beta.56](https://github.com/appium/appium/compare/appium@2.0.0-beta.55...appium@2.0.0-beta.56) (2023-02-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* appium:options should work via --default-capabilities ([11e7ad0](https://github.com/appium/appium/commit/11e7ad0cd403ab1dc100f581cdf93772e3449db3)), closes [#18191](https://github.com/appium/appium/issues/18191)
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v7.30.1 ([547b8a4](https://github.com/appium/appium/commit/547b8a45c4b5629720b0f4bdb0485a861aecbebf))
|
||||
* **support:** update dependency axios to v1.3.4 ([49f157d](https://github.com/appium/appium/commit/49f157d63e3bdbd205527a5dc8f997df68540546))
|
||||
* **types:** update dependency type-fest to v3.5.7 ([b4416c5](https://github.com/appium/appium/commit/b4416c5c0f40200b36909a1fbb492d8c4a212108))
|
||||
* **types:** update dependency type-fest to v3.6.0 ([08a6f3a](https://github.com/appium/appium/commit/08a6f3a308c7ee162e992629888557b31e50a26e))
|
||||
* update axios to v1.3.3 ([8f9de63](https://github.com/appium/appium/commit/8f9de63e4a622712db545ab63f9f4ce6654e4a91))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.55](https://github.com/appium/appium/compare/appium@2.0.0-beta.54...appium@2.0.0-beta.55) (2023-02-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docutils:** fix bad option name and ignore most falsy args ([5446e5c](https://github.com/appium/appium/commit/5446e5c7a755be081f46f1ed1ca8c13665d9a772))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.54](https://github.com/appium/appium/compare/appium@2.0.0-beta.53...appium@2.0.0-beta.54) (2023-02-09)
|
||||
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ async function main() {
|
||||
log.info(`Building docs for language '${lang}' and version ${majMinVer}`);
|
||||
const configFile = path.join(DOCS_DIR, `mkdocs-${lang}.yml`);
|
||||
await deploy({
|
||||
mkDocsYml: configFile,
|
||||
mkdocsYml: configFile,
|
||||
branch,
|
||||
prefix: path.join(basePrefix, lang),
|
||||
remote,
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
DELETE_SESSION_COMMAND,
|
||||
GET_STATUS_COMMAND,
|
||||
promoteAppiumOptions,
|
||||
promoteAppiumOptionsForObject,
|
||||
} from '@appium/base-driver';
|
||||
import AsyncLock from 'async-lock';
|
||||
import {parseCapsForInnerDriver, pullSettings} from './utils';
|
||||
@@ -44,7 +45,7 @@ class AppiumDriver extends DriverCore {
|
||||
* It is not recommended to access this property directly from the outside
|
||||
* @type {Record<string,ExternalDriver>}
|
||||
*/
|
||||
sessions = {};
|
||||
sessions;
|
||||
|
||||
/**
|
||||
* Access to pending drivers list must be guarded with a Semaphore, because
|
||||
@@ -52,14 +53,14 @@ class AppiumDriver extends DriverCore {
|
||||
* It is not recommended to access this property directly from the outside
|
||||
* @type {Record<string,ExternalDriver[]>}
|
||||
*/
|
||||
pendingDrivers = {};
|
||||
pendingDrivers;
|
||||
|
||||
/**
|
||||
* Note that {@linkcode AppiumDriver} has no `newCommandTimeout` method.
|
||||
* `AppiumDriver` does not set and observe its own timeouts; individual
|
||||
* sessions (managed drivers) do instead.
|
||||
*/
|
||||
newCommandTimeoutMs = 0;
|
||||
newCommandTimeoutMs;
|
||||
|
||||
/**
|
||||
* List of active plugins
|
||||
@@ -71,13 +72,13 @@ class AppiumDriver extends DriverCore {
|
||||
* map of sessions to actual plugin instances per session
|
||||
* @type {Record<string,InstanceType<PluginClass>[]>}
|
||||
*/
|
||||
sessionPlugins = {};
|
||||
sessionPlugins;
|
||||
|
||||
/**
|
||||
* some commands are sessionless, so we need a set of plugins for them
|
||||
* @type {InstanceType<PluginClass>[]}
|
||||
*/
|
||||
sessionlessPlugins = [];
|
||||
sessionlessPlugins;
|
||||
|
||||
/** @type {DriverConfig} */
|
||||
driverConfig;
|
||||
@@ -85,7 +86,11 @@ class AppiumDriver extends DriverCore {
|
||||
/** @type {AppiumServer} */
|
||||
server;
|
||||
|
||||
desiredCapConstraints = desiredCapabilityConstraints;
|
||||
/**
|
||||
* @type {AppiumDriverConstraints}
|
||||
* @readonly
|
||||
*/
|
||||
desiredCapConstraints;
|
||||
|
||||
/** @type {DriverOpts} */
|
||||
args;
|
||||
@@ -105,6 +110,13 @@ class AppiumDriver extends DriverCore {
|
||||
super(opts);
|
||||
|
||||
this.args = {...opts};
|
||||
this.sessions = {};
|
||||
this.pendingDrivers = {};
|
||||
this.newCommandTimeoutMs = 0;
|
||||
this.pluginClasses = new Map();
|
||||
this.sessionPlugins = {};
|
||||
this.sessionlessPlugins = [];
|
||||
this.desiredCapConstraints = desiredCapabilityConstraints;
|
||||
|
||||
// allow this to happen in the background, so no `await`
|
||||
(async () => {
|
||||
@@ -243,7 +255,7 @@ class AppiumDriver extends DriverCore {
|
||||
jsonwpCaps,
|
||||
promoteAppiumOptions(w3cCapabilities),
|
||||
this.desiredCapConstraints,
|
||||
defaultCapabilities
|
||||
defaultCapabilities ? promoteAppiumOptionsForObject(defaultCapabilities) : undefined
|
||||
);
|
||||
|
||||
const {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities} =
|
||||
|
||||
@@ -693,7 +693,7 @@ class ExtensionCommand {
|
||||
);
|
||||
}
|
||||
|
||||
if (!(scriptName in extScripts)) {
|
||||
if (!(scriptName in /** @type {Record<string,string>} */ (extScripts))) {
|
||||
throw this._createFatalError(
|
||||
`The ${this.type} named '${installSpec}' does not support the script: '${scriptName}'`
|
||||
);
|
||||
|
||||
@@ -435,7 +435,7 @@ export class ExtensionConfig {
|
||||
/**
|
||||
* @param {string} extName
|
||||
* @param {ExtManifest<ExtType>} extManifest
|
||||
* @param {ExtensionConfigMutationOpts} [opts]
|
||||
* @param {ExtensionConfigMutationOpts} opts
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async addExtension(extName, extManifest, {write = true} = {}) {
|
||||
@@ -448,7 +448,7 @@ export class ExtensionConfig {
|
||||
/**
|
||||
* @param {ExtName<ExtType>} extName
|
||||
* @param {ExtManifest<ExtType>} extManifest
|
||||
* @param {ExtensionConfigMutationOpts} [opts]
|
||||
* @param {ExtensionConfigMutationOpts} opts
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async updateExtension(extName, extManifest, {write = true} = {}) {
|
||||
@@ -465,7 +465,7 @@ export class ExtensionConfig {
|
||||
* Remove an extension from the list of installed extensions, and optionally avoid a write to the manifest file.
|
||||
*
|
||||
* @param {ExtName<ExtType>} extName
|
||||
* @param {ExtensionConfigMutationOpts} [opts]
|
||||
* @param {ExtensionConfigMutationOpts} opts
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async removeExtension(extName, {write = true} = {}) {
|
||||
|
||||
@@ -94,7 +94,7 @@ export class ArgSpec {
|
||||
* The _constructor_ is private. Use {@link ArgSpec.create} instead.
|
||||
* @private
|
||||
* @param {string} name
|
||||
* @param {ArgSpecOptions<D>} [opts]
|
||||
* @param {ArgSpecOptions<D>} opts
|
||||
*/
|
||||
constructor(name, {extType, extName, dest, defaultValue} = {}) {
|
||||
// we must normalize the extension name to fit into our convention for CLI
|
||||
|
||||
@@ -41,7 +41,9 @@ export class RoachHotelMap extends Map {
|
||||
/**
|
||||
* Extensions that an extension schema file can have.
|
||||
*/
|
||||
export const ALLOWED_SCHEMA_EXTENSIONS = new Set(['.json', '.js', '.cjs']);
|
||||
export const ALLOWED_SCHEMA_EXTENSIONS = Object.freeze(
|
||||
new Set(/** @type {AllowedSchemaExtension[]} */ (['.json', '.js', '.cjs']))
|
||||
);
|
||||
|
||||
const SCHEMA_KEY = '$schema';
|
||||
|
||||
@@ -57,44 +59,39 @@ class AppiumSchema {
|
||||
* An "argument" is a CLI argument or a config property.
|
||||
*
|
||||
* Used to provide easy lookups of argument metadata when converting between different representations of those arguments.
|
||||
* @private
|
||||
* @type {RoachHotelMap<string,ArgSpec>}
|
||||
*/
|
||||
_argSpecs = new RoachHotelMap();
|
||||
#argSpecs = new RoachHotelMap();
|
||||
|
||||
/**
|
||||
* A map of extension types to extension names to schema objects.
|
||||
*
|
||||
* This data structure is used to ensure there are no naming conflicts. The schemas
|
||||
* are stored here in memory until the instance is _finalized_.
|
||||
* @private
|
||||
* @type {Record<ExtensionType,Map<string,SchemaObject>>}
|
||||
*/
|
||||
_registeredSchemas = {[DRIVER_TYPE]: new Map(), [PLUGIN_TYPE]: new Map()};
|
||||
#registeredSchemas = {[DRIVER_TYPE]: new Map(), [PLUGIN_TYPE]: new Map()};
|
||||
|
||||
/**
|
||||
* Ajv instance
|
||||
*
|
||||
* @private
|
||||
* @type {Ajv}
|
||||
*/
|
||||
_ajv;
|
||||
#ajv;
|
||||
|
||||
/**
|
||||
* Singleton instance.
|
||||
* @private
|
||||
* @type {AppiumSchema}
|
||||
*/
|
||||
static _instance;
|
||||
static #instance;
|
||||
|
||||
/**
|
||||
* Lookup of schema IDs to finalized schemas.
|
||||
*
|
||||
* This does not include references, but rather the root schemas themselves.
|
||||
* @private
|
||||
* @type {Record<string,StrictSchemaObject>?}
|
||||
*/
|
||||
_finalizedSchemas = null;
|
||||
#finalizedSchemas = null;
|
||||
|
||||
/**
|
||||
* Initializes Ajv, adds standard formats and our custom keywords.
|
||||
@@ -102,7 +99,7 @@ class AppiumSchema {
|
||||
* @private
|
||||
*/
|
||||
constructor() {
|
||||
this._ajv = AppiumSchema._instantiateAjv();
|
||||
this.#ajv = AppiumSchema._instantiateAjv();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,9 +110,9 @@ class AppiumSchema {
|
||||
* @returns {AppiumSchema}
|
||||
*/
|
||||
static create() {
|
||||
if (!AppiumSchema._instance) {
|
||||
if (!AppiumSchema.#instance) {
|
||||
const instance = new AppiumSchema();
|
||||
AppiumSchema._instance = instance;
|
||||
AppiumSchema.#instance = instance;
|
||||
_.bindAll(instance, [
|
||||
'finalize',
|
||||
'flatten',
|
||||
@@ -133,7 +130,7 @@ class AppiumSchema {
|
||||
]);
|
||||
}
|
||||
|
||||
return AppiumSchema._instance;
|
||||
return AppiumSchema.#instance;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,7 +142,7 @@ class AppiumSchema {
|
||||
* @returns {boolean} If registered
|
||||
*/
|
||||
hasRegisteredSchema(extType, extName) {
|
||||
return this._registeredSchemas[extType].has(extName);
|
||||
return this.#registeredSchemas[extType].has(extName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,11 +151,11 @@ class AppiumSchema {
|
||||
* @returns {boolean} If finalized
|
||||
*/
|
||||
isFinalized() {
|
||||
return Boolean(this._finalizedSchemas);
|
||||
return Boolean(this.#finalizedSchemas);
|
||||
}
|
||||
|
||||
getAllArgSpecs() {
|
||||
return this._argSpecs;
|
||||
return this.#argSpecs;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,10 +179,10 @@ class AppiumSchema {
|
||||
*/
|
||||
finalize() {
|
||||
if (this.isFinalized()) {
|
||||
return /** @type {NonNullable<typeof this._finalizedSchemas>} */ (this._finalizedSchemas);
|
||||
return /** @type {Record<string,StrictSchemaObject>} */ (this.#finalizedSchemas);
|
||||
}
|
||||
|
||||
const ajv = this._ajv;
|
||||
const ajv = this.#ajv;
|
||||
|
||||
// Ajv will _mutate_ the schema, so we need to clone it.
|
||||
const baseSchema = _.cloneDeep(AppiumConfigJsonSchema);
|
||||
@@ -205,7 +202,7 @@ class AppiumSchema {
|
||||
extName,
|
||||
});
|
||||
const {arg} = argSpec;
|
||||
this._argSpecs.set(arg, argSpec);
|
||||
this.#argSpecs.set(arg, argSpec);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -217,7 +214,7 @@ class AppiumSchema {
|
||||
const finalizedSchemas = {};
|
||||
|
||||
const finalSchema = _.reduce(
|
||||
this._registeredSchemas,
|
||||
this.#registeredSchemas,
|
||||
/**
|
||||
* @param {typeof baseSchema} baseSchema
|
||||
* @param {Map<string,SchemaObject>} extensionSchemas
|
||||
@@ -246,7 +243,7 @@ class AppiumSchema {
|
||||
finalizedSchemas[APPIUM_CONFIG_SCHEMA_ID] = finalSchema;
|
||||
ajv.validateSchema(finalSchema, true);
|
||||
|
||||
this._finalizedSchemas = finalizedSchemas;
|
||||
this.#finalizedSchemas = finalizedSchemas;
|
||||
return Object.freeze(finalizedSchemas);
|
||||
}
|
||||
|
||||
@@ -283,18 +280,18 @@ class AppiumSchema {
|
||||
* @returns {void}
|
||||
*/
|
||||
reset() {
|
||||
for (const schemaId of Object.keys(this._finalizedSchemas ?? {})) {
|
||||
this._ajv.removeSchema(schemaId);
|
||||
for (const schemaId of Object.keys(this.#finalizedSchemas ?? {})) {
|
||||
this.#ajv.removeSchema(schemaId);
|
||||
}
|
||||
this._argSpecs = new RoachHotelMap();
|
||||
this._registeredSchemas = {
|
||||
this.#argSpecs = new RoachHotelMap();
|
||||
this.#registeredSchemas = {
|
||||
[DRIVER_TYPE]: new Map(),
|
||||
[PLUGIN_TYPE]: new Map(),
|
||||
};
|
||||
this._finalizedSchemas = null;
|
||||
this.#finalizedSchemas = null;
|
||||
|
||||
// Ajv seems to have an over-eager cache, so we have to dump the object entirely.
|
||||
this._ajv = AppiumSchema._instantiateAjv();
|
||||
this.#ajv = AppiumSchema._instantiateAjv();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -318,14 +315,14 @@ class AppiumSchema {
|
||||
}
|
||||
const normalizedExtName = _.kebabCase(extName);
|
||||
if (this.hasRegisteredSchema(extType, normalizedExtName)) {
|
||||
if (_.isEqual(this._registeredSchemas[extType].get(normalizedExtName), schema)) {
|
||||
if (_.isEqual(this.#registeredSchemas[extType].get(normalizedExtName), schema)) {
|
||||
return;
|
||||
}
|
||||
throw new SchemaNameConflictError(extType, extName);
|
||||
}
|
||||
this._ajv.validateSchema(schema, true);
|
||||
this.#ajv.validateSchema(schema, true);
|
||||
|
||||
this._registeredSchemas[extType].set(normalizedExtName, schema);
|
||||
this.#registeredSchemas[extType].set(normalizedExtName, schema);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -336,7 +333,7 @@ class AppiumSchema {
|
||||
* @returns {ArgSpec|undefined} ArgSpec or `undefined` if not found
|
||||
*/
|
||||
getArgSpec(name, extType, extName) {
|
||||
return this._argSpecs.get(ArgSpec.toArg(name, extType, extName));
|
||||
return this.#argSpecs.get(ArgSpec.toArg(name, extType, extName));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -347,7 +344,7 @@ class AppiumSchema {
|
||||
* @returns {boolean} `true` if such an {@link ArgSpec} exists
|
||||
*/
|
||||
hasArgSpec(name, extType, extName) {
|
||||
return this._argSpecs.has(ArgSpec.toArg(name, extType, extName));
|
||||
return this.#argSpecs.has(ArgSpec.toArg(name, extType, extName));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -391,7 +388,7 @@ class AppiumSchema {
|
||||
|
||||
/** @type {DefaultValues<Flattened>} */
|
||||
const retval = {};
|
||||
return [...this._argSpecs.values()].reduce(reducer, retval);
|
||||
return [...this.#argSpecs.values()].reduce(reducer, retval);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -405,7 +402,7 @@ class AppiumSchema {
|
||||
if (!this.isFinalized()) {
|
||||
throw new SchemaFinalizationError();
|
||||
}
|
||||
const specs = [...this._argSpecs.values()].filter(
|
||||
const specs = [...this.#argSpecs.values()].filter(
|
||||
(spec) => spec.extType === extType && spec.extName === extName
|
||||
);
|
||||
return specs.reduce((defaults, {defaultValue, rawDest}) => {
|
||||
@@ -506,7 +503,7 @@ class AppiumSchema {
|
||||
* @returns {import('ajv').ValidateFunction}
|
||||
*/
|
||||
_getValidator(id = APPIUM_CONFIG_SCHEMA_ID) {
|
||||
const validator = this._ajv.getSchema(id);
|
||||
const validator = this.#ajv.getSchema(id);
|
||||
if (!validator) {
|
||||
if (id === APPIUM_CONFIG_SCHEMA_ID) {
|
||||
throw new SchemaFinalizationError();
|
||||
@@ -532,11 +529,13 @@ class AppiumSchema {
|
||||
|
||||
/**
|
||||
* Returns `true` if `filename`'s file extension is allowed (in {@link ALLOWED_SCHEMA_EXTENSIONS}).
|
||||
* @param {string} filename
|
||||
* @param {import('type-fest').LiteralUnion<AllowedSchemaExtension, string>} filename
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static isAllowedSchemaFileExtension(filename) {
|
||||
return ALLOWED_SCHEMA_EXTENSIONS.has(path.extname(filename));
|
||||
return ALLOWED_SCHEMA_EXTENSIONS.has(
|
||||
/** @type {AllowedSchemaExtension} */ (path.extname(filename))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -720,3 +719,7 @@ export const {isAllowedSchemaFileExtension} = AppiumSchema;
|
||||
* @template {boolean|undefined} Flattened
|
||||
* @typedef {Record<string,Flattened extends true ? ArgSpecDefaultValue : ArgSpecDefaultValue | NestedArgSpecDefaultValue>} DefaultValues
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {'.json'|'.js'|'.cjs'} AllowedSchemaExtension
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "appium",
|
||||
"version": "2.0.0-beta.54",
|
||||
"version": "2.0.0-beta.56",
|
||||
"description": "Automation for Apps.",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -59,12 +59,12 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/base-driver": "^9.3.1",
|
||||
"@appium/base-plugin": "^2.2.1",
|
||||
"@appium/docutils": "^0.2.0",
|
||||
"@appium/schema": "^0.2.4",
|
||||
"@appium/support": "^3.1.5",
|
||||
"@appium/types": "^0.9.1",
|
||||
"@appium/base-driver": "^9.3.2",
|
||||
"@appium/base-plugin": "^2.2.2",
|
||||
"@appium/docutils": "^0.2.2",
|
||||
"@appium/schema": "^0.2.5",
|
||||
"@appium/support": "^3.1.6",
|
||||
"@appium/types": "^0.10.0",
|
||||
"@sidvind/better-ajv-errors": "2.1.0",
|
||||
"@types/argparse": "2.0.10",
|
||||
"@types/bluebird": "3.5.38",
|
||||
@@ -77,7 +77,7 @@
|
||||
"argparse": "2.0.1",
|
||||
"async-lock": "1.4.0",
|
||||
"asyncbox": "2.9.4",
|
||||
"axios": "1.2.3",
|
||||
"axios": "1.3.4",
|
||||
"bluebird": "3.7.2",
|
||||
"cross-env": "7.0.3",
|
||||
"find-up": "5.0.0",
|
||||
@@ -92,7 +92,7 @@
|
||||
"semver": "7.3.8",
|
||||
"source-map-support": "0.5.21",
|
||||
"teen_process": "2.0.2",
|
||||
"type-fest": "3.5.6",
|
||||
"type-fest": "3.6.0",
|
||||
"winston": "3.8.2",
|
||||
"wrap-ansi": "7.0.0",
|
||||
"yaml": "2.2.1"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"webdriverio": "7.30.0"
|
||||
"webdriverio": "7.30.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,35 +88,32 @@ describe('FakeDriver via HTTP', function () {
|
||||
}
|
||||
|
||||
FakeDriver = driverConfig.require('fake');
|
||||
// then start server if we need to
|
||||
await serverStart(port, {appiumHome});
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
await serverClose();
|
||||
await fs.rimraf(appiumHome);
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} port
|
||||
* @param {Partial<import('appium/types').ParsedArgs>} [args]
|
||||
*/
|
||||
async function serverStart(port, args = {}) {
|
||||
args = {...args, port, address: TEST_HOST};
|
||||
if (shouldStartServer) {
|
||||
server = await appiumServer(args);
|
||||
}
|
||||
}
|
||||
|
||||
async function serverClose() {
|
||||
if (server) {
|
||||
await server.close();
|
||||
}
|
||||
function withServer(args = {}) {
|
||||
before(async function () {
|
||||
args = {...args, appiumHome, port, address: TEST_HOST};
|
||||
if (shouldStartServer) {
|
||||
server = await appiumServer(args);
|
||||
}
|
||||
});
|
||||
after(async function () {
|
||||
if (server) {
|
||||
await server.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
describe('server updating', function () {
|
||||
withServer();
|
||||
it('should allow drivers to update the server in arbitrary ways', async function () {
|
||||
const {data} = await axios.get(`${testServerBaseUrl}/fakedriver`);
|
||||
data.should.eql({fakedriver: 'fakeResponse'});
|
||||
@@ -132,6 +129,7 @@ describe('FakeDriver via HTTP', function () {
|
||||
});
|
||||
|
||||
describe('cli args handling for empty args', function () {
|
||||
withServer();
|
||||
it('should not receive user cli args if none passed in', async function () {
|
||||
let driver = await wdio({...wdOpts, capabilities: caps});
|
||||
const {sessionId} = driver;
|
||||
@@ -146,16 +144,7 @@ describe('FakeDriver via HTTP', function () {
|
||||
});
|
||||
|
||||
describe('cli args handling for passed in args', function () {
|
||||
before(async function () {
|
||||
await serverClose();
|
||||
await serverStart(port, {appiumHome, ...FAKE_DRIVER_ARGS});
|
||||
});
|
||||
after(async function () {
|
||||
await serverClose();
|
||||
// this weirdness here is to restart the same server which was originally started in the parent suite's
|
||||
// "before all" hook. another way of doing this would be to just...not nest suites this way.
|
||||
await serverStart(port, {appiumHome});
|
||||
});
|
||||
withServer(FAKE_DRIVER_ARGS);
|
||||
it('should receive user cli args from a driver if arguments were passed in', async function () {
|
||||
let driver = await wdio({...wdOpts, capabilities: caps});
|
||||
const {sessionId} = driver;
|
||||
@@ -169,7 +158,46 @@ describe('FakeDriver via HTTP', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('default capabilities via cli', function () {
|
||||
withServer({
|
||||
defaultCapabilities: {
|
||||
'appium:options': {
|
||||
automationName: 'Fake',
|
||||
deviceName: 'Fake',
|
||||
app: TEST_FAKE_APP,
|
||||
},
|
||||
platformName: 'Fake',
|
||||
},
|
||||
});
|
||||
it('should allow appium-prefixed caps sent via appium:options through --default-capabilities', async function () {
|
||||
const appiumOptsCaps = {
|
||||
capabilities: {
|
||||
alwaysMatch: {},
|
||||
firstMatch: [{}],
|
||||
},
|
||||
};
|
||||
|
||||
// Create the session
|
||||
const {value} = (await axios.post(testServerBaseSessionUrl, appiumOptsCaps)).data;
|
||||
try {
|
||||
value.sessionId.should.be.a.string;
|
||||
value.should.exist;
|
||||
value.capabilities.should.deep.equal({
|
||||
automationName: 'Fake',
|
||||
platformName: 'Fake',
|
||||
deviceName: 'Fake',
|
||||
app: TEST_FAKE_APP,
|
||||
});
|
||||
} finally {
|
||||
// End session
|
||||
await axios.delete(`${testServerBaseSessionUrl}/${value.sessionId}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('session handling', function () {
|
||||
withServer();
|
||||
|
||||
it('should start and stop a session and not allow commands after session stopped', async function () {
|
||||
let driver = await wdio({...wdOpts, capabilities: caps});
|
||||
should.exist(driver.sessionId);
|
||||
|
||||
@@ -3,6 +3,28 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [9.3.2](https://github.com/appium/appium/compare/@appium/base-driver@9.3.1...@appium/base-driver@9.3.2) (2023-02-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* appium:options should work via --default-capabilities ([11e7ad0](https://github.com/appium/appium/commit/11e7ad0cd403ab1dc100f581cdf93772e3449db3)), closes [#18191](https://github.com/appium/appium/issues/18191)
|
||||
* **base-driver:** fix type problem w/r/t axios headers ([61a0275](https://github.com/appium/appium/commit/61a0275e29e7a705785282415fd670a3ad617f18))
|
||||
* **base-driver:** update dependency body-parser to v1.20.2 ([1cb01a5](https://github.com/appium/appium/commit/1cb01a5e85e7aee54ce1edc751e104138852d59a))
|
||||
* **images-plugin:** update dependency lru-cache to v7.15.0 ([4cf8dbc](https://github.com/appium/appium/commit/4cf8dbc0d34769e3167bceed65facabe71b9cbde))
|
||||
* **images-plugin:** update dependency lru-cache to v7.16.0 ([d54901a](https://github.com/appium/appium/commit/d54901a9c3982dd5595ffd54010e5029b60c4754))
|
||||
* **images-plugin:** update dependency lru-cache to v7.16.1 ([2fa6bf1](https://github.com/appium/appium/commit/2fa6bf1e11e66ba8256d0641e345359bd108bc7d))
|
||||
* **images-plugin:** update dependency lru-cache to v7.16.2 ([4b10322](https://github.com/appium/appium/commit/4b10322b80e0032dd4585ac7766edd1ddf798139))
|
||||
* **images-plugin:** update dependency lru-cache to v7.17.0 ([eb73be8](https://github.com/appium/appium/commit/eb73be8303c517dbaa965ae99bac3381522d939a))
|
||||
* **support:** update dependency axios to v1.3.4 ([49f157d](https://github.com/appium/appium/commit/49f157d63e3bdbd205527a5dc8f997df68540546))
|
||||
* **types:** update dependency type-fest to v3.5.7 ([b4416c5](https://github.com/appium/appium/commit/b4416c5c0f40200b36909a1fbb492d8c4a212108))
|
||||
* **types:** update dependency type-fest to v3.6.0 ([08a6f3a](https://github.com/appium/appium/commit/08a6f3a308c7ee162e992629888557b31e50a26e))
|
||||
* update axios to v1.3.3 ([8f9de63](https://github.com/appium/appium/commit/8f9de63e4a622712db545ab63f9f4ce6654e4a91))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [9.3.1](https://github.com/appium/appium/compare/@appium/base-driver@9.3.0...@appium/base-driver@9.3.1) (2023-02-09)
|
||||
|
||||
|
||||
|
||||
@@ -333,6 +333,68 @@ function processCapabilities(
|
||||
return /** @type {Capabilities<C>} */ (matchedCaps ?? {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a copy of a "bare" (single-level, non-W3C) capabilities object which has taken everything
|
||||
* within the 'appium:options' capability and promoted it to the top level.
|
||||
*
|
||||
* @template {Constraints} C
|
||||
* @param {NSCapabilities<C>} obj
|
||||
* @return {NSCapabilities<C>} the capabilities with 'options' promoted if necessary
|
||||
*/
|
||||
function promoteAppiumOptionsForObject(obj) {
|
||||
const appiumOptions = obj[PREFIXED_APPIUM_OPTS_CAP];
|
||||
if (!appiumOptions) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (!_.isPlainObject(appiumOptions)) {
|
||||
throw new errors.SessionNotCreatedError(
|
||||
`The ${PREFIXED_APPIUM_OPTS_CAP} capability must be an object`
|
||||
);
|
||||
}
|
||||
if (_.isEmpty(appiumOptions)) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
log.debug(
|
||||
`Found ${PREFIXED_APPIUM_OPTS_CAP} capability present; will promote items inside to caps`
|
||||
);
|
||||
|
||||
/**
|
||||
* @param {string} capName
|
||||
*/
|
||||
const shouldAddVendorPrefix = (capName) => !capName.startsWith(APPIUM_VENDOR_PREFIX);
|
||||
const verifyIfAcceptable = (/** @type {string} */ capName) => {
|
||||
if (!_.isString(capName)) {
|
||||
throw new errors.SessionNotCreatedError(
|
||||
`Capability names in ${PREFIXED_APPIUM_OPTS_CAP} must be strings. '${capName}' is unexpected`
|
||||
);
|
||||
}
|
||||
if (isStandardCap(capName)) {
|
||||
throw new errors.SessionNotCreatedError(
|
||||
`${PREFIXED_APPIUM_OPTS_CAP} must only contain vendor-specific capabilties. '${capName}' is unexpected`
|
||||
);
|
||||
}
|
||||
return capName;
|
||||
};
|
||||
const preprocessedOptions = _(appiumOptions)
|
||||
.mapKeys((value, /** @type {string} */ key) => verifyIfAcceptable(key))
|
||||
.mapKeys((value, key) => (shouldAddVendorPrefix(key) ? `${APPIUM_VENDOR_PREFIX}${key}` : key))
|
||||
.value();
|
||||
// warn if we are going to overwrite any keys on the base caps object
|
||||
const overwrittenKeys = _.intersection(Object.keys(obj), Object.keys(preprocessedOptions));
|
||||
if (overwrittenKeys.length > 0) {
|
||||
log.warn(
|
||||
`Found capabilities inside ${PREFIXED_APPIUM_OPTS_CAP} that will overwrite ` +
|
||||
`capabilities at the top level: ${JSON.stringify(overwrittenKeys)}`
|
||||
);
|
||||
}
|
||||
return _.cloneDeep({
|
||||
.../** @type {NSCapabilities<C>} */ (_.omit(obj, PREFIXED_APPIUM_OPTS_CAP)),
|
||||
...preprocessedOptions,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a copy of a capabilities object which has taken everything within the 'options'
|
||||
* capability and promoted it to the top level.
|
||||
@@ -342,69 +404,15 @@ function processCapabilities(
|
||||
* @return {import('@appium/types').W3CCapabilities<C>} the capabilities with 'options' promoted if necessary
|
||||
*/
|
||||
function promoteAppiumOptions(originalCaps) {
|
||||
const promoteForObject = (obj) => {
|
||||
const appiumOptions = obj[PREFIXED_APPIUM_OPTS_CAP];
|
||||
if (!appiumOptions) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (!_.isPlainObject(appiumOptions)) {
|
||||
throw new errors.SessionNotCreatedError(
|
||||
`The ${PREFIXED_APPIUM_OPTS_CAP} capability must be an object`
|
||||
);
|
||||
}
|
||||
if (_.isEmpty(appiumOptions)) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
log.debug(
|
||||
`Found ${PREFIXED_APPIUM_OPTS_CAP} capability present; will promote items inside to caps`
|
||||
);
|
||||
|
||||
/**
|
||||
* @param {string} capName
|
||||
*/
|
||||
const shouldAddVendorPrefix = (capName) => !capName.startsWith(APPIUM_VENDOR_PREFIX);
|
||||
const verifyIfAcceptable = (capName) => {
|
||||
if (!_.isString(capName)) {
|
||||
throw new errors.SessionNotCreatedError(
|
||||
`Capability names in ${PREFIXED_APPIUM_OPTS_CAP} must be strings. '${capName}' is unexpected`
|
||||
);
|
||||
}
|
||||
if (isStandardCap(capName)) {
|
||||
throw new errors.SessionNotCreatedError(
|
||||
`${PREFIXED_APPIUM_OPTS_CAP} must only contain vendor-specific capabilties. '${capName}' is unexpected`
|
||||
);
|
||||
}
|
||||
return capName;
|
||||
};
|
||||
const preprocessedOptions = _(appiumOptions)
|
||||
.mapKeys((value, key) => verifyIfAcceptable(key))
|
||||
.mapKeys((value, key) => (shouldAddVendorPrefix(key) ? `${APPIUM_VENDOR_PREFIX}${key}` : key))
|
||||
.value();
|
||||
// warn if we are going to overwrite any keys on the base caps object
|
||||
const overwrittenKeys = _.intersection(Object.keys(obj), Object.keys(preprocessedOptions));
|
||||
if (overwrittenKeys.length > 0) {
|
||||
log.warn(
|
||||
`Found capabilities inside ${PREFIXED_APPIUM_OPTS_CAP} that will overwrite ` +
|
||||
`capabilities at the top level: ${JSON.stringify(overwrittenKeys)}`
|
||||
);
|
||||
}
|
||||
return _.cloneDeep({
|
||||
..._.omit(obj, PREFIXED_APPIUM_OPTS_CAP),
|
||||
...preprocessedOptions,
|
||||
});
|
||||
};
|
||||
|
||||
const {alwaysMatch, firstMatch} = originalCaps;
|
||||
const result = {};
|
||||
const {alwaysMatch, firstMatch} = originalCaps;
|
||||
if (_.isPlainObject(alwaysMatch)) {
|
||||
result.alwaysMatch = promoteForObject(alwaysMatch);
|
||||
result.alwaysMatch = promoteAppiumOptionsForObject(alwaysMatch);
|
||||
} else if ('alwaysMatch' in originalCaps) {
|
||||
result.alwaysMatch = alwaysMatch;
|
||||
}
|
||||
if (_.isArray(firstMatch)) {
|
||||
result.firstMatch = firstMatch.map(promoteForObject);
|
||||
result.firstMatch = firstMatch.map(promoteAppiumOptionsForObject);
|
||||
} else if ('firstMatch' in originalCaps) {
|
||||
result.firstMatch = firstMatch;
|
||||
}
|
||||
@@ -421,6 +429,7 @@ export {
|
||||
isStandardCap,
|
||||
stripAppiumPrefixes,
|
||||
promoteAppiumOptions,
|
||||
promoteAppiumOptionsForObject,
|
||||
PREFIXED_APPIUM_OPTS_CAP,
|
||||
};
|
||||
|
||||
@@ -431,6 +440,11 @@ export {
|
||||
* @typedef {import('@appium/types').BaseDriverCapConstraints} BaseDriverCapConstraints
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Constraints} C
|
||||
* @typedef {import('@appium/types').ConstraintsToCaps<C>} ConstraintsToCaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef ValidateCapsOpts
|
||||
* @property {boolean} [skipPresenceConstraint] - if true, skip the presence constraint
|
||||
|
||||
@@ -35,7 +35,7 @@ class DriverCore {
|
||||
/**
|
||||
* @type {string?}
|
||||
*/
|
||||
sessionId = null;
|
||||
sessionId;
|
||||
|
||||
/**
|
||||
* @type {import('@appium/types').DriverOpts<C>}
|
||||
@@ -47,7 +47,8 @@ class DriverCore {
|
||||
*/
|
||||
initialOpts;
|
||||
|
||||
helpers = helpers;
|
||||
/** @type {typeof helpers} */
|
||||
helpers;
|
||||
|
||||
/**
|
||||
* basePath is used for several purposes, for example in setting up
|
||||
@@ -56,39 +57,43 @@ class DriverCore {
|
||||
* initially but it is automatically updated during any actual program
|
||||
* execution by the routeConfiguringFunction, which is necessarily run as
|
||||
* the entrypoint for any Appium server
|
||||
* @type {string}
|
||||
*/
|
||||
basePath = DEFAULT_BASE_PATH;
|
||||
basePath;
|
||||
|
||||
relaxedSecurityEnabled = false;
|
||||
/** @type {boolean} */
|
||||
relaxedSecurityEnabled;
|
||||
|
||||
/** @type {string[]} */
|
||||
allowInsecure = [];
|
||||
allowInsecure;
|
||||
|
||||
/** @type {string[]} */
|
||||
denyInsecure = [];
|
||||
denyInsecure;
|
||||
|
||||
newCommandTimeoutMs = NEW_COMMAND_TIMEOUT_MS;
|
||||
/** @type {number} */
|
||||
newCommandTimeoutMs;
|
||||
|
||||
implicitWaitMs = 0;
|
||||
/** @type {number} */
|
||||
implicitWaitMs;
|
||||
|
||||
/** @type {string[]} */
|
||||
locatorStrategies = [];
|
||||
locatorStrategies;
|
||||
|
||||
/** @type {string[]} */
|
||||
webLocatorStrategies = [];
|
||||
webLocatorStrategies;
|
||||
|
||||
/** @type {Driver[]} */
|
||||
managedDrivers = [];
|
||||
managedDrivers;
|
||||
|
||||
/** @type {NodeJS.Timeout?} */
|
||||
noCommandTimer = null;
|
||||
noCommandTimer;
|
||||
|
||||
/** @type {EventHistory} */
|
||||
_eventHistory = {commands: []};
|
||||
_eventHistory;
|
||||
|
||||
// used to handle driver events
|
||||
/** @type {NodeJS.EventEmitter} */
|
||||
eventEmitter = new EventEmitter();
|
||||
eventEmitter;
|
||||
|
||||
/**
|
||||
* @type {AppiumLogger}
|
||||
@@ -97,8 +102,9 @@ class DriverCore {
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @type {boolean}
|
||||
*/
|
||||
shutdownUnexpectedly = false;
|
||||
shutdownUnexpectedly;
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
@@ -107,16 +113,18 @@ class DriverCore {
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @type {AsyncLock}
|
||||
*/
|
||||
commandsQueueGuard = new AsyncLock();
|
||||
commandsQueueGuard;
|
||||
|
||||
/**
|
||||
* settings should be instantiated by drivers which extend BaseDriver, but
|
||||
* we set it to an empty DeviceSettings instance here to make sure that the
|
||||
* default settings are applied even if an extending driver doesn't utilize
|
||||
* the settings functionality itself
|
||||
* @type {DeviceSettings}
|
||||
*/
|
||||
settings = new DeviceSettings();
|
||||
settings;
|
||||
|
||||
/**
|
||||
* @param {DriverOpts<C>} opts
|
||||
@@ -139,6 +147,22 @@ class DriverCore {
|
||||
this.initialOpts = _.cloneDeep(opts);
|
||||
|
||||
this.sessionId = null;
|
||||
this.helpers = helpers;
|
||||
this.basePath = DEFAULT_BASE_PATH;
|
||||
this.relaxedSecurityEnabled = false;
|
||||
this.allowInsecure = [];
|
||||
this.denyInsecure = [];
|
||||
this.newCommandTimeoutMs = NEW_COMMAND_TIMEOUT_MS;
|
||||
this.implicitWaitMs = 0;
|
||||
this.locatorStrategies = [];
|
||||
this.webLocatorStrategies = [];
|
||||
this.managedDrivers = [];
|
||||
this.noCommandTimer = null;
|
||||
this._eventHistory = {commands: []};
|
||||
this.eventEmitter = new EventEmitter();
|
||||
this.shutdownUnexpectedly = false;
|
||||
this.commandsQueueGuard = new AsyncLock();
|
||||
this.settings = new DeviceSettings();
|
||||
}
|
||||
|
||||
get log() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable require-await */
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
import {validateCaps, PREFIXED_APPIUM_OPTS_CAP, processCapabilities} from './capabilities';
|
||||
import {validateCaps, processCapabilities} from './capabilities';
|
||||
import {DriverCore} from './core';
|
||||
import {util} from '@appium/support';
|
||||
import B from 'bluebird';
|
||||
|
||||
@@ -52,17 +52,22 @@ process.on('exit', () => {
|
||||
}
|
||||
});
|
||||
|
||||
async function retrieveHeaders(link) {
|
||||
/**
|
||||
*
|
||||
* @param {string} url
|
||||
* @returns {Promise<import('axios').AxiosResponse['headers']>}
|
||||
*/
|
||||
async function retrieveHeaders(url) {
|
||||
try {
|
||||
return (
|
||||
await axios({
|
||||
url: link,
|
||||
url,
|
||||
method: 'HEAD',
|
||||
timeout: 5000,
|
||||
})
|
||||
).headers;
|
||||
} catch (e) {
|
||||
logger.info(`Cannot send HEAD request to '${link}'. Original error: ${e.message}`);
|
||||
logger.info(`Cannot send HEAD request to '${url}'. Original error: ${e.message}`);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -228,6 +233,7 @@ async function configureApp(app, options = /** @type {ConfigureAppOptions} */ ({
|
||||
let newApp = app;
|
||||
let shouldUnzipApp = false;
|
||||
let packageHash = null;
|
||||
/** @type {import('axios').AxiosResponse['headers']?} */
|
||||
let headers = null;
|
||||
/** @type {RemoteAppProps} */
|
||||
const remoteAppProps = {
|
||||
|
||||
@@ -35,6 +35,7 @@ export {
|
||||
isStandardCap,
|
||||
validateCaps,
|
||||
promoteAppiumOptions,
|
||||
promoteAppiumOptionsForObject,
|
||||
} from './basedriver/capabilities';
|
||||
|
||||
// Web socket helpers
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/base-driver",
|
||||
"version": "9.3.1",
|
||||
"version": "9.3.2",
|
||||
"description": "Base driver class for Appium drivers",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -43,8 +43,8 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^3.1.5",
|
||||
"@appium/types": "^0.9.1",
|
||||
"@appium/support": "^3.1.6",
|
||||
"@appium/types": "^0.10.0",
|
||||
"@colors/colors": "1.5.0",
|
||||
"@types/async-lock": "1.4.0",
|
||||
"@types/bluebird": "3.5.38",
|
||||
@@ -53,19 +53,19 @@
|
||||
"@types/serve-favicon": "2.5.3",
|
||||
"async-lock": "1.4.0",
|
||||
"asyncbox": "2.9.4",
|
||||
"axios": "1.2.3",
|
||||
"axios": "1.3.4",
|
||||
"bluebird": "3.7.2",
|
||||
"body-parser": "1.20.1",
|
||||
"body-parser": "1.20.2",
|
||||
"es6-error": "4.1.1",
|
||||
"express": "4.18.2",
|
||||
"http-status-codes": "2.2.0",
|
||||
"lodash": "4.17.21",
|
||||
"lru-cache": "7.14.1",
|
||||
"lru-cache": "7.17.0",
|
||||
"method-override": "3.0.0",
|
||||
"morgan": "1.10.0",
|
||||
"serve-favicon": "2.5.0",
|
||||
"source-map-support": "0.5.21",
|
||||
"type-fest": "3.5.6",
|
||||
"type-fest": "3.6.0",
|
||||
"validate.js": "0.13.1"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.2.2](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.1...@appium/base-plugin@2.2.2) (2023-02-24)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.1](https://github.com/appium/appium/compare/@appium/base-plugin@2.2.0...@appium/base-plugin@2.2.1) (2023-02-09)
|
||||
|
||||
**Note:** Version bump only for package @appium/base-plugin
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/base-plugin",
|
||||
"version": "2.2.1",
|
||||
"version": "2.2.2",
|
||||
"description": "The base plugin used to create Appium 2.0 plugins",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -40,8 +40,8 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/base-driver": "^9.3.1",
|
||||
"@appium/support": "^3.1.5"
|
||||
"@appium/base-driver": "^9.3.2",
|
||||
"@appium/support": "^3.1.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.0.8](https://github.com/appium/appium/compare/@appium/doctor@2.0.7...@appium/doctor@2.0.8) (2023-02-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docutils:** update dependency yargs to v17.7.0 ([70d65b7](https://github.com/appium/appium/commit/70d65b7d6230dd415f26aaba6d097a3cbe876b47))
|
||||
* **docutils:** update dependency yargs to v17.7.1 ([2602016](https://github.com/appium/appium/commit/2602016eb38c73d2d9a40f94e95277faf2444305))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.7](https://github.com/appium/appium/compare/@appium/doctor@2.0.6...@appium/doctor@2.0.7) (2023-02-09)
|
||||
|
||||
**Note:** Version bump only for package @appium/doctor
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/doctor",
|
||||
"version": "2.0.7",
|
||||
"version": "2.0.8",
|
||||
"description": "Test environment for fitness to run Appium",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -44,7 +44,7 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^3.1.5",
|
||||
"@appium/support": "^3.1.6",
|
||||
"@colors/colors": "1.5.0",
|
||||
"@types/bluebird": "3.5.38",
|
||||
"@types/teen_process": "2.0.0",
|
||||
@@ -54,7 +54,7 @@
|
||||
"lodash": "4.17.21",
|
||||
"source-map-support": "0.5.21",
|
||||
"teen_process": "2.0.2",
|
||||
"yargs": "17.6.2"
|
||||
"yargs": "17.7.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.2.2](https://github.com/appium/appium/compare/@appium/docutils@0.2.1...@appium/docutils@0.2.2) (2023-02-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docutils:** fix typescript version ([6b8eb2c](https://github.com/appium/appium/commit/6b8eb2cbfec39185d8b81e2f4e2ce3f646ffd65f))
|
||||
* **docutils:** update dependency typedoc to v0.23.25 ([a4c58cc](https://github.com/appium/appium/commit/a4c58cc5618a9c83de4c044bf8bc59f1ddb80122))
|
||||
* **docutils:** update dependency yargs to v17.7.0 ([70d65b7](https://github.com/appium/appium/commit/70d65b7d6230dd415f26aaba6d097a3cbe876b47))
|
||||
* **docutils:** update dependency yargs to v17.7.1 ([2602016](https://github.com/appium/appium/commit/2602016eb38c73d2d9a40f94e95277faf2444305))
|
||||
* **types:** update dependency type-fest to v3.5.7 ([b4416c5](https://github.com/appium/appium/commit/b4416c5c0f40200b36909a1fbb492d8c4a212108))
|
||||
* **types:** update dependency type-fest to v3.6.0 ([08a6f3a](https://github.com/appium/appium/commit/08a6f3a308c7ee162e992629888557b31e50a26e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.2.1](https://github.com/appium/appium/compare/@appium/docutils@0.2.0...@appium/docutils@0.2.1) (2023-02-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docutils:** fix bad offset in nav updates ([ab366f3](https://github.com/appium/appium/commit/ab366f3a1f9109f7bd75ae5ac32b862d5e6dd1bf))
|
||||
* **docutils:** fix bad option name and ignore most falsy args ([5446e5c](https://github.com/appium/appium/commit/5446e5c7a755be081f46f1ed1ca8c13665d9a772))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [0.2.0](https://github.com/appium/appium/compare/@appium/docutils@0.1.6...@appium/docutils@0.2.0) (2023-02-09)
|
||||
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ async function findDeployVersion(packageJsonPath?: string, cwd = process.cwd()):
|
||||
* @param opts Options
|
||||
*/
|
||||
export async function deploy({
|
||||
mkDocsYml: mkDocsYmlPath,
|
||||
mkdocsYml: mkDocsYmlPath,
|
||||
packageJson: packageJsonPath,
|
||||
deployVersion: version,
|
||||
cwd = process.cwd(),
|
||||
@@ -111,7 +111,10 @@ export async function deploy({
|
||||
host,
|
||||
};
|
||||
if (serve) {
|
||||
const mikeArgs = [...argify(_.pickBy(mikeOpts, Boolean)), version];
|
||||
const mikeArgs = [
|
||||
...argify(_.pickBy(mikeOpts, (value) => _.isNumber(value) || Boolean(value))),
|
||||
version,
|
||||
];
|
||||
if (alias) {
|
||||
mikeArgs.push(alias);
|
||||
}
|
||||
@@ -120,7 +123,10 @@ export async function deploy({
|
||||
} else {
|
||||
const mikeArgs = [
|
||||
...argify(
|
||||
_.omitBy(mikeOpts, (value, key) => _.includes(['port', 'host'], key) || value === false)
|
||||
_.omitBy(
|
||||
mikeOpts,
|
||||
(value, key) => _.includes(['port', 'host'], key) || (!_.isNumber(value) && !value)
|
||||
)
|
||||
),
|
||||
version,
|
||||
];
|
||||
@@ -140,7 +146,7 @@ export interface DeployOpts {
|
||||
/**
|
||||
* Path to `mike.yml`
|
||||
*/
|
||||
mkDocsYml?: string;
|
||||
mkdocsYml?: string;
|
||||
|
||||
/**
|
||||
* Current working directory
|
||||
|
||||
@@ -170,7 +170,6 @@ function getRootHeaderKeypath(headerItems: ParsedNavData[], header: string) {
|
||||
* @param mkDocsDocsDir Configured `docs_dir` or via options
|
||||
* @param navData Nav data parsed by {@linkcode parseNav}
|
||||
* @param all If `true`, process all markdown files, not just commands
|
||||
* @returns
|
||||
*/
|
||||
async function getNavItemsForDir(
|
||||
dir: string,
|
||||
@@ -180,45 +179,52 @@ async function getNavItemsForDir(
|
||||
all = false
|
||||
) {
|
||||
let dataChanged = false;
|
||||
const newHeaderItems: Omit<ParsedNavData, 'name'>[] = [];
|
||||
const newNavHeaderItems: Omit<ParsedNavData, 'name'>[] = [];
|
||||
const referenceOutputFilepaths = await findRelativeMarkdownFiles(dir, mkDocsDocsDir);
|
||||
if (!referenceOutputFilepaths.length) {
|
||||
log.warn('No markdown files found in %s; did TypeDoc run?', dir);
|
||||
return {data: [], changed: false};
|
||||
}
|
||||
|
||||
const newRefFilepaths = await findRelativeMarkdownFiles(dir, mkDocsDocsDir);
|
||||
|
||||
const header = all ? _.startCase(path.basename(dir)) : DEFAULT_NAV_HEADER;
|
||||
|
||||
const headerItems = filterHeaderItems(navData, header);
|
||||
const navHeader = all ? _.startCase(path.basename(dir)) : DEFAULT_NAV_HEADER;
|
||||
const navHeaderItems = filterHeaderItems(navData, navHeader);
|
||||
|
||||
// if we found items with this header already, we are going
|
||||
// to replace them all wholesale
|
||||
if (headerItems.length) {
|
||||
if (navHeaderItems.length) {
|
||||
log.debug('Found %d item(s) in header %s', navHeaderItems.length, navHeader);
|
||||
// we append indices or names to this keypath
|
||||
const rootHeaderKeypath = getRootHeaderKeypath(headerItems, header);
|
||||
const rootHeaderKeypath = getRootHeaderKeypath(navHeaderItems, navHeader);
|
||||
|
||||
for (const [offset, fileOrUrl] of newRefFilepaths.entries()) {
|
||||
const data = headerItems.find((item) => item.fileOrUrl === fileOrUrl);
|
||||
const keypath = getKeypathForHeaderItem(rootHeaderKeypath, offset, data);
|
||||
newHeaderItems.push({keypath, fileOrUrl});
|
||||
for (const fileOrUrl of referenceOutputFilepaths) {
|
||||
let offset = navHeaderItems.findIndex((item) => item.fileOrUrl === fileOrUrl);
|
||||
const newOffset = offset >= 0 ? offset : navHeaderItems.length;
|
||||
const data = navHeaderItems[offset];
|
||||
log.warn(rootHeaderKeypath, newOffset, data);
|
||||
const keypath = getKeypathForHeaderItem(rootHeaderKeypath, newOffset, data);
|
||||
newNavHeaderItems.push({keypath, fileOrUrl});
|
||||
}
|
||||
|
||||
// look for any differences between what we have and what's in the file
|
||||
if (navDataDidChange(newHeaderItems, headerItems)) {
|
||||
log.debug('Will write new nav data for header %s', header);
|
||||
if (navDataDidChange(newNavHeaderItems, navHeaderItems)) {
|
||||
log.debug('Will write new nav data for header %s: %O', navHeader, newNavHeaderItems);
|
||||
dataChanged = true;
|
||||
} else {
|
||||
log.debug('No changes for header %s', header);
|
||||
log.debug('No changes for header %s', navHeader);
|
||||
}
|
||||
} else {
|
||||
log.debug('No items found in header %s', navHeader);
|
||||
let navOffset = nav.length;
|
||||
for (const [idx, newRefFilepath] of newRefFilepaths.entries()) {
|
||||
newHeaderItems.push({
|
||||
keypath: `${navOffset}.${header}.${idx}`,
|
||||
for (const [idx, newRefFilepath] of referenceOutputFilepaths.entries()) {
|
||||
newNavHeaderItems.push({
|
||||
keypath: `${navOffset}.${navHeader}.${idx}`,
|
||||
fileOrUrl: newRefFilepath,
|
||||
});
|
||||
}
|
||||
log.debug('Will create nav data for header %s', header);
|
||||
log.debug('Will create nav data for header %s', navHeader);
|
||||
dataChanged = true;
|
||||
}
|
||||
return {data: newHeaderItems, changed: dataChanged};
|
||||
return {data: newNavHeaderItems, changed: dataChanged};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,7 +11,7 @@ const NAME_GROUP_DEPLOY = 'Deployment Options:';
|
||||
const NAME_GROUP_SERVE = 'Serve Options:';
|
||||
const NAME_GROUP_BUILD_PATHS = 'Paths:';
|
||||
|
||||
const opts = {
|
||||
const opts = Object.freeze({
|
||||
reference: {
|
||||
describe: 'Run TypeDoc command API reference build (Markdown)',
|
||||
group: NAME_GROUP_BUILD,
|
||||
@@ -183,12 +183,11 @@ const opts = {
|
||||
implies: 'serve',
|
||||
defaultDescription: 'localhost',
|
||||
},
|
||||
} as const;
|
||||
}) satisfies Record<string, Options>;
|
||||
|
||||
opts as Record<string, Options>;
|
||||
type BuildOptions = InferredOptionTypes<typeof opts>;
|
||||
|
||||
const buildCommand: CommandModule<{}, BuildOptions> = {
|
||||
export default {
|
||||
command: 'build',
|
||||
describe: 'Build Appium extension documentation',
|
||||
builder: (yargs) =>
|
||||
@@ -224,6 +223,4 @@ const buildCommand: CommandModule<{}, BuildOptions> = {
|
||||
}
|
||||
log.success('Done! (total: %dms)', stop());
|
||||
},
|
||||
};
|
||||
|
||||
export default buildCommand;
|
||||
} as CommandModule<{}, BuildOptions>;
|
||||
|
||||
@@ -10,7 +10,7 @@ const NAME_GROUP_INIT_MKDOCS = 'MkDocs Config:';
|
||||
const NAME_GROUP_INIT_PATHS = 'Paths:';
|
||||
const NAME_GROUP_INIT_BEHAVIOR = 'Initialization Behavior:';
|
||||
|
||||
const opts = {
|
||||
const opts = Object.freeze({
|
||||
copyright: {
|
||||
description: 'Copyright notice',
|
||||
group: NAME_GROUP_INIT_MKDOCS,
|
||||
@@ -147,12 +147,11 @@ const opts = {
|
||||
group: NAME_GROUP_INIT_BEHAVIOR,
|
||||
type: 'boolean',
|
||||
},
|
||||
} as const;
|
||||
opts as Record<string, Options>; // type check
|
||||
}) satisfies Record<string, Options>;
|
||||
|
||||
type InitOptions = InferredOptionTypes<typeof opts>;
|
||||
|
||||
const initCommand: CommandModule<{}, InitOptions> = {
|
||||
export default {
|
||||
command: 'init',
|
||||
describe: 'Initialize package for doc generation',
|
||||
builder: opts,
|
||||
@@ -161,6 +160,4 @@ const initCommand: CommandModule<{}, InitOptions> = {
|
||||
await init({...args, overwrite: args.force, cwd: args.dir});
|
||||
log.success('Done (%dms)', done());
|
||||
},
|
||||
};
|
||||
|
||||
export default initCommand;
|
||||
} as CommandModule<{}, InitOptions>;
|
||||
|
||||
@@ -9,7 +9,7 @@ const log = logger.withTag('validate');
|
||||
const NAME_GROUP_VALIDATE = 'Validation Behavior:';
|
||||
const NAME_GROUP_VALIDATE_PATHS = 'Paths:';
|
||||
|
||||
const opts = {
|
||||
const opts = Object.freeze({
|
||||
mkdocs: {
|
||||
default: true,
|
||||
description: 'Validate MkDocs environment',
|
||||
@@ -79,10 +79,11 @@ const opts = {
|
||||
group: NAME_GROUP_VALIDATE,
|
||||
type: 'boolean',
|
||||
},
|
||||
} as const;
|
||||
opts as Record<string, Options>;
|
||||
}) satisfies Record<string, Options>;
|
||||
|
||||
type ValidateOptions = InferredOptionTypes<typeof opts>;
|
||||
const validateCommand: CommandModule<{}, ValidateOptions> = {
|
||||
|
||||
export default {
|
||||
command: 'validate',
|
||||
describe: 'Validate Environment',
|
||||
builder: opts,
|
||||
@@ -117,6 +118,4 @@ const validateCommand: CommandModule<{}, ValidateOptions> = {
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default validateCommand;
|
||||
} as CommandModule<{}, ValidateOptions>;
|
||||
|
||||
@@ -32,34 +32,34 @@ class DocutilsReporter extends FancyReporter {
|
||||
/**
|
||||
* Mapping of log types (the name of the logging method called) to chalk fg colors
|
||||
*/
|
||||
static TYPE_COLOR_MAP = {
|
||||
static readonly TYPE_COLOR_MAP = {
|
||||
info: 'cyan',
|
||||
success: 'green',
|
||||
error: 'red',
|
||||
warn: 'yellow',
|
||||
} as const;
|
||||
} satisfies {[k in LogType]?: typeof ForegroundColor};
|
||||
|
||||
/**
|
||||
* Mapping of log levels to chalk fg colors
|
||||
*/
|
||||
static LEVEL_COLORS = {
|
||||
static readonly LEVEL_COLORS = {
|
||||
0: 'red',
|
||||
1: 'yellow',
|
||||
2: 'white',
|
||||
3: 'green',
|
||||
} as const;
|
||||
} satisfies {[k in LogLevel]?: typeof ForegroundColor};
|
||||
|
||||
/**
|
||||
* Mapping of log types to icons/symbols
|
||||
*/
|
||||
static TYPE_ICONS = {
|
||||
static readonly TYPE_ICONS = {
|
||||
info: logSymbols.info,
|
||||
success: logSymbols.success,
|
||||
error: logSymbols.error,
|
||||
warn: logSymbols.warning,
|
||||
debug: figures('›'),
|
||||
trace: figures('›'),
|
||||
} as const;
|
||||
} satisfies {[k in LogType]?: string};
|
||||
|
||||
/**
|
||||
* Default color to use if we can't find a color for the log type or level
|
||||
@@ -191,8 +191,3 @@ function createLogProxy(logger: Consola): Consola {
|
||||
* @see {createLogProxy}
|
||||
*/
|
||||
export default rootLogger;
|
||||
|
||||
// these are just type-sanity checks
|
||||
<{[k in LogType]?: typeof ForegroundColor}>DocutilsReporter.TYPE_COLOR_MAP;
|
||||
<{[k in LogLevel]?: typeof ForegroundColor}>DocutilsReporter.LEVEL_COLORS;
|
||||
<{[k in LogType]?: string}>DocutilsReporter.TYPE_ICONS;
|
||||
|
||||
@@ -9,16 +9,10 @@ import type {TypeDocOptions} from 'typedoc';
|
||||
|
||||
/**
|
||||
* A `tsconfig.json` file w/ `$schema` prop
|
||||
*
|
||||
* Due to some `unknown` types in {@linkcode type-fest.TsConfigJson}, we cannot use that type
|
||||
* directly and need to use `Jsonify`.
|
||||
*
|
||||
*/
|
||||
export type TsConfigJson = Jsonify<
|
||||
TsConfigJsonBase & {
|
||||
$schema?: string;
|
||||
}
|
||||
>;
|
||||
export type TsConfigJson = TsConfigJsonBase & {
|
||||
$schema?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* A `typedoc.json` file w/ `$schema` and `extends` props
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/docutils",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.2",
|
||||
"description": "Documentation generation utilities for Appium and related projects",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -49,9 +49,9 @@
|
||||
"start": "node ./build/lib/cli.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^3.1.5",
|
||||
"@appium/support": "^3.1.6",
|
||||
"@appium/tsconfig": "^0.2.4",
|
||||
"@appium/typedoc-plugin-appium": "^0.4.0",
|
||||
"@appium/typedoc-plugin-appium": "^0.5.0",
|
||||
"@sliphua/lilconfig-ts-loader": "3.2.2",
|
||||
"chalk": "4.1.2",
|
||||
"consola": "2.15.3",
|
||||
@@ -69,13 +69,13 @@
|
||||
"semver": "7.3.8",
|
||||
"source-map-support": "0.5.21",
|
||||
"teen_process": "2.0.2",
|
||||
"type-fest": "3.5.6",
|
||||
"typedoc": "0.23.24",
|
||||
"type-fest": "3.6.0",
|
||||
"typedoc": "0.23.25",
|
||||
"typedoc-plugin-markdown": "3.14.0",
|
||||
"typedoc-plugin-resolve-crossmodule-references": "0.3.3",
|
||||
"typescript": "4.7.4",
|
||||
"typescript": "4.9.5",
|
||||
"yaml": "2.2.1",
|
||||
"yargs": "17.6.2",
|
||||
"yargs": "17.7.1",
|
||||
"yargs-parser": "21.1.1"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
mkdocs==1.4.2
|
||||
mkdocs-material==9.0.11
|
||||
mkdocs-material==9.0.14
|
||||
mkdocs-material-extensions==1.1.1
|
||||
mike==1.1.2
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Mike} from '../..';
|
||||
import {Mike} from '../../lib/mike';
|
||||
import {expect} from 'chai';
|
||||
|
||||
describe('Mike', function () {
|
||||
|
||||
@@ -3,6 +3,25 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [0.4.0](https://github.com/appium/appium/compare/@appium/driver-test-support@0.3.7...@appium/driver-test-support@0.4.0) (2023-02-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **support:** update dependency axios to v1.3.4 ([49f157d](https://github.com/appium/appium/commit/49f157d63e3bdbd205527a5dc8f997df68540546))
|
||||
* **types:** update dependency type-fest to v3.5.7 ([b4416c5](https://github.com/appium/appium/commit/b4416c5c0f40200b36909a1fbb492d8c4a212108))
|
||||
* **types:** update dependency type-fest to v3.6.0 ([08a6f3a](https://github.com/appium/appium/commit/08a6f3a308c7ee162e992629888557b31e50a26e))
|
||||
* update axios to v1.3.3 ([8f9de63](https://github.com/appium/appium/commit/8f9de63e4a622712db545ab63f9f4ce6654e4a91))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **driver-test-support:** add startStoppableAppium() ([893bd88](https://github.com/appium/appium/commit/893bd888cf4e7b43e07c83e8ce71d71a30a8428e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.3.7](https://github.com/appium/appium/compare/@appium/driver-test-support@0.3.6...@appium/driver-test-support@0.3.7) (2023-02-09)
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@ export {createSessionHelpers, driverE2ETestSuite} from './e2e-suite';
|
||||
export * from './unit-suite';
|
||||
export * from './helpers';
|
||||
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
export * from './stoppable';
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').DriverClass} DriverClass
|
||||
* @typedef {import('@appium/types').BaseNSCapabilities} BaseNSCapabilities
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import type {AppiumServer} from '@appium/types';
|
||||
import {main as startAppium} from 'appium';
|
||||
import type {Args, CliCommandServer} from 'appium/types';
|
||||
import B from 'bluebird';
|
||||
import type {Server} from 'node:http';
|
||||
import stoppable from 'stoppable';
|
||||
import type {Asyncify} from 'type-fest';
|
||||
|
||||
/**
|
||||
* Options for {@linkcode startStoppableAppium}
|
||||
*/
|
||||
export type AppiumServerOpts = Args<CliCommandServer>;
|
||||
|
||||
/**
|
||||
* An {@linkcode AppiumServer} with a method `stop() => Promise<void>`, which closes all sockets and fully stops the server.
|
||||
*
|
||||
* Returned by {@linkcode startStoppableAppium}
|
||||
*/
|
||||
export type TestAppiumServer = Omit<NormativeAppiumServer, 'close'> & {
|
||||
stop: Asyncify<stoppable.WithStop['stop']>;
|
||||
close: (callback: (err?: Error) => void) => Promise<void>;
|
||||
};
|
||||
|
||||
/**
|
||||
* The {@linkcode AppiumServer} type, but with the `close` method normalized to a callback-style function.
|
||||
*/
|
||||
export type NormativeAppiumServer = Omit<AppiumServer, 'close'> & {
|
||||
close: Server['close'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Coerces {@linkcode AppiumServer} into a {@linkcode TestAppiumServer}.
|
||||
* @param opts Options for {@linkcode startAppium}
|
||||
* @todo This should be moved into `@appium/driver-test-support` or something
|
||||
* @returns A stoppable Appium server
|
||||
*/
|
||||
export async function startStoppableAppium(opts: AppiumServerOpts): Promise<TestAppiumServer> {
|
||||
const appiumServer = (await startAppium(opts)) as AppiumServer;
|
||||
const stoppableServer = stoppable(appiumServer as unknown as NormativeAppiumServer, 0);
|
||||
const originalAsyncClose = appiumServer.close;
|
||||
(stoppableServer as unknown as TestAppiumServer).close = async function (
|
||||
callback?: (err?: Error) => void
|
||||
) {
|
||||
if (callback) {
|
||||
try {
|
||||
await originalAsyncClose.call(this);
|
||||
callback();
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
} else {
|
||||
await originalAsyncClose.call(this);
|
||||
}
|
||||
};
|
||||
stoppableServer.stop = B.promisify(stoppableServer.stop, {context: stoppableServer});
|
||||
return stoppableServer as unknown as TestAppiumServer;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/driver-test-support",
|
||||
"version": "0.3.7",
|
||||
"version": "0.4.0",
|
||||
"description": "Test utilities for Appium drivers",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -41,16 +41,18 @@
|
||||
},
|
||||
"types": "./build/lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@appium/types": "^0.9.1",
|
||||
"@appium/types": "^0.10.0",
|
||||
"@types/lodash": "4.14.191",
|
||||
"axios": "1.2.3",
|
||||
"@types/stoppable": "1.1.1",
|
||||
"axios": "1.3.4",
|
||||
"bluebird": "3.7.2",
|
||||
"chai": "4.3.7",
|
||||
"get-port": "5.1.1",
|
||||
"lodash": "4.17.21",
|
||||
"sinon": "15.0.1",
|
||||
"source-map-support": "0.5.21",
|
||||
"type-fest": "3.5.6"
|
||||
"stoppable": "1.1.0",
|
||||
"type-fest": "3.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"appium": "^2.0.0-beta.43",
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import axios from 'axios';
|
||||
import B from 'bluebird';
|
||||
import {Agent} from 'node:http';
|
||||
import {startStoppableAppium, TestAppiumServer} from '../../lib';
|
||||
import getPort from 'get-port';
|
||||
|
||||
const {expect} = chai;
|
||||
|
||||
describe('startStoppableAppium()', function () {
|
||||
it('should start an Appium server', async function () {
|
||||
let server: TestAppiumServer | undefined;
|
||||
try {
|
||||
server = await startStoppableAppium({port: await getPort()});
|
||||
expect(server, 'to be an object');
|
||||
} finally {
|
||||
if (server) {
|
||||
await expect(server.stop()).to.be.fulfilled;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
describe('when the server has connections', function () {
|
||||
it('should stop the server and resolve with a boolean', async function () {
|
||||
const port = await getPort();
|
||||
const server = await startStoppableAppium({port});
|
||||
const getConnections = B.promisify(server.getConnections, {context: server});
|
||||
await axios.get(`http://127.0.0.1:${port}/status`, {
|
||||
httpAgent: new Agent({keepAlive: true}),
|
||||
});
|
||||
try {
|
||||
await expect(getConnections()).to.eventually.be.greaterThan(0);
|
||||
} finally {
|
||||
await expect(server.stop()).to.eventually.be.a('boolean');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -10,6 +10,6 @@
|
||||
},
|
||||
"types": ["mocha", "chai", "chai-as-promised"]
|
||||
},
|
||||
"include": ["./lib"],
|
||||
"include": ["lib", "test"],
|
||||
"references": [{"path": "../types"}, {"path": "../base-driver"}]
|
||||
}
|
||||
|
||||
@@ -3,6 +3,17 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.0.7](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@3.0.6...@appium/execute-driver-plugin@3.0.7) (2023-02-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **execute-driver-plugin:** update dependency webdriverio to v7.30.1 ([547b8a4](https://github.com/appium/appium/commit/547b8a45c4b5629720b0f4bdb0485a861aecbebf))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.0.6](https://github.com/appium/appium/compare/@appium/execute-driver-plugin@3.0.5...@appium/execute-driver-plugin@3.0.6) (2023-02-09)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/execute-driver-plugin",
|
||||
"version": "3.0.6",
|
||||
"version": "3.0.7",
|
||||
"description": "Plugin for batching and executing driver commands with Appiums",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -43,7 +43,7 @@
|
||||
"lodash": "4.17.21",
|
||||
"source-map-support": "0.5.21",
|
||||
"vm2": "3.9.14",
|
||||
"webdriverio": "7.30.0"
|
||||
"webdriverio": "7.30.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"appium": "^2.0.0-beta.35"
|
||||
|
||||
@@ -3,6 +3,17 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [5.2.0](https://github.com/appium/appium/compare/@appium/fake-driver@5.1.5...@appium/fake-driver@5.2.0) (2023-02-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **typedoc-plugin-appium:** extract descriptions of return values from builtins ([ebe9477](https://github.com/appium/appium/commit/ebe9477a3c56afd60c30c4591436c4ec68119f2a))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.5](https://github.com/appium/appium/compare/@appium/fake-driver@5.1.4...@appium/fake-driver@5.1.5) (2023-02-09)
|
||||
|
||||
**Note:** Version bump only for package @appium/fake-driver
|
||||
|
||||
@@ -73,7 +73,7 @@ export class FakeDriver extends BaseDriver {
|
||||
* @param {W3CFakeDriverCaps} [w3cCapabilities3] W3C Capabilities
|
||||
* @param {import('@appium/types').DriverData[]} [driverData] Other session data
|
||||
* @override
|
||||
* @returns {Promise<[string,FakeDriverCaps]>} Session ID and normalized capabilities
|
||||
* @returns {Promise<[string,FakeDriverCaps]>}
|
||||
*/
|
||||
async createSession(w3cCapabilities1, w3cCapabilities2, w3cCapabilities3, driverData = []) {
|
||||
// TODO add validation on caps.app that we will get for free from
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/fake-driver",
|
||||
"version": "5.1.5",
|
||||
"version": "5.2.0",
|
||||
"description": "Mock driver used internally by Appium for testing. Ignore",
|
||||
"keywords": [
|
||||
"automation",
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.1.6](https://github.com/appium/appium/compare/@appium/fake-plugin@3.1.5...@appium/fake-plugin@3.1.6) (2023-02-24)
|
||||
|
||||
**Note:** Version bump only for package @appium/fake-plugin
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.1.5](https://github.com/appium/appium/compare/@appium/fake-plugin@3.1.4...@appium/fake-plugin@3.1.5) (2023-02-09)
|
||||
|
||||
**Note:** Version bump only for package @appium/fake-plugin
|
||||
|
||||
@@ -5,7 +5,20 @@ import {BasePlugin} from 'appium/plugin';
|
||||
import B from 'bluebird';
|
||||
|
||||
class FakePlugin extends BasePlugin {
|
||||
fakeThing = 'PLUGIN_FAKE_THING';
|
||||
/**
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
fakeThing;
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {Record<string,unknown>} cliArgs
|
||||
*/
|
||||
constructor(name, cliArgs) {
|
||||
super(name, cliArgs);
|
||||
this.fakeThing = 'PLUGIN_FAKE_THING';
|
||||
}
|
||||
|
||||
static newMethodMap = /** @type {const} */ ({
|
||||
'/session/:sessionId/fake_data': {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/fake-plugin",
|
||||
"version": "3.1.5",
|
||||
"version": "3.1.6",
|
||||
"description": "A fake Appium 2.0 plugin",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -39,8 +39,8 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/base-plugin": "^2.2.1",
|
||||
"@appium/support": "^3.1.5",
|
||||
"@appium/base-plugin": "^2.2.2",
|
||||
"@appium/support": "^3.1.6",
|
||||
"@types/bluebird": "3.5.38",
|
||||
"bluebird": "3.7.2",
|
||||
"lodash": "4.17.21",
|
||||
|
||||
@@ -3,6 +3,21 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.0.7](https://github.com/appium/appium/compare/@appium/images-plugin@2.0.6...@appium/images-plugin@2.0.7) (2023-02-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **images-plugin:** update dependency lru-cache to v7.15.0 ([4cf8dbc](https://github.com/appium/appium/commit/4cf8dbc0d34769e3167bceed65facabe71b9cbde))
|
||||
* **images-plugin:** update dependency lru-cache to v7.16.0 ([d54901a](https://github.com/appium/appium/commit/d54901a9c3982dd5595ffd54010e5029b60c4754))
|
||||
* **images-plugin:** update dependency lru-cache to v7.16.1 ([2fa6bf1](https://github.com/appium/appium/commit/2fa6bf1e11e66ba8256d0641e345359bd108bc7d))
|
||||
* **images-plugin:** update dependency lru-cache to v7.16.2 ([4b10322](https://github.com/appium/appium/commit/4b10322b80e0032dd4585ac7766edd1ddf798139))
|
||||
* **images-plugin:** update dependency lru-cache to v7.17.0 ([eb73be8](https://github.com/appium/appium/commit/eb73be8303c517dbaa965ae99bac3381522d939a))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.6](https://github.com/appium/appium/compare/@appium/images-plugin@2.0.5...@appium/images-plugin@2.0.6) (2023-02-09)
|
||||
|
||||
**Note:** Version bump only for package @appium/images-plugin
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/images-plugin",
|
||||
"version": "2.0.6",
|
||||
"version": "2.0.7",
|
||||
"description": "Plugin for working with images and image elements in Appium",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -39,9 +39,9 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/opencv": "^2.0.6",
|
||||
"@appium/opencv": "^2.0.7",
|
||||
"lodash": "4.17.21",
|
||||
"lru-cache": "7.14.1",
|
||||
"lru-cache": "7.17.0",
|
||||
"source-map-support": "0.5.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -3,6 +3,17 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.0.7](https://github.com/appium/appium/compare/@appium/opencv@2.0.6...@appium/opencv@2.0.7) (2023-02-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **support:** update dependency jimp to v0.22.5 ([998514f](https://github.com/appium/appium/commit/998514fccee0b1a7381b810280cbc30558c3c282))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.6](https://github.com/appium/appium/compare/@appium/opencv@2.0.5...@appium/opencv@2.0.6) (2023-02-09)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/opencv",
|
||||
"version": "2.0.6",
|
||||
"version": "2.0.7",
|
||||
"description": "OpenCV-related helper library",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -43,7 +43,7 @@
|
||||
"dependencies": {
|
||||
"@types/bluebird": "3.5.38",
|
||||
"bluebird": "3.7.2",
|
||||
"jimp": "0.22.4",
|
||||
"jimp": "0.22.5",
|
||||
"lodash": "4.17.21",
|
||||
"opencv-bindings": "4.5.5",
|
||||
"source-map-support": "0.5.21"
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.3.8](https://github.com/appium/appium/compare/@appium/plugin-test-support@0.3.7...@appium/plugin-test-support@0.3.8) (2023-02-24)
|
||||
|
||||
**Note:** Version bump only for package @appium/plugin-test-support
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.3.7](https://github.com/appium/appium/compare/@appium/plugin-test-support@0.3.6...@appium/plugin-test-support@0.3.7) (2023-02-09)
|
||||
|
||||
**Note:** Version bump only for package @appium/plugin-test-support
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/plugin-test-support",
|
||||
"version": "0.3.7",
|
||||
"version": "0.3.8",
|
||||
"description": "Testing utilities for Appium plugins",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -41,7 +41,7 @@
|
||||
"test:unit": "exit 0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/types": "^0.9.1",
|
||||
"@appium/types": "^0.10.0",
|
||||
"get-port": "5.1.1",
|
||||
"log-symbols": "4.1.0",
|
||||
"source-map-support": "0.5.21",
|
||||
|
||||
@@ -3,6 +3,17 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.2.5](https://github.com/appium/appium/compare/@appium/schema@0.2.4...@appium/schema@0.2.5) (2023-02-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **schema:** move output files due to tsc's confusion ([943dce5](https://github.com/appium/appium/commit/943dce5eec4c047073ef9dfcb97da9ccd6be9459))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.2.4](https://github.com/appium/appium/compare/@appium/schema@0.2.3...@appium/schema@0.2.4) (2023-02-09)
|
||||
|
||||
**Note:** Version bump only for package @appium/schema
|
||||
|
||||
@@ -1 +1 @@
|
||||
module.exports = require('./build/index.js');
|
||||
module.exports = require('./build/lib/index.js');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/schema",
|
||||
"version": "0.2.4",
|
||||
"version": "0.2.5",
|
||||
"description": "Appium Configuration Schema",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -23,7 +23,7 @@
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"author": "https://github.com/appium",
|
||||
"types": "./build/index.d.ts",
|
||||
"types": "./build/lib/index.d.ts",
|
||||
"files": [
|
||||
"build",
|
||||
"lib",
|
||||
|
||||
@@ -29,7 +29,7 @@ const JSON_FILENAME = 'appium-config.schema.json';
|
||||
/**
|
||||
* Path to the schema artifact.
|
||||
*/
|
||||
const SCHEMA_SRC = path.join(SCHEMA_ROOT, 'build', 'appium-config-schema.js');
|
||||
const SCHEMA_SRC = path.join(SCHEMA_ROOT, 'build', 'lib', 'appium-config-schema.js');
|
||||
|
||||
/**
|
||||
* Full path to output `.json`
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"extends": "@appium/tsconfig/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "lib",
|
||||
"rootDir": ".",
|
||||
"outDir": "build",
|
||||
"checkJs": true
|
||||
"checkJs": true,
|
||||
"strict": true
|
||||
},
|
||||
"include": ["./lib/**/*.js"]
|
||||
"include": ["lib"]
|
||||
}
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.1.6](https://github.com/appium/appium/compare/@appium/support@3.1.5...@appium/support@3.1.6) (2023-02-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **support:** update dependency @types/uuid to v9.0.1 ([91831cf](https://github.com/appium/appium/commit/91831cf82da943726d04e9f38a3b63c9304fb20e))
|
||||
* **support:** update dependency axios to v1.3.4 ([49f157d](https://github.com/appium/appium/commit/49f157d63e3bdbd205527a5dc8f997df68540546))
|
||||
* **support:** update dependency jimp to v0.22.5 ([998514f](https://github.com/appium/appium/commit/998514fccee0b1a7381b810280cbc30558c3c282))
|
||||
* **types:** update dependency type-fest to v3.5.7 ([b4416c5](https://github.com/appium/appium/commit/b4416c5c0f40200b36909a1fbb492d8c4a212108))
|
||||
* **types:** update dependency type-fest to v3.6.0 ([08a6f3a](https://github.com/appium/appium/commit/08a6f3a308c7ee162e992629888557b31e50a26e))
|
||||
* update axios to v1.3.3 ([8f9de63](https://github.com/appium/appium/commit/8f9de63e4a622712db545ab63f9f4ce6654e4a91))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.1.5](https://github.com/appium/appium/compare/@appium/support@3.1.4...@appium/support@3.1.5) (2023-02-09)
|
||||
|
||||
|
||||
|
||||
@@ -193,10 +193,10 @@ export class NPM {
|
||||
* Installs a package w/ `npm`
|
||||
* @param {string} cwd
|
||||
* @param {string} pkgName
|
||||
* @param {InstallPackageOpts} [opts]
|
||||
* @param {InstallPackageOpts} opts
|
||||
* @returns {Promise<NpmInstallReceipt>}
|
||||
*/
|
||||
async installPackage(cwd, pkgName, {pkgVer, installType}) {
|
||||
async installPackage(cwd, pkgName, {pkgVer, installType} = {}) {
|
||||
/** @type {any} */
|
||||
let dummyPkgJson;
|
||||
const dummyPkgPath = path.join(cwd, 'package.json');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/support",
|
||||
"version": "3.1.5",
|
||||
"version": "3.1.6",
|
||||
"description": "Support libs used across appium packages",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -42,7 +42,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/tsconfig": "^0.2.4",
|
||||
"@appium/types": "^0.9.1",
|
||||
"@appium/types": "^0.10.0",
|
||||
"@colors/colors": "1.5.0",
|
||||
"@types/archiver": "5.3.1",
|
||||
"@types/base64-stream": "1.0.2",
|
||||
@@ -61,9 +61,9 @@
|
||||
"@types/shell-quote": "1.7.1",
|
||||
"@types/supports-color": "8.1.1",
|
||||
"@types/teen_process": "2.0.0",
|
||||
"@types/uuid": "9.0.0",
|
||||
"@types/uuid": "9.0.1",
|
||||
"archiver": "5.3.1",
|
||||
"axios": "1.2.3",
|
||||
"axios": "1.3.4",
|
||||
"base64-stream": "1.0.0",
|
||||
"bluebird": "3.7.2",
|
||||
"bplist-creator": "0.1.1",
|
||||
@@ -71,7 +71,7 @@
|
||||
"form-data": "4.0.0",
|
||||
"get-stream": "6.0.1",
|
||||
"glob": "8.1.0",
|
||||
"jimp": "0.22.4",
|
||||
"jimp": "0.22.5",
|
||||
"jsftp": "2.1.3",
|
||||
"klaw": "4.1.0",
|
||||
"lockfile": "1.0.4",
|
||||
@@ -95,7 +95,7 @@
|
||||
"source-map-support": "0.5.21",
|
||||
"supports-color": "8.1.1",
|
||||
"teen_process": "2.0.2",
|
||||
"type-fest": "3.5.6",
|
||||
"type-fest": "3.6.0",
|
||||
"uuid": "9.0.0",
|
||||
"which": "3.0.0",
|
||||
"yauzl": "2.10.0"
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.0.8](https://github.com/appium/appium/compare/@appium/test-support@3.0.7...@appium/test-support@3.0.8) (2023-02-24)
|
||||
|
||||
**Note:** Version bump only for package @appium/test-support
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.0.7](https://github.com/appium/appium/compare/@appium/test-support@3.0.6...@appium/test-support@3.0.7) (2023-02-09)
|
||||
|
||||
**Note:** Version bump only for package @appium/test-support
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/test-support",
|
||||
"version": "3.0.7",
|
||||
"version": "3.0.8",
|
||||
"description": "A collection of test utilities used across Appium packages",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -45,7 +45,7 @@
|
||||
"test:unit": "mocha \"./test/unit/**/*.spec.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/support": "^3.1.5",
|
||||
"@appium/support": "^3.1.6",
|
||||
"@colors/colors": "1.5.0",
|
||||
"bluebird": "3.7.2",
|
||||
"lodash": "4.17.21",
|
||||
|
||||
@@ -3,6 +3,25 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [0.5.0](https://github.com/appium/appium/compare/@appium/typedoc-plugin-appium@0.4.0...@appium/typedoc-plugin-appium@0.5.0) (2023-02-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **typedoc-plugin-appium:** fix broken references during clone operations ([242c2cc](https://github.com/appium/appium/commit/242c2ccbdc8030de0f0c2f60162b6a56941fc238))
|
||||
* **typedoc-plugin-appium:** parameter descriptions work ([592f9ad](https://github.com/appium/appium/commit/592f9adcb12a8a6b0b95bd73cd155a8094e79202)), closes [#18132](https://github.com/appium/appium/issues/18132)
|
||||
* **types:** update dependency type-fest to v3.5.7 ([b4416c5](https://github.com/appium/appium/commit/b4416c5c0f40200b36909a1fbb492d8c4a212108))
|
||||
* **types:** update dependency type-fest to v3.6.0 ([08a6f3a](https://github.com/appium/appium/commit/08a6f3a308c7ee162e992629888557b31e50a26e))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **typedoc-plugin-appium:** extract descriptions of return values from builtins ([ebe9477](https://github.com/appium/appium/commit/ebe9477a3c56afd60c30c4591436c4ec68119f2a))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [0.4.0](https://github.com/appium/appium/compare/@appium/typedoc-plugin-appium@0.3.4...@appium/typedoc-plugin-appium@0.4.0) (2023-02-09)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {Context} from 'typedoc';
|
||||
import {AppiumPluginLogger} from '../logger';
|
||||
import {ModuleCommands} from '../model';
|
||||
|
||||
export abstract class BaseConverter<Result> {
|
||||
constructor(protected ctx: Context, protected log: AppiumPluginLogger, ...args: any[]) {}
|
||||
|
||||
@@ -6,13 +6,7 @@
|
||||
|
||||
import _ from 'lodash';
|
||||
import pluralize from 'pluralize';
|
||||
import {
|
||||
ContainerReflection,
|
||||
Context,
|
||||
DeclarationReflection,
|
||||
ProjectReflection,
|
||||
ReflectionKind,
|
||||
} from 'typedoc';
|
||||
import {ContainerReflection, Context, DeclarationReflection, ProjectReflection} from 'typedoc';
|
||||
import {isParentReflection} from '../guards';
|
||||
import {AppiumPluginLogger} from '../logger';
|
||||
import {
|
||||
@@ -22,7 +16,6 @@ import {
|
||||
ExecMethodData,
|
||||
ExtensionReflection,
|
||||
ModuleCommands,
|
||||
ParentReflection,
|
||||
ProjectCommands,
|
||||
Route,
|
||||
} from '../model';
|
||||
@@ -48,13 +41,12 @@ import {findChildByNameAndGuard} from './utils';
|
||||
* @internal
|
||||
*/
|
||||
export function createCommandReflection(
|
||||
log: AppiumPluginLogger,
|
||||
ctx: Context,
|
||||
data: CommandData | ExecMethodData,
|
||||
parent: ExtensionReflection,
|
||||
route?: Route
|
||||
): void {
|
||||
const commandRefl = new CommandReflection(data, parent, log, route);
|
||||
const commandRefl = new CommandReflection(data, parent, route);
|
||||
// yes, the `undefined`s are needed
|
||||
ctx.postReflectionCreation(commandRefl, undefined, undefined);
|
||||
ctx.finalizeDeclarationReflection(commandRefl);
|
||||
@@ -99,12 +91,12 @@ export function createExtensionReflection(
|
||||
|
||||
for (const [route, commandSet] of routeMap) {
|
||||
for (const data of commandSet) {
|
||||
createCommandReflection(log, parentCtx, data, extRefl, route);
|
||||
createCommandReflection(parentCtx, data, extRefl, route);
|
||||
}
|
||||
}
|
||||
|
||||
for (const data of execCommandsData) {
|
||||
createCommandReflection(log, parentCtx, data, extRefl);
|
||||
createCommandReflection(parentCtx, data, extRefl);
|
||||
}
|
||||
|
||||
ctx.finalizeDeclarationReflection(extRefl);
|
||||
|
||||
@@ -4,7 +4,7 @@ import {isAppiumTypesReflection, isExternalDriverDeclarationReflection} from '..
|
||||
import {AppiumPluginLogger} from '../logger';
|
||||
import {BaseConverter} from './base-converter';
|
||||
import {AppiumTypesReflection, KnownMethods} from './types';
|
||||
import {findAsyncMethodsInReflection, findParentReflectionByName} from './utils';
|
||||
import {findParentReflectionByName, findCommandMethodsInReflection} from './utils';
|
||||
|
||||
/**
|
||||
* Name of the module containing `ExternalDriver`
|
||||
@@ -30,15 +30,15 @@ export class BuiltinExternalDriverConverter extends BaseConverter<KnownMethods>
|
||||
}
|
||||
|
||||
#convertMethodDeclarations(refl: AppiumTypesReflection): KnownMethods {
|
||||
const externalDriver = refl.getChildByName(NAME_EXTERNAL_DRIVER);
|
||||
const externalDriverRefl = refl.getChildByName(NAME_EXTERNAL_DRIVER);
|
||||
let methods: KnownMethods = new Map();
|
||||
|
||||
if (!isExternalDriverDeclarationReflection(externalDriver)) {
|
||||
if (!isExternalDriverDeclarationReflection(externalDriverRefl)) {
|
||||
this.log.error('Could not find %s', NAME_EXTERNAL_DRIVER);
|
||||
return methods;
|
||||
}
|
||||
|
||||
methods = findAsyncMethodsInReflection(externalDriver);
|
||||
methods = findCommandMethodsInReflection(externalDriverRefl);
|
||||
|
||||
if (!methods.size) {
|
||||
this.log.error('No methods found in %s', NAME_EXTERNAL_DRIVER);
|
||||
@@ -56,11 +56,10 @@ export class BuiltinExternalDriverConverter extends BaseConverter<KnownMethods>
|
||||
|
||||
public override convert(): KnownMethods {
|
||||
const {project} = this.ctx;
|
||||
let methods: KnownMethods = new Map();
|
||||
const typesModule = findParentReflectionByName(project, NAME_TYPES_MODULE);
|
||||
if (!isAppiumTypesReflection(typesModule)) {
|
||||
this.log.error('Could not find %s', NAME_TYPES_MODULE);
|
||||
return methods;
|
||||
return new Map();
|
||||
}
|
||||
|
||||
this.log.verbose('Found %s; converting', NAME_EXTERNAL_DRIVER);
|
||||
|
||||
@@ -5,13 +5,13 @@ import {
|
||||
isMethodMapDeclarationReflection,
|
||||
} from '../guards';
|
||||
import {AppiumPluginLogger} from '../logger';
|
||||
import {BaseConverter} from './base-converter';
|
||||
import {BuiltinCommands} from '../model/builtin-commands';
|
||||
import {BaseConverter} from './base-converter';
|
||||
import {convertMethodMap} from './method-map';
|
||||
import {KnownMethods} from './types';
|
||||
import {
|
||||
findChildByNameAndGuard,
|
||||
findAsyncMethodsInReflection,
|
||||
findCommandMethodsInReflection,
|
||||
findParentReflectionByName,
|
||||
} from './utils';
|
||||
|
||||
@@ -36,7 +36,7 @@ export class BuiltinMethodMapConverter extends BaseConverter<BuiltinCommands> {
|
||||
constructor(
|
||||
ctx: Context,
|
||||
log: AppiumPluginLogger,
|
||||
protected readonly knownMethods: KnownMethods
|
||||
protected readonly knownBuiltinMethods: KnownMethods
|
||||
) {
|
||||
super(ctx, log.createChildLogger(NAME_BUILTIN_COMMAND_MODULE));
|
||||
}
|
||||
@@ -76,11 +76,13 @@ export class BuiltinMethodMapConverter extends BaseConverter<BuiltinCommands> {
|
||||
return new BuiltinCommands();
|
||||
}
|
||||
|
||||
const knownClassMethods = findCommandMethodsInReflection(baseDriverClassRefl);
|
||||
const baseDriverRoutes = convertMethodMap({
|
||||
log: this.log,
|
||||
methodMapRefl: methodMap,
|
||||
parentRefl: baseDriverModuleRefl,
|
||||
methods: findAsyncMethodsInReflection(baseDriverClassRefl),
|
||||
knownClassMethods,
|
||||
knownBuiltinMethods: this.knownBuiltinMethods,
|
||||
});
|
||||
|
||||
if (!baseDriverRoutes.size) {
|
||||
|
||||
@@ -1,334 +0,0 @@
|
||||
/**
|
||||
* Utilities to derive comments from various sources
|
||||
* @module
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import {SetOptional, ValueOf} from 'type-fest';
|
||||
import {Comment, CommentTag} from 'typedoc';
|
||||
import {CommandMethodDeclarationReflection, KnownMethods} from './types';
|
||||
|
||||
export const NAME_EXAMPLE_TAG = '@example';
|
||||
|
||||
/**
|
||||
* Languages which can be used in example code blocks
|
||||
*
|
||||
* The key is the identifier used in a fenced code block, and the value is the "display" value
|
||||
*/
|
||||
export const ExampleLanguage = {
|
||||
ts: 'TypeScript',
|
||||
typescript: 'TypeScript',
|
||||
js: 'JavaScript',
|
||||
javascript: 'JavaScript',
|
||||
py: 'Python',
|
||||
python: 'Python',
|
||||
rb: 'Ruby',
|
||||
ruby: 'Ruby',
|
||||
java: 'Java',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* The beginning of fenced code block looks like this.
|
||||
*
|
||||
* @todo Ensure the _end_ of the fenced code block exists
|
||||
*/
|
||||
const FENCED_CODE_BLOCK_REGEX = /^\s*```(\w+)?/;
|
||||
|
||||
/**
|
||||
* A function which queries some object for a {@linkcode Comment}, and if found,
|
||||
* its {@linkcode Comment.summary summary}.
|
||||
* @internal
|
||||
*/
|
||||
interface CommentFinder {
|
||||
getter: (opts: CommentFinderGetterOptions) => Comment | undefined;
|
||||
commentSource: CommentSourceType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@linkcode CommentFinder.getter} functions
|
||||
* @internal
|
||||
*/
|
||||
interface CommentFinderGetterOptions {
|
||||
refl?: CommandMethodDeclarationReflection;
|
||||
comment?: Comment;
|
||||
knownMethods?: KnownMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper around a found {@linkcode Comment} which also contains a {@linkcode CommentSourceType}
|
||||
* so we can observe where the `Comment` came from.
|
||||
*/
|
||||
export interface CommentData {
|
||||
comment: Comment;
|
||||
commentSource: CommentSourceType;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is basically a fenced code block split into two portions: the text itself and the language
|
||||
* specified in the opening fence. Part of {@linkcode ExtractedExamples}
|
||||
*/
|
||||
export interface Example {
|
||||
text: string;
|
||||
lang: ValueOf<typeof ExampleLanguage>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A pair of a comment and any examples which were removed from it. Returned by {@linkcode extractExamples}
|
||||
*/
|
||||
export interface ExtractedExamples {
|
||||
examples?: Example[];
|
||||
comment: Comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mainly for debugging purposes, these tell us (roughly) where a comment came from.
|
||||
* In the case of {@linkcode CommentSourceType.Multiple}, the comment was derived
|
||||
* from multiple sources.
|
||||
* @internal
|
||||
*/
|
||||
export enum CommentSourceType {
|
||||
/**
|
||||
* This is a comment directly on the `DeclarationReference` itself.
|
||||
*
|
||||
* It's unclear to me why sometimes comments are attached to the method proper or its signature;
|
||||
* might have something to do with `ReferenceType`.
|
||||
*/
|
||||
Method = 'method',
|
||||
/**
|
||||
* A comment attached to the method's call signature.
|
||||
*/
|
||||
MethodSignature = 'method-signature',
|
||||
/**
|
||||
* A comment from "elsewhere", which is usually a method map or exec method map.
|
||||
*/
|
||||
OtherComment = 'other-comment',
|
||||
/**
|
||||
* A comment coming out of the `@appium/types` package; specifically a method in `ExternalDriver`
|
||||
*/
|
||||
OtherMethod = 'builtin-interface',
|
||||
/**
|
||||
* A comment _built_ from any of the above sources from one or more `DeclarationReference`
|
||||
* objects. For example, the summary (description) of an implementation of `doubleClick()` and
|
||||
* the `@example` block tag from the `ExternalDriver` interface.
|
||||
*/
|
||||
Multiple = 'multiple',
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@linkcode deriveComment}
|
||||
*/
|
||||
interface DeriveCommentOptions {
|
||||
refl?: CommandMethodDeclarationReflection;
|
||||
comment?: Comment;
|
||||
knownMethods?: KnownMethods;
|
||||
}
|
||||
|
||||
const knownComments: Map<string, Comment> = new Map();
|
||||
|
||||
/**
|
||||
* Array of strategies for finding comments. They can come from a variety of places depending on
|
||||
* where a variable is declared, if it overrides a method, if it implements a method in an
|
||||
* interface, etc.
|
||||
*
|
||||
* These have an order (of precedence), which is why this is an array.
|
||||
*/
|
||||
const commentFinders: Readonly<CommentFinder[]> = [
|
||||
{
|
||||
/**
|
||||
*
|
||||
* @returns A comment probably from the method map itself
|
||||
*/
|
||||
getter: ({comment}) => comment,
|
||||
commentSource: CommentSourceType.OtherComment,
|
||||
},
|
||||
{
|
||||
/**
|
||||
* @returns The comment on the method itself
|
||||
*/
|
||||
getter: ({refl}) => refl?.comment,
|
||||
commentSource: CommentSourceType.Method,
|
||||
},
|
||||
{
|
||||
/**
|
||||
* @returns The comment from the method's signature (may be inherited)
|
||||
*/
|
||||
getter: ({refl}) => refl?.getAllSignatures().find((sig) => sig.comment?.summary)?.comment,
|
||||
commentSource: CommentSourceType.MethodSignature,
|
||||
},
|
||||
{
|
||||
/**
|
||||
* @returns The comment from some method that this one implements or overwrites or w/e;
|
||||
* typically coming from interfaces in `@appium/types`
|
||||
*/
|
||||
getter: ({refl, knownMethods}) => {
|
||||
if (refl && knownComments.has(refl.name)) {
|
||||
return knownComments.get(refl.name);
|
||||
}
|
||||
// if the `refl` is a known command, it should be in `knownMethods`;
|
||||
// if it isn't (or doesn't exist) we aren't going to display it anyway, so abort
|
||||
const otherRefl = refl && knownMethods?.get(refl.name);
|
||||
if (!otherRefl) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if `otherRefl` exists, then the comment could live in several places,
|
||||
// which happen to be findable by the _other_ `CommentFinder`s. we avoid
|
||||
// `CommentSourceType.OtherMethod`, which corresponds to _this_ `CommentFinder`
|
||||
// to avoid recursion (for now).
|
||||
//
|
||||
// after looping thru the finders, if we have a comment in the list of `commentData`
|
||||
// objects, return the first one found.
|
||||
const comment = commentFinders
|
||||
.filter(({commentSource}) => commentSource !== CommentSourceType.OtherMethod)
|
||||
.map(({getter, commentSource}) => ({
|
||||
comment: getter({refl: otherRefl, knownMethods}),
|
||||
commentSource,
|
||||
}))
|
||||
.find(({comment}) => Boolean(comment))?.comment;
|
||||
if (comment) {
|
||||
knownComments.set(refl.name, comment);
|
||||
}
|
||||
return comment;
|
||||
},
|
||||
commentSource: CommentSourceType.OtherMethod,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Tries to figure out a comment for a command
|
||||
* @param opts Options
|
||||
* @returns A {@linkcode CommentData} containing a {@linkcode Comment}, if one can be found
|
||||
* @internal
|
||||
*/
|
||||
export function deriveComment(opts: DeriveCommentOptions = {}): CommentData | undefined {
|
||||
const {refl, comment, knownMethods} = opts;
|
||||
/**
|
||||
* The result of running thru all of the comment finders. Each value will have a
|
||||
* {@linkcode CommentSourceType} corresponding to the finder, and the a `comment` property _if and
|
||||
* only if_ a {@linkcode Comment} was found.
|
||||
*/
|
||||
const rawCommentData: SetOptional<CommentData, 'comment'>[] = commentFinders.map(
|
||||
({getter, commentSource}) => ({
|
||||
comment: getter({refl, comment, knownMethods}),
|
||||
commentSource,
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* The result of filtering out any {@linkcode CommentData} objects which do not have a `comment` property
|
||||
* _or_ which do but the {@linkcode Comment} property does not have any visible components.
|
||||
*
|
||||
* A comment without visible components is defined by TypeDoc as one without a "summary" (or an
|
||||
* empty summary) and one which has no block tags. Block tags, by definition, display something.
|
||||
*/
|
||||
const commentData: CommentData[] = rawCommentData.filter(({comment}) =>
|
||||
comment?.hasVisibleComponent()
|
||||
) as CommentData[];
|
||||
|
||||
// the first summary found; this is where precedence comes in to play
|
||||
const summaryCommentData = commentData.find(({comment}) => comment.summary?.length);
|
||||
|
||||
// get a big pile of block tags from all sources, and pick the first from each unique tag
|
||||
const allBlockTags = commentData.flatMap(({comment}) => comment.blockTags);
|
||||
const uniqueBlockTags = _.uniqBy(allBlockTags, 'tag');
|
||||
|
||||
// if we have a summary comment and some block tags, and the block tags are not the same as the
|
||||
// summary comment's block tags, then create a new comment merged from the summary and all block
|
||||
// tags.
|
||||
// note that by definition `uniqueBlockTags` will contain all tags in the summary comment's
|
||||
// `blockTags` prop
|
||||
if (
|
||||
summaryCommentData &&
|
||||
uniqueBlockTags.length &&
|
||||
_.xor(summaryCommentData.comment.blockTags, uniqueBlockTags).length
|
||||
) {
|
||||
const comment = cloneComment(summaryCommentData.comment, uniqueBlockTags);
|
||||
|
||||
return {comment, commentSource: CommentSourceType.Multiple};
|
||||
}
|
||||
|
||||
// pick the first one found
|
||||
return _.first(commentData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones a comment. Mostly. I think.
|
||||
* @param comment Comment to clone
|
||||
* @param blockTags Block tags to use; if not provided, the comment's block tags will be used (these
|
||||
* are also cloned)
|
||||
* @returns A new comment
|
||||
*/
|
||||
export function cloneComment(comment: Comment, blockTags?: CommentTag[]): Comment {
|
||||
const newComment = new Comment(
|
||||
Comment.cloneDisplayParts(comment.summary),
|
||||
(blockTags ?? comment.blockTags).map((blockTag) => {
|
||||
const tag = new CommentTag(blockTag.tag, Comment.cloneDisplayParts(blockTag.content));
|
||||
tag.name = blockTag.name;
|
||||
return tag;
|
||||
})
|
||||
);
|
||||
newComment.modifierTags = new Set(comment.modifierTags);
|
||||
|
||||
return newComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to narrow a string to a key of {@linkcode ExampleLanguage}
|
||||
* @param value anything
|
||||
* @returns `true` if the value is a valid language
|
||||
*/
|
||||
function isValidLang(value: any): value is keyof typeof ExampleLanguage {
|
||||
return value in ExampleLanguage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds any `@example` tags within a comment and creates an {@linkcode ExtractedExamples} object
|
||||
* for them. Creates a new comment with the examples removed, so we can handle them in a
|
||||
* custom way via our theme.
|
||||
*
|
||||
* Note that the same `Comment` can be passed multiple times (if it is displayed in multiple
|
||||
* modules). Our choices are either to a) clone the comment each time, or b) memoize the function
|
||||
* in order to avoid a problem wherein the comment has its examples extracted on the first call, but
|
||||
* subsequent calls will not contain any examples (since they've already been extracted). It may be
|
||||
* more memory-friendly to memoize, but it may not be the safest thing to do (unclear).
|
||||
*
|
||||
* @param comment A comment to extract examples from
|
||||
* @returns New comment (with examples removed)
|
||||
*/
|
||||
export const extractExamples = (comment?: Comment): ExtractedExamples | undefined => {
|
||||
if (!comment) {
|
||||
return;
|
||||
}
|
||||
|
||||
comment = cloneComment(comment);
|
||||
|
||||
const exampleTags = comment.getTags(NAME_EXAMPLE_TAG);
|
||||
|
||||
if (!exampleTags) {
|
||||
return {comment};
|
||||
}
|
||||
|
||||
const examples = exampleTags.flatMap(({content}) =>
|
||||
content.reduce((examples, {text}) => {
|
||||
const match = text.match(FENCED_CODE_BLOCK_REGEX);
|
||||
if (match) {
|
||||
const matchedLang = match[1] ?? 'js';
|
||||
const lang = isValidLang(matchedLang) ? matchedLang : 'js';
|
||||
return [
|
||||
...examples,
|
||||
{
|
||||
lang: ExampleLanguage[lang],
|
||||
text,
|
||||
},
|
||||
];
|
||||
}
|
||||
return examples;
|
||||
}, [] as Example[])
|
||||
);
|
||||
|
||||
if (examples.length) {
|
||||
comment.removeTags(NAME_EXAMPLE_TAG);
|
||||
return {examples, comment};
|
||||
}
|
||||
return {comment};
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
import parameter from './parameter';
|
||||
import method from './method';
|
||||
import signature from './signature';
|
||||
import {Reflection, ReflectionKind} from 'typedoc';
|
||||
import {CommentFinder} from './types';
|
||||
import {CommentSource} from '../types';
|
||||
|
||||
const nullFinder: CommentFinder = {
|
||||
/**
|
||||
*
|
||||
* @returns A comment probably from the method map itself
|
||||
*/
|
||||
getter: ({comment}) => comment,
|
||||
commentSource: CommentSource.OtherComment,
|
||||
};
|
||||
|
||||
function getFinders(refl?: Reflection): Readonly<CommentFinder[]> {
|
||||
if (!refl) {
|
||||
return [nullFinder];
|
||||
}
|
||||
switch (refl.kind) {
|
||||
case ReflectionKind.Parameter:
|
||||
return [...parameter, nullFinder];
|
||||
case ReflectionKind.Method:
|
||||
case ReflectionKind.Property:
|
||||
return [...method, nullFinder];
|
||||
case ReflectionKind.CallSignature:
|
||||
return [...signature, nullFinder];
|
||||
}
|
||||
return [nullFinder];
|
||||
}
|
||||
|
||||
export {getFinders};
|
||||
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* Utilities to derive comments from various sources
|
||||
* @module
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import {SetOptional} from 'type-fest';
|
||||
import {Comment, CommentTag, Reflection} from 'typedoc';
|
||||
import {CommentSource, Example, ExampleLanguage, ExtractedExamples, KnownMethods} from '../types';
|
||||
import {getFinders} from './finder';
|
||||
import {CommentData} from './types';
|
||||
import {fallbackLogger} from '../../logger';
|
||||
|
||||
const log = fallbackLogger.createChildLogger('comment');
|
||||
|
||||
export const NAME_EXAMPLE_TAG = '@example';
|
||||
|
||||
/**
|
||||
* The beginning of fenced code block looks like this.
|
||||
*
|
||||
* @todo Ensure the _end_ of the fenced code block exists
|
||||
*/
|
||||
const FENCED_CODE_BLOCK_REGEX = /^\s*```(\w+)?/;
|
||||
|
||||
/**
|
||||
* Options for {@linkcode deriveComment}
|
||||
*/
|
||||
interface DeriveCommentOptions {
|
||||
refl?: Reflection;
|
||||
comment?: Comment;
|
||||
knownMethods?: KnownMethods;
|
||||
}
|
||||
/**
|
||||
* Tries to figure out a comment for a command
|
||||
* @param opts Options
|
||||
* @returns A {@linkcode CommentData} containing a {@linkcode Comment}, if one can be found
|
||||
* @internal
|
||||
*/
|
||||
export function deriveComment(opts: DeriveCommentOptions = {}): CommentData | undefined {
|
||||
const {refl, comment, knownMethods} = opts;
|
||||
|
||||
const commentFinders = getFinders(refl);
|
||||
|
||||
/**
|
||||
* The result of running thru all of the comment finders. Each value will have a
|
||||
* {@linkcode CommentSource} corresponding to the finder, and the a `comment` property _if and
|
||||
* only if_ a {@linkcode Comment} was found.
|
||||
*/
|
||||
const rawCommentData: SetOptional<CommentData, 'comment'>[] = commentFinders.map(
|
||||
({getter, commentSource}) => ({
|
||||
comment: getter({refl, comment, knownBuiltinMethods: knownMethods}),
|
||||
commentSource,
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* The result of filtering out any {@linkcode CommentData} objects which do not have a `comment` property
|
||||
* _or_ which do but the {@linkcode Comment} property does not have any visible components.
|
||||
*
|
||||
* A comment without visible components is defined by TypeDoc as one without a "summary" (or an
|
||||
* empty summary) and one which has no block tags. Block tags, by definition, display something.
|
||||
*/
|
||||
const commentData: CommentData[] = rawCommentData.filter(({comment}) =>
|
||||
comment?.hasVisibleComponent()
|
||||
) as CommentData[];
|
||||
|
||||
// the first summary found; this is where precedence comes in to play
|
||||
const summaryCommentData = commentData.find(({comment}) => comment.summary.length);
|
||||
|
||||
// in this bit we want to pull all block tags from all comments, and see which have content and
|
||||
// which don't. for each tag name (e.g., `@returns`), prefer the tag with content. if there is
|
||||
// no such tag, just pick the first one
|
||||
const allBlockTags = commentData.flatMap(({comment}) => comment.blockTags);
|
||||
const tagsByTag = _.groupBy(allBlockTags, 'tag');
|
||||
const finalBlockTags: CommentTag[] = [];
|
||||
for (const tags of Object.values(tagsByTag)) {
|
||||
if (tags.length === 1) {
|
||||
finalBlockTags.push(_.first(tags)!);
|
||||
} else if (tags.length > 1) {
|
||||
// prefer a tag with content
|
||||
const tag = tags.find((t) => t.content.length) ?? tags[0];
|
||||
finalBlockTags.push(tag);
|
||||
} else {
|
||||
if (refl) {
|
||||
log.warn(
|
||||
'No comment tags found in derived comment for %s "%s". This is a bug',
|
||||
refl.constructor.name,
|
||||
refl.name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we have a summary comment and some block tags, and the block tags are not the same as the
|
||||
// summary comment's block tags, then create a new comment merged from the summary and all block
|
||||
// tags.
|
||||
if (
|
||||
summaryCommentData &&
|
||||
finalBlockTags.length &&
|
||||
_.xor(summaryCommentData.comment.blockTags, finalBlockTags).length
|
||||
) {
|
||||
const comment = cloneComment(summaryCommentData.comment, finalBlockTags);
|
||||
|
||||
return {comment, commentSource: CommentSource.Multiple};
|
||||
}
|
||||
|
||||
// pick the first one found
|
||||
return _.first(commentData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones a comment. Mostly. I think.
|
||||
* @param comment Comment to clone
|
||||
* @param blockTags Block tags to use; if not provided, the comment's block tags will be used (these
|
||||
* are also cloned)
|
||||
* @returns A new comment
|
||||
*/
|
||||
export function cloneComment(comment: Comment, blockTags?: CommentTag[]): Comment {
|
||||
const newComment = new Comment(
|
||||
Comment.cloneDisplayParts(comment.summary),
|
||||
(blockTags ?? comment.blockTags).map((blockTag) => {
|
||||
const tag = new CommentTag(blockTag.tag, Comment.cloneDisplayParts(blockTag.content));
|
||||
tag.name = blockTag.name;
|
||||
return tag;
|
||||
})
|
||||
);
|
||||
newComment.modifierTags = new Set(comment.modifierTags);
|
||||
|
||||
return newComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to narrow a string to a key of {@linkcode ExampleLanguage}
|
||||
* @param value anything
|
||||
* @returns `true` if the value is a valid language
|
||||
*/
|
||||
function isValidLang(value: any): value is keyof typeof ExampleLanguage {
|
||||
return value in ExampleLanguage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds any `@example` tags within a comment and creates an {@linkcode ExtractedExamples} object
|
||||
* for them. Creates a new comment with the examples removed, so we can handle them in a
|
||||
* custom way via our theme.
|
||||
*
|
||||
* Note that the same `Comment` can be passed multiple times (if it is displayed in multiple
|
||||
* modules). Our choices are either to a) clone the comment each time, or b) memoize the function
|
||||
* in order to avoid a problem wherein the comment has its examples extracted on the first call, but
|
||||
* subsequent calls will not contain any examples (since they've already been extracted). It may be
|
||||
* more memory-friendly to memoize, but it may not be the safest thing to do (unclear).
|
||||
*
|
||||
* @param comment A comment to extract examples from
|
||||
* @returns New comment (with examples removed)
|
||||
*/
|
||||
export const extractExamples = (comment?: Comment): ExtractedExamples | undefined => {
|
||||
if (!comment) {
|
||||
return;
|
||||
}
|
||||
|
||||
comment = cloneComment(comment);
|
||||
|
||||
const exampleTags = comment.getTags(NAME_EXAMPLE_TAG);
|
||||
|
||||
if (!exampleTags) {
|
||||
return {comment};
|
||||
}
|
||||
|
||||
const examples = exampleTags.flatMap(({content}) =>
|
||||
content.reduce((examples, {text}) => {
|
||||
const match = text.match(FENCED_CODE_BLOCK_REGEX);
|
||||
if (match) {
|
||||
const matchedLang = match[1] ?? 'js';
|
||||
const lang = isValidLang(matchedLang) ? matchedLang : 'js';
|
||||
return [
|
||||
...examples,
|
||||
{
|
||||
lang: ExampleLanguage[lang],
|
||||
text,
|
||||
},
|
||||
];
|
||||
}
|
||||
return examples;
|
||||
}, [] as Example[])
|
||||
);
|
||||
|
||||
if (examples.length) {
|
||||
comment.removeTags(NAME_EXAMPLE_TAG);
|
||||
return {examples, comment};
|
||||
}
|
||||
return {comment};
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
import {Comment} from 'typedoc';
|
||||
import {isDeclarationReflection} from '../../guards';
|
||||
import {CommentSource} from '../types';
|
||||
import {CommentFinder} from './types';
|
||||
|
||||
const knownDeclarationRefComments: Map<string, Comment> = new Map();
|
||||
|
||||
/**
|
||||
* Array of strategies for finding comments. They can come from a variety of places depending on
|
||||
* where a variable is declared, if it overrides a method, if it implements a method in an
|
||||
* interface, etc.
|
||||
*
|
||||
* These have an order (of precedence), which is why this is an array.
|
||||
*/
|
||||
const MethodCommentFinders: Readonly<CommentFinder[]> = [
|
||||
{
|
||||
/**
|
||||
* @returns The comment on the method itself
|
||||
*/
|
||||
getter({refl}) {
|
||||
return refl?.comment?.hasVisibleComponent() ? refl.comment : undefined;
|
||||
},
|
||||
commentSource: CommentSource.Method,
|
||||
},
|
||||
{
|
||||
/**
|
||||
* @returns The comment from the method's signature (may be inherited)
|
||||
*/
|
||||
getter: ({refl}) => {
|
||||
if (isDeclarationReflection(refl)) {
|
||||
return refl.getAllSignatures().find((sig) => sig.comment?.summary)?.comment;
|
||||
}
|
||||
},
|
||||
commentSource: CommentSource.MethodSignature,
|
||||
},
|
||||
{
|
||||
/**
|
||||
* @returns The comment from some method that this one implements or overwrites or w/e;
|
||||
* typically coming from interfaces in `@appium/types`
|
||||
*/
|
||||
getter: ({refl, knownBuiltinMethods}) => {
|
||||
if (!refl) {
|
||||
return;
|
||||
}
|
||||
if (knownDeclarationRefComments.has(refl.name)) {
|
||||
return knownDeclarationRefComments.get(refl.name);
|
||||
}
|
||||
// if the `refl` is a known command, it should be in `knownMethods`;
|
||||
// if it isn't (or doesn't exist) we aren't going to display it anyway, so abort
|
||||
const otherRefl = refl && knownBuiltinMethods?.get(refl.name);
|
||||
if (!otherRefl) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if `otherRefl` exists, then the comment could live in several places,
|
||||
// which happen to be findable by the _other_ `CommentFinder`s. we avoid
|
||||
// `CommentSourceType.OtherMethod`, which corresponds to _this_ `CommentFinder`
|
||||
// to avoid recursion (for now).
|
||||
//
|
||||
// after looping thru the finders, if we have a comment in the list of `commentData`
|
||||
// objects, return the first one found.
|
||||
const comment = MethodCommentFinders.filter(
|
||||
({commentSource}) => commentSource !== CommentSource.OtherMethod
|
||||
)
|
||||
.map(({getter, commentSource}) => ({
|
||||
comment: getter({refl: otherRefl, knownBuiltinMethods: knownBuiltinMethods}),
|
||||
commentSource,
|
||||
}))
|
||||
.find(({comment}) => Boolean(comment))?.comment;
|
||||
if (comment) {
|
||||
knownDeclarationRefComments.set(refl.name, comment);
|
||||
}
|
||||
return comment;
|
||||
},
|
||||
commentSource: CommentSource.OtherMethod,
|
||||
},
|
||||
];
|
||||
|
||||
export default MethodCommentFinders;
|
||||
@@ -0,0 +1,54 @@
|
||||
import {ParameterReflection} from 'typedoc';
|
||||
import {isCallSignatureReflection, isParameterReflection} from '../../guards';
|
||||
import {findCallSignature} from '../../utils';
|
||||
import {CommentSource} from '../types';
|
||||
import {CommentFinder} from './types';
|
||||
|
||||
export default [
|
||||
{
|
||||
getter({refl}) {
|
||||
if (!isParameterReflection(refl)) {
|
||||
return;
|
||||
}
|
||||
return refl.comment?.hasVisibleComponent() ? refl.comment : undefined;
|
||||
},
|
||||
commentSource: CommentSource.Parameter,
|
||||
},
|
||||
{
|
||||
/**
|
||||
* @returns The comment from some method that this one implements or overwrites or w/e;
|
||||
* typically coming from interfaces in `@appium/types`
|
||||
*/
|
||||
getter({refl, knownBuiltinMethods}) {
|
||||
if (!isParameterReflection(refl) || !knownBuiltinMethods) {
|
||||
return;
|
||||
}
|
||||
const signatureRefl = refl.parent;
|
||||
if (!isCallSignatureReflection(signatureRefl)) {
|
||||
return;
|
||||
}
|
||||
const methodRefl = signatureRefl.parent;
|
||||
const paramIdx = signatureRefl.parameters?.indexOf(refl);
|
||||
if (paramIdx === undefined || paramIdx < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const builtinMethodRefl = knownBuiltinMethods.get(methodRefl.name);
|
||||
|
||||
if (!builtinMethodRefl) {
|
||||
return;
|
||||
}
|
||||
|
||||
const builtinParams = findCallSignature(builtinMethodRefl)?.parameters;
|
||||
|
||||
if (!builtinParams?.[paramIdx]) {
|
||||
return;
|
||||
}
|
||||
const builtinParam = builtinParams[paramIdx];
|
||||
if (builtinParam.comment?.hasVisibleComponent()) {
|
||||
return builtinParam.comment;
|
||||
}
|
||||
},
|
||||
commentSource: CommentSource.BuiltinParameter,
|
||||
},
|
||||
] as Readonly<CommentFinder[]>;
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Strategies to derive a comment from a `SignatureReflection`
|
||||
* @module
|
||||
*/
|
||||
|
||||
import {isCallSignatureReflection} from '../../guards';
|
||||
import {findCallSignature} from '../../utils';
|
||||
import {CommentSource} from '../types';
|
||||
import {CommentFinder} from './types';
|
||||
|
||||
export default [
|
||||
{
|
||||
getter({refl}) {
|
||||
if (!isCallSignatureReflection(refl)) {
|
||||
return;
|
||||
}
|
||||
if (refl.comment?.hasVisibleComponent()) {
|
||||
return refl.comment;
|
||||
}
|
||||
},
|
||||
commentSource: CommentSource.Signature,
|
||||
},
|
||||
{
|
||||
/**
|
||||
* @returns The comment from some method that this one implements or overwrites or w/e;
|
||||
* typically coming from interfaces in `@appium/types`
|
||||
*/
|
||||
getter({refl, knownBuiltinMethods}) {
|
||||
if (!isCallSignatureReflection(refl)) {
|
||||
return;
|
||||
}
|
||||
const methodRefl = refl.parent;
|
||||
const builtinMethodRefl = knownBuiltinMethods?.get(methodRefl.name);
|
||||
if (!builtinMethodRefl) {
|
||||
return;
|
||||
}
|
||||
const builtinSig = findCallSignature(builtinMethodRefl);
|
||||
|
||||
if (!builtinSig) {
|
||||
return;
|
||||
}
|
||||
if (builtinSig.comment?.hasVisibleComponent()) {
|
||||
return builtinSig.comment;
|
||||
}
|
||||
},
|
||||
commentSource: CommentSource.BuiltinSignature,
|
||||
},
|
||||
] as Readonly<CommentFinder[]>;
|
||||
@@ -0,0 +1,31 @@
|
||||
import {Comment, Reflection} from 'typedoc';
|
||||
import {CommentSource, KnownMethods} from '../types';
|
||||
|
||||
/**
|
||||
* A function which queries some object for a {@linkcode Comment}, and if found,
|
||||
* its {@linkcode Comment.summary summary}.
|
||||
* @internal
|
||||
*/
|
||||
export interface CommentFinder {
|
||||
getter: (opts: CommentFinderGetterOptions) => Comment | undefined;
|
||||
commentSource: CommentSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@linkcode CommentFinder.getter} functions
|
||||
* @internal
|
||||
*/
|
||||
export interface CommentFinderGetterOptions {
|
||||
refl?: Reflection;
|
||||
comment?: Comment;
|
||||
knownBuiltinMethods?: KnownMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper around a found {@linkcode Comment} which also contains a {@linkcode CommentSource}
|
||||
* so we can observe where the `Comment` came from.
|
||||
*/
|
||||
export interface CommentData {
|
||||
comment: Comment;
|
||||
commentSource: CommentSource;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import {DeclarationReflection, ReflectionKind} from 'typedoc';
|
||||
import {
|
||||
isCommandMethodDeclarationReflection,
|
||||
isCommandPropDeclarationReflection,
|
||||
isExecMethodDefParamsPropDeclarationReflection,
|
||||
} from '../guards';
|
||||
@@ -34,7 +35,7 @@ export interface ConvertExecuteMethodMapOpts {
|
||||
/**
|
||||
* Builtin methods from `@appium/types`
|
||||
*/
|
||||
builtinMethods: KnownMethods;
|
||||
knownMethods: KnownMethods;
|
||||
/**
|
||||
* If `true`, do not add a route if the method it references cannot be found
|
||||
*/
|
||||
@@ -54,7 +55,7 @@ export function convertExecuteMethodMap({
|
||||
log,
|
||||
parentRefl,
|
||||
execMethodMapRefl,
|
||||
builtinMethods,
|
||||
knownMethods,
|
||||
strict = false,
|
||||
isPluginCommand = false,
|
||||
}: ConvertExecuteMethodMapOpts): ExecMethodDataSet {
|
||||
@@ -98,20 +99,21 @@ export function convertExecuteMethodMap({
|
||||
const requiredParams = convertRequiredCommandParams(paramsProp);
|
||||
const optionalParams = convertOptionalCommandParams(paramsProp);
|
||||
|
||||
const methodRefl = builtinMethods.get(command);
|
||||
const methodRefl = knownMethods.get(command);
|
||||
|
||||
if (strict && !methodRefl) {
|
||||
log.error('No method found for command "%s" from script "%s"', command, script);
|
||||
if (!methodRefl) {
|
||||
if (strict) {
|
||||
log.error('No method found for command "%s" from script "%s"', command, script);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const commentData = deriveComment({refl: methodRefl, comment, knownMethods: builtinMethods});
|
||||
const commentData = deriveComment({refl: methodRefl, comment, knownMethods: knownMethods});
|
||||
|
||||
commandRefs.add(
|
||||
new ExecMethodData(log, command, script, {
|
||||
new ExecMethodData(log, command, methodRefl, script, {
|
||||
requiredParams,
|
||||
optionalParams,
|
||||
refl: methodRefl,
|
||||
comment: commentData?.comment,
|
||||
commentSource: commentData?.commentSource,
|
||||
isPluginCommand,
|
||||
|
||||
@@ -17,15 +17,15 @@ import {
|
||||
RouteMap,
|
||||
} from '../model';
|
||||
import {BaseConverter} from './base-converter';
|
||||
import {convertOverrides} from './overrides';
|
||||
import {convertExecuteMethodMap} from './exec-method-map';
|
||||
import {convertMethodMap} from './method-map';
|
||||
import {convertOverrides} from './overrides';
|
||||
import {ClassDeclarationReflection, KnownMethods} from './types';
|
||||
import {
|
||||
filterChildrenByGuard,
|
||||
findChildByGuard,
|
||||
findChildByNameAndGuard,
|
||||
findAsyncMethodsInReflection,
|
||||
findCommandMethodsInReflection,
|
||||
} from './utils';
|
||||
|
||||
/**
|
||||
@@ -152,22 +152,22 @@ export class ExternalConverter extends BaseConverter<ProjectCommands> {
|
||||
findChildByGuard(classRefl, isConstructorDeclarationReflection)
|
||||
);
|
||||
|
||||
const methods = findAsyncMethodsInReflection(classRefl);
|
||||
const classMethods: KnownMethods = findCommandMethodsInReflection(classRefl);
|
||||
|
||||
if (!methods.size) {
|
||||
if (!classMethods.size) {
|
||||
// may or may not be expected
|
||||
log.verbose('No methods found');
|
||||
continue;
|
||||
}
|
||||
|
||||
log.verbose('Analyzing %s', pluralize('method', methods.size, true));
|
||||
log.verbose('Analyzing %s', pluralize('method', classMethods.size, true));
|
||||
|
||||
const newRouteMap = this.#findAndConvertNewMethodMap(classRefl, methods, log, isPlugin);
|
||||
const newRouteMap = this.#findAndConvertNewMethodMap(classRefl, classMethods, log, isPlugin);
|
||||
routeMap = new Map([...routeMap, ...newRouteMap]);
|
||||
|
||||
const newExecMethodData = this.#findAndConvertExecMethodMap(
|
||||
classRefl,
|
||||
methods,
|
||||
classMethods,
|
||||
log,
|
||||
isPlugin
|
||||
);
|
||||
@@ -177,7 +177,7 @@ export class ExternalConverter extends BaseConverter<ProjectCommands> {
|
||||
? convertOverrides({
|
||||
log,
|
||||
parentRefl: classRefl,
|
||||
classMethods: methods,
|
||||
classMethods,
|
||||
builtinMethods: this.builtinMethods,
|
||||
newRouteMap,
|
||||
newExecMethodMap: execMethodData,
|
||||
@@ -220,7 +220,7 @@ export class ExternalConverter extends BaseConverter<ProjectCommands> {
|
||||
log,
|
||||
parentRefl,
|
||||
execMethodMapRefl,
|
||||
builtinMethods: methods,
|
||||
knownMethods: methods,
|
||||
strict: true,
|
||||
isPluginCommand,
|
||||
});
|
||||
@@ -253,8 +253,8 @@ export class ExternalConverter extends BaseConverter<ProjectCommands> {
|
||||
log,
|
||||
methodMapRefl: newMethodMapRefl,
|
||||
parentRefl,
|
||||
methods,
|
||||
knownMethods: this.builtinMethods,
|
||||
knownClassMethods: methods,
|
||||
knownBuiltinMethods: this.builtinMethods,
|
||||
strict: true,
|
||||
isPluginCommand,
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ export interface ConvertMethodMapOpts {
|
||||
/**
|
||||
* All builtin methods from `@appium/types`
|
||||
*/
|
||||
knownMethods?: KnownMethods;
|
||||
knownBuiltinMethods: KnownMethods;
|
||||
/**
|
||||
* Logger
|
||||
*/
|
||||
@@ -37,7 +37,7 @@ export interface ConvertMethodMapOpts {
|
||||
/**
|
||||
* All async methods in `parentRefl`
|
||||
*/
|
||||
methods: KnownMethods;
|
||||
knownClassMethods: KnownMethods;
|
||||
/**
|
||||
* The parent of `methodMapRef`; could be a class or module
|
||||
*/
|
||||
@@ -62,8 +62,8 @@ export function convertMethodMap({
|
||||
log,
|
||||
methodMapRefl,
|
||||
parentRefl,
|
||||
methods,
|
||||
knownMethods = new Map(),
|
||||
knownClassMethods,
|
||||
knownBuiltinMethods,
|
||||
strict = false,
|
||||
isPluginCommand = false,
|
||||
}: ConvertMethodMapOpts): RouteMap {
|
||||
@@ -108,18 +108,25 @@ export function convertMethodMap({
|
||||
|
||||
const command = String(commandProp.type.value);
|
||||
|
||||
const method = methods.get(command);
|
||||
const method = knownClassMethods.get(command);
|
||||
|
||||
if (strict && !method) {
|
||||
log.warn('(%s) No method found for command "%s"; this is a bug', parentRefl.name, command);
|
||||
if (!method) {
|
||||
if (strict) {
|
||||
log.error(
|
||||
'(%s) No method found for command "%s"; this may be a bug',
|
||||
parentRefl.name,
|
||||
command
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const commentData = deriveComment({
|
||||
refl: method,
|
||||
comment: mapComment,
|
||||
knownMethods,
|
||||
knownMethods: knownBuiltinMethods,
|
||||
});
|
||||
const {comment, commentSource} = commentData ?? {};
|
||||
|
||||
const payloadParamsProp = findChildByGuard(
|
||||
httpMethodProp,
|
||||
@@ -132,14 +139,14 @@ export function convertMethodMap({
|
||||
const commandSet: CommandSet = routes.get(route) ?? new Set();
|
||||
|
||||
commandSet.add(
|
||||
new CommandData(log, command, httpMethod, route, {
|
||||
new CommandData(log, command, method, httpMethod, route, {
|
||||
requiredParams,
|
||||
optionalParams,
|
||||
comment: commentData?.comment,
|
||||
commentSource: commentData?.commentSource,
|
||||
refl: method,
|
||||
comment,
|
||||
commentSource,
|
||||
parentRefl,
|
||||
isPluginCommand,
|
||||
knownBuiltinMethods,
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -71,18 +71,18 @@ export function convertOverrides({
|
||||
// this must be defined, because if it wasn't then builtinRoutes would be empty and we'd continue the loop
|
||||
const commandSet = builtinCommands.routeMap.get(route)!;
|
||||
for (const commandData of commandSet) {
|
||||
const method = classMethods.get(command);
|
||||
if (!method) {
|
||||
const methodRefl = classMethods.get(command);
|
||||
if (!methodRefl) {
|
||||
log.warn('No such method "%s"; this is a bug', command);
|
||||
continue;
|
||||
}
|
||||
const commentData = deriveComment({
|
||||
refl: method,
|
||||
refl: methodRefl,
|
||||
knownMethods: builtinMethods,
|
||||
});
|
||||
const newCommandData = commandData.clone({
|
||||
refl: method,
|
||||
const newCommandData = commandData.clone(methodRefl, {
|
||||
parentRefl,
|
||||
knownBuiltinMethods: builtinMethods,
|
||||
...commentData,
|
||||
});
|
||||
log.verbose('Linked route %s %s for command "%s"', commandData.httpMethod, route, command);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import {ValueOf} from 'type-fest';
|
||||
import {
|
||||
Comment,
|
||||
DeclarationReflection,
|
||||
@@ -276,3 +277,87 @@ export type ExecuteMethodCommandReflection = CommandReflection & {
|
||||
kind: typeof AppiumPluginReflectionKind.ExecuteMethod;
|
||||
script: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Languages which can be used in example code blocks
|
||||
*
|
||||
* The key is the identifier used in a fenced code block, and the value is the "display" value
|
||||
*/
|
||||
export const ExampleLanguage = Object.freeze({
|
||||
ts: 'TypeScript',
|
||||
typescript: 'TypeScript',
|
||||
js: 'JavaScript',
|
||||
javascript: 'JavaScript',
|
||||
py: 'Python',
|
||||
python: 'Python',
|
||||
rb: 'Ruby',
|
||||
ruby: 'Ruby',
|
||||
java: 'Java',
|
||||
}) satisfies Record<string, string>;
|
||||
|
||||
/**
|
||||
* This is basically a fenced code block split into two portions: the text itself and the language
|
||||
* specified in the opening fence. Part of {@linkcode ExtractedExamples}
|
||||
*/
|
||||
export interface Example {
|
||||
text: string;
|
||||
lang: ValueOf<typeof ExampleLanguage>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A pair of a comment and any examples which were removed from it. Returned by {@linkcode extractExamples}
|
||||
*/
|
||||
export interface ExtractedExamples {
|
||||
examples?: Example[];
|
||||
comment: Comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mainly for debugging purposes, these tell us (roughly) where a comment came from.
|
||||
* In the case of {@linkcode CommentSource.Multiple}, the comment was derived
|
||||
* from multiple sources.
|
||||
*/
|
||||
export enum CommentSource {
|
||||
/**
|
||||
* This is a comment directly on the `DeclarationReference` itself.
|
||||
*
|
||||
* It's unclear to me why sometimes comments are attached to the method proper or its signature;
|
||||
* might have something to do with `ReferenceType`.
|
||||
*/
|
||||
Method = 'method',
|
||||
/**
|
||||
* A comment attached to the method's call signature.
|
||||
*/
|
||||
MethodSignature = 'method-signature',
|
||||
/**
|
||||
* A comment from "elsewhere", which is usually a method map or exec method map.
|
||||
*/
|
||||
OtherComment = 'other-comment',
|
||||
/**
|
||||
* A comment coming out of the `@appium/types` package; specifically a method in `ExternalDriver`
|
||||
*/
|
||||
OtherMethod = 'builtin-interface',
|
||||
/**
|
||||
* A comment _built_ from any of the above sources from one or more `DeclarationReference`
|
||||
* objects. For example, the summary (description) of an implementation of `doubleClick()` and
|
||||
* the `@example` block tag from the `ExternalDriver` interface.
|
||||
*/
|
||||
Multiple = 'multiple',
|
||||
|
||||
/**
|
||||
* A comment found in a `ParameterReflection`
|
||||
*/
|
||||
Parameter = 'parameter',
|
||||
/**
|
||||
* A comment found in a `ParameterReflection` within a builtin method (e.g., from `ExternalDriver`)
|
||||
*/
|
||||
BuiltinParameter = 'builtin-parameter',
|
||||
/**
|
||||
* A comment found in a `SignatureReflection` within a builtin method
|
||||
*/
|
||||
BuiltinSignature = 'builtin-signature',
|
||||
/**
|
||||
* A comment found in a `SignatureReflection`, but not via a method.
|
||||
*/
|
||||
Signature = 'signature',
|
||||
}
|
||||
|
||||
@@ -3,16 +3,31 @@
|
||||
* @module
|
||||
*/
|
||||
|
||||
import {DeclarationReflection, LiteralType, ProjectReflection, ReflectionKind} from 'typedoc';
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
isAsyncMethodDeclarationReflection,
|
||||
DeclarationReflection,
|
||||
LiteralType,
|
||||
ParameterReflection,
|
||||
ProjectReflection,
|
||||
Reflection,
|
||||
ReflectionFlag,
|
||||
ReflectionFlags,
|
||||
ReflectionKind,
|
||||
SignatureReflection,
|
||||
SomeType,
|
||||
TypeParameterReflection,
|
||||
} from 'typedoc';
|
||||
import {
|
||||
isCommandMethodDeclarationReflection,
|
||||
isMethodDefParamNamesDeclarationReflection,
|
||||
isReflectionWithReflectedType,
|
||||
} from '../guards';
|
||||
import {ParentReflection} from '../model';
|
||||
import {deriveComment} from './comment';
|
||||
import {NAME_OPTIONAL, NAME_REQUIRED} from './external';
|
||||
import {
|
||||
ClassDeclarationReflection,
|
||||
CommandMethodDeclarationReflection,
|
||||
Guard,
|
||||
InterfaceDeclarationReflection,
|
||||
KnownMethods,
|
||||
@@ -103,15 +118,15 @@ export function filterChildrenByKind<T extends DeclarationReflection>(
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds _all_ async methods in a class or interface
|
||||
* Finds _all_ async command methods in a class or interface
|
||||
* @param refl Class reflection
|
||||
* @returns Map of method names to method reflections
|
||||
*/
|
||||
export function findAsyncMethodsInReflection(
|
||||
export function findCommandMethodsInReflection(
|
||||
refl: ClassDeclarationReflection | InterfaceDeclarationReflection
|
||||
): KnownMethods {
|
||||
return new Map(
|
||||
filterChildrenByGuard(refl, isAsyncMethodDeclarationReflection).map((method) => [
|
||||
filterChildrenByGuard(refl, isCommandMethodDeclarationReflection).map((method) => [
|
||||
method.name,
|
||||
method,
|
||||
])
|
||||
@@ -171,3 +186,185 @@ export function convertRequiredCommandParams(
|
||||
): string[] {
|
||||
return convertCommandParams(NAME_REQUIRED, methodDefRefl);
|
||||
}
|
||||
|
||||
/**
|
||||
* List of fields to shallow copy from a `ParameterReflection` to a clone
|
||||
* @internal
|
||||
*/
|
||||
const PARAMETER_REFLECTION_CLONE_FIELDS = [
|
||||
'anchor',
|
||||
'cssClasses',
|
||||
'defaultValue',
|
||||
'hasOwnDocument',
|
||||
'label',
|
||||
'originalName',
|
||||
'sources',
|
||||
'type',
|
||||
'url',
|
||||
];
|
||||
|
||||
/**
|
||||
* Clones a `ParameterReflection`.
|
||||
*
|
||||
* @privateRemarks I think.
|
||||
* @param pRefl A `ParameterReflection`
|
||||
* @param param Desired name of parameter
|
||||
* @param parent Custom signature reflection
|
||||
* @param knownMethods Builtin methods for aggregating comments
|
||||
* @param optional If the parameter is considered "optional"
|
||||
* @returns A new `ParameterReflection` based on the first
|
||||
*/
|
||||
export function cloneParameterReflection(
|
||||
pRefl: ParameterReflection,
|
||||
param: string,
|
||||
parent: SignatureReflection,
|
||||
knownMethods?: KnownMethods,
|
||||
optional = false
|
||||
) {
|
||||
const newPRefl = new ParameterReflection(param, ReflectionKind.Parameter, parent);
|
||||
_.assign(newPRefl, _.pick(pRefl, PARAMETER_REFLECTION_CLONE_FIELDS));
|
||||
// attempt to derive param comments. these are "summary" comments only,
|
||||
// so we do not need to worry about combining block/summary comments like with methods.
|
||||
newPRefl.comment = deriveComment({
|
||||
refl: pRefl,
|
||||
knownMethods: knownMethods,
|
||||
comment: pRefl.comment,
|
||||
})?.comment;
|
||||
// there doesn't seem to be a straightforward way to clone flags.
|
||||
newPRefl.flags = new ReflectionFlags(...pRefl.flags);
|
||||
newPRefl.flags.setFlag(ReflectionFlag.Optional, optional);
|
||||
return newPRefl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones a type parameter reflection
|
||||
* @param tPRefl Type parameter reflection
|
||||
* @param parentRefl Parent
|
||||
* @returns A clone of the original type parameter reflection
|
||||
*/
|
||||
export function cloneTypeParameterReflection(
|
||||
tPRefl: TypeParameterReflection,
|
||||
parentRefl: Reflection
|
||||
) {
|
||||
return new TypeParameterReflection(
|
||||
tPRefl.name,
|
||||
tPRefl.type,
|
||||
tPRefl.default,
|
||||
parentRefl,
|
||||
tPRefl.varianceModifier
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* List of fields to shallow copy from a `SignatureReflection` to a clone
|
||||
* @internal
|
||||
*/
|
||||
const SIGNATURE_REFLECTION_CLONE_FIELDS = [
|
||||
'anchor',
|
||||
'comment',
|
||||
'flags',
|
||||
'hasOwnDocument',
|
||||
'implementationOf',
|
||||
'inheritedFrom',
|
||||
'kindString',
|
||||
'label',
|
||||
'originalName',
|
||||
'overwrites',
|
||||
'parameters',
|
||||
'sources',
|
||||
'typeParameters',
|
||||
'url',
|
||||
];
|
||||
|
||||
/**
|
||||
* This loops over a list of command parameter names as defined in the method/execute map and attempts
|
||||
* to create a new `ParameterReflection` for each, based on the given data.
|
||||
*
|
||||
* Because the command param names are essentially properties of a JSON object and the
|
||||
* `ParameterReflection` instances represent the arguments of a method, we must match them by
|
||||
* index. In JS, optional arguments cannot become before required arguments in a function
|
||||
* signature, so we can do those first. If there are _more_ method arguments than command param
|
||||
* names, we toss them out, because they may not be part of the public API.
|
||||
* @param sig Signature reflection
|
||||
* @param opts Options
|
||||
* @returns List of refls with names matching `commandParams`, throwing out any extra refls
|
||||
*/
|
||||
export function createNewParamRefls(
|
||||
sig: SignatureReflection,
|
||||
opts: CreateNewParamReflsOpts = {}
|
||||
): ParameterReflection[] {
|
||||
let {builtinMethods = new Map(), commandParams = [], isOptional, isPluginCommand} = opts;
|
||||
if (!sig.parameters?.length) {
|
||||
// this should not happen, I think?
|
||||
return [];
|
||||
}
|
||||
// a plugin command's method has two leading args we don't need
|
||||
const newParamRefls: ParameterReflection[] = [];
|
||||
const pRefls = isPluginCommand ? sig.parameters.slice(2) : sig.parameters;
|
||||
for (const [idx, param] of commandParams.entries()) {
|
||||
const pRefl = pRefls[idx];
|
||||
if (pRefl) {
|
||||
const newPRefl = cloneParameterReflection(pRefl, param, sig, builtinMethods, isOptional);
|
||||
newParamRefls.push(newPRefl);
|
||||
}
|
||||
}
|
||||
return newParamRefls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones a `SignatureReflection` with a new parent and type.
|
||||
*
|
||||
* @privateRemarks I'm not sure this is sufficient.
|
||||
* @param sig A `SignatureReflection` to clone
|
||||
* @param parent The desired parent of the new `SignatureReflection`
|
||||
* @param type The desired type of the new `SignatureReflection`; if not provided, the original type
|
||||
* will be used
|
||||
* @returns A clone of `sig` with the given parent and type
|
||||
*/
|
||||
export function cloneSignatureReflection(
|
||||
sig: SignatureReflection,
|
||||
parent: CommandMethodDeclarationReflection,
|
||||
type?: SomeType
|
||||
) {
|
||||
const newSig = new SignatureReflection(sig.name, ReflectionKind.CallSignature, parent);
|
||||
|
||||
_.assign(newSig, _.pick(sig, SIGNATURE_REFLECTION_CLONE_FIELDS));
|
||||
|
||||
if (newSig.parameters) {
|
||||
newSig.parameters.map((p) => cloneParameterReflection(p, p.name, newSig));
|
||||
}
|
||||
if (newSig.typeParameters) {
|
||||
newSig.typeParameters.map((tP) => cloneTypeParameterReflection(tP, newSig));
|
||||
}
|
||||
|
||||
if (type) {
|
||||
newSig.type = type;
|
||||
}
|
||||
return newSig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@linkcode createNewParamRefls}
|
||||
*/
|
||||
export interface CreateNewParamReflsOpts {
|
||||
/**
|
||||
* Map of known methods
|
||||
*/
|
||||
builtinMethods?: KnownMethods;
|
||||
/**
|
||||
* If the parameter is marked as optional in the method def
|
||||
*/
|
||||
isOptional?: boolean;
|
||||
/**
|
||||
* If the class containing the method is a Plugin.
|
||||
*
|
||||
* This is important because the `PluginCommand` type has a different signature than the
|
||||
* `DriverCommand` type; the former always has two specific arguments heading its parameter list,
|
||||
* and we do not need to include in the generated docs.
|
||||
*/
|
||||
isPluginCommand?: boolean;
|
||||
/**
|
||||
* List of parameter names from method def
|
||||
*/
|
||||
commandParams?: string[];
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
ContainerReflection,
|
||||
DeclarationReflection,
|
||||
LiteralType,
|
||||
ParameterReflection,
|
||||
ProjectReflection,
|
||||
ReferenceType,
|
||||
Reflection,
|
||||
@@ -280,7 +282,7 @@ export function isExternalDriverDeclarationReflection(
|
||||
* call signature returning a `Promise`.
|
||||
* @param value
|
||||
*/
|
||||
export function isAsyncMethodDeclarationReflection(
|
||||
export function isCommandMethodDeclarationReflection(
|
||||
value: any
|
||||
): value is CommandMethodDeclarationReflection {
|
||||
if (
|
||||
@@ -364,3 +366,11 @@ export function isBasePluginConstructorDeclarationReflection(
|
||||
: undefined;
|
||||
return ref?.name === `${NAME_BASE_PLUGIN}.constructor`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guard for {@linkcode ParameterReflection}
|
||||
* @param value any
|
||||
*/
|
||||
export function isParameterReflection(value: any): value is ParameterReflection {
|
||||
return value instanceof ParameterReflection;
|
||||
}
|
||||
|
||||
@@ -45,38 +45,48 @@ export const setup: (app: Application) => Application = _.flow(configureTheme, c
|
||||
/**
|
||||
* Finds commands and creates new reflections for them, adding them to the project.
|
||||
*
|
||||
* Resolves after {@linkcode Converter.EVENT_RESOLVE_BEGIN} emits and when it's finished.
|
||||
* Resolves after {@linkcode Converter.EVENT_RESOLVE_END} emits and when it's finished.
|
||||
* @param app Typedoc Application
|
||||
* @returns A {@linkcode ConvertResult} receipt from the conversion
|
||||
*/
|
||||
export async function convert(app: Application): Promise<ConvertResult> {
|
||||
return new Promise((resolve) => {
|
||||
app.converter.once(Converter.EVENT_RESOLVE_BEGIN, (ctx: Context) => {
|
||||
let extensionReflections: ExtensionReflection[] | undefined;
|
||||
let projectCommands: ProjectCommands | undefined;
|
||||
app.converter.once(
|
||||
Converter.EVENT_RESOLVE_END,
|
||||
/**
|
||||
* This listener _must_ trigger on {@linkcode Converter.EVENT_RESOLVE_END}, because TypeDoc's
|
||||
* internal plugins do some post-processing on the project's reflections--specifically, it
|
||||
* finds `@param` tags in a `SignatureReflection`'s `comment` and "moves" them into the
|
||||
* appropriate `ParameterReflections`. Without this in place, we won't be able aggregate
|
||||
* parameter comments and they will not display in the generated docs.
|
||||
*/
|
||||
(ctx: Context) => {
|
||||
let extensionReflections: ExtensionReflection[] | undefined;
|
||||
let projectCommands: ProjectCommands | undefined;
|
||||
|
||||
// we don't want to do this work if we're not using the custom theme!
|
||||
log = log ?? new AppiumPluginLogger(app.logger, NS);
|
||||
// we don't want to do this work if we're not using the custom theme!
|
||||
log = log ?? new AppiumPluginLogger(app.logger, NS);
|
||||
|
||||
// this should not be necessary given the `AppiumPluginOptionsReader` forces the issue, but
|
||||
// it's a safeguard nonetheless.
|
||||
if (app.renderer.themeName === THEME_NAME) {
|
||||
// this queries the declarations created by TypeDoc and extracts command information
|
||||
projectCommands = convertCommands(ctx, log);
|
||||
// this should not be necessary given the `AppiumPluginOptionsReader` forces the issue, but
|
||||
// it's a safeguard nonetheless.
|
||||
if (app.renderer.themeName === THEME_NAME) {
|
||||
// this queries the declarations created by TypeDoc and extracts command information
|
||||
projectCommands = convertCommands(ctx, log);
|
||||
|
||||
if (!projectCommands) {
|
||||
log.verbose('Skipping creation of reflections');
|
||||
resolve({ctx});
|
||||
return;
|
||||
if (!projectCommands) {
|
||||
log.verbose('Skipping creation of reflections');
|
||||
resolve({ctx});
|
||||
return;
|
||||
}
|
||||
// this creates new custom reflections from the data we gathered and registers them
|
||||
// with TypeDoc
|
||||
extensionReflections = createReflections(ctx, log, projectCommands);
|
||||
} else {
|
||||
log.warn(`Appium theme disabled! Use "theme: 'appium'" in your typedoc.json`);
|
||||
}
|
||||
// this creates new custom reflections from the data we gathered and registers them
|
||||
// with TypeDoc
|
||||
extensionReflections = createReflections(ctx, log, projectCommands);
|
||||
} else {
|
||||
log.warn(`Appium theme disabled! Use "theme: 'appium'" in your typedoc.json`);
|
||||
resolve({ctx, extensionReflections, projectCommands});
|
||||
}
|
||||
resolve({ctx, extensionReflections, projectCommands});
|
||||
});
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -85,7 +95,7 @@ export async function convert(app: Application): Promise<ConvertResult> {
|
||||
*/
|
||||
export interface ConvertResult {
|
||||
/**
|
||||
* Context at time of {@linkcode Context.EVENT_RESOLVE_BEGIN}
|
||||
* Context at time of {@linkcode Context.EVENT_RESOLVE_END}
|
||||
*/
|
||||
ctx: Context;
|
||||
/**
|
||||
|
||||
@@ -12,6 +12,23 @@
|
||||
import _ from 'lodash';
|
||||
import {format} from 'node:util';
|
||||
import {Logger, LogLevel} from 'typedoc';
|
||||
import path from 'path';
|
||||
|
||||
// this is a hack to get around package export restrictions.
|
||||
// since TypeDoc's ConsoleLogger is a private API, we will fall back to a vanilla `Logger`;
|
||||
// I'm not entirely sure what it will do.
|
||||
let ConsoleLogger: typeof Logger;
|
||||
|
||||
try {
|
||||
ConsoleLogger = require(path.join(
|
||||
path.dirname(require.resolve('typedoc/package.json')),
|
||||
'dist',
|
||||
'lib',
|
||||
'utils'
|
||||
)).ConsoleLogger;
|
||||
} catch {
|
||||
ConsoleLogger = Logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapping of TypeDoc {@linkcode LogLevel}s to method names.
|
||||
@@ -171,3 +188,11 @@ export class AppiumPluginLogger extends Logger {
|
||||
* Used internally by {@link AppiumPluginLogger.createChildLogger} to pass log messages to the parent.
|
||||
*/
|
||||
export type AppiumPluginParentLogger = (level: LogLevel, message: string, ...args: any[]) => void;
|
||||
|
||||
/**
|
||||
* Fallback logger. **Do not use this unless you really mean it.**
|
||||
*
|
||||
* Prefer to pass a `Logger` or `AppiumPluginLogger` instance to the constructor of the class you
|
||||
* are using or the function you are calling. If this makes the API too cumbersome, consider using this.
|
||||
*/
|
||||
export const fallbackLogger = new AppiumPluginLogger(new ConsoleLogger(), 'appium');
|
||||
|
||||
@@ -1,56 +1,20 @@
|
||||
import _ from 'lodash';
|
||||
import {Comment, DeclarationReflection, ParameterReflection, SignatureReflection} from 'typedoc';
|
||||
import {
|
||||
Comment,
|
||||
DeclarationReflection,
|
||||
ParameterReflection,
|
||||
ReflectionFlag,
|
||||
ReflectionFlags,
|
||||
ReflectionKind,
|
||||
SignatureReflection,
|
||||
} from 'typedoc';
|
||||
import {CommandMethodDeclarationReflection, CommentSourceType} from '../converter';
|
||||
import {isCallSignatureReflection, isReferenceType} from '../guards';
|
||||
cloneSignatureReflection,
|
||||
CommandMethodDeclarationReflection,
|
||||
CommentSource,
|
||||
createNewParamRefls,
|
||||
deriveComment,
|
||||
Example,
|
||||
extractExamples,
|
||||
KnownMethods,
|
||||
} from '../converter';
|
||||
import {isReferenceType} from '../guards';
|
||||
import {AppiumPluginLogger} from '../logger';
|
||||
import {findCallSignature} from '../utils';
|
||||
import {AllowedHttpMethod, Command, Route} from './types';
|
||||
|
||||
/**
|
||||
* List of fields to shallow copy from a `SignatureReflection` to a clone
|
||||
* @internal
|
||||
*/
|
||||
const SIGNATURE_REFLECTION_CLONE_FIELDS = [
|
||||
'anchor',
|
||||
'comment',
|
||||
'flags',
|
||||
'hasOwnDocument',
|
||||
'implementationOf',
|
||||
'inheritedFrom',
|
||||
'kindString',
|
||||
'label',
|
||||
'originalName',
|
||||
'overwrites',
|
||||
'parameters',
|
||||
'sources',
|
||||
'typeParameters',
|
||||
'url',
|
||||
];
|
||||
|
||||
/**
|
||||
* List of fields to shallow copy from a `ParameterReflection` to a clone
|
||||
* @internal
|
||||
*/
|
||||
const PARAMETER_REFLECTION_CLONE_FIELDS = [
|
||||
'anchor',
|
||||
'comment',
|
||||
'cssClasses',
|
||||
'defaultValue',
|
||||
'hasOwnDocument',
|
||||
'label',
|
||||
'originalName',
|
||||
'sources',
|
||||
'type',
|
||||
'url',
|
||||
];
|
||||
|
||||
/**
|
||||
* Abstract representation of metadata for some sort of Appium command
|
||||
*/
|
||||
@@ -73,108 +37,89 @@ export abstract class BaseCommandData {
|
||||
*
|
||||
* For debugging purposes mainly
|
||||
*/
|
||||
public readonly commentSource?: CommentSourceType;
|
||||
public readonly commentSource?: CommentSource;
|
||||
/**
|
||||
* Language-specific examples for the command, if any
|
||||
*/
|
||||
public readonly examples?: Example[];
|
||||
/**
|
||||
* If `true`, this command is from a Plugin (not a Driver)
|
||||
*/
|
||||
public readonly isPluginCommand: boolean;
|
||||
/**
|
||||
* Actual method reflection.
|
||||
*
|
||||
* @todo Determine if this should be required
|
||||
* Map of known builtin methods
|
||||
*/
|
||||
public readonly methodRefl?: CommandMethodDeclarationReflection;
|
||||
public readonly knownBuiltinMethods?: KnownMethods;
|
||||
/**
|
||||
* Actual method reflection.
|
||||
*/
|
||||
public readonly methodRefl: CommandMethodDeclarationReflection;
|
||||
/**
|
||||
* List of optional parameter names derived from a method map
|
||||
*/
|
||||
public readonly optionalParams?: string[];
|
||||
/**
|
||||
* Parameter reflections for this command's method declaration, to eventually be displayed in rendered docs
|
||||
*
|
||||
* These are _not_ the same objects as in the `parameters` property of a the call signature
|
||||
* reflection in `methodRefl`; the comments therein have been aggregated and the parameters have
|
||||
* been renamed and possibly truncated.
|
||||
*/
|
||||
public readonly parameters?: ParameterReflection[];
|
||||
/**
|
||||
* The thing which the method is a member of for documentation purposes
|
||||
*/
|
||||
public readonly parentRefl?: DeclarationReflection;
|
||||
/**
|
||||
* List of required parameter names derived from a method map
|
||||
*/
|
||||
public readonly requiredParams?: string[];
|
||||
|
||||
/**
|
||||
* Loops through signatures of the command's method declaration and returns the first that is a
|
||||
* `CallSignatureReflection` (if any). This is what we think of when we think "function signature"
|
||||
* Signature reflection for this command's method declaration, to eventually be displayed in
|
||||
* rendered docs
|
||||
*
|
||||
* `methodRefl` is a {@linkcode CommandMethodDeclarationReflection}, so it returns a `Promise<T>`, by
|
||||
* definition. This signature reflection is modified so that it returns `T` instead, since
|
||||
* `Promise`s don't make much sense in the rendered documentaion.
|
||||
*
|
||||
* The default TypeDoc output uses the original `SignatureReflection`, so you _will_ see
|
||||
* `Promise<T>` there.
|
||||
*/
|
||||
public static findCallSignature = _.memoize((cmd: BaseCommandData) =>
|
||||
cmd.methodRefl?.getAllSignatures()?.find(isCallSignatureReflection)
|
||||
);
|
||||
public readonly signature?: SignatureReflection;
|
||||
|
||||
/**
|
||||
* Returns a list of `ParameterReflection` objects in the command's method declaration;
|
||||
* rewrites them to prefer the method map parameter list (and the param names)
|
||||
**/
|
||||
public static rewriteParameters = _.memoize((cmd: BaseCommandData) => {
|
||||
if (!cmd.hasCommandParams) {
|
||||
return [];
|
||||
}
|
||||
const sig = BaseCommandData.findCallSignature(cmd);
|
||||
|
||||
if (!sig) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const pRefls = (cmd.isPluginCommand ? sig.parameters?.slice(2) : sig.parameters?.slice()) ?? [];
|
||||
|
||||
if (pRefls.length < cmd.requiredParams!.length + cmd.optionalParams!.length) {
|
||||
cmd.log.warn(
|
||||
'(%s) Method %s has fewer parameters (%d) than specified in the method map (%d)',
|
||||
cmd.parentRefl!.name,
|
||||
cmd.methodRefl!.name,
|
||||
pRefls.length,
|
||||
cmd.requiredParams!.length + cmd.optionalParams!.length
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This loops over the command parameter names as defined in the method/execute map and attempts
|
||||
* to associate a `ParameterReflection` object with each.
|
||||
*
|
||||
* Because the command param names are essentially properties of a JSON object and the
|
||||
* `ParameterReflection` instances represent the arguments of a method, we must match them by
|
||||
* index. In JS, Required arguments always come first, so we can do those first. If there are
|
||||
* _more_ method arguments than command param names, we toss them out, because they may not be
|
||||
* part of the public API.
|
||||
* @param kind Either `required` or `optional`
|
||||
* @returns List of refls with names matching `commandParams`, throwing out any extra refls
|
||||
*/
|
||||
const createNewRefls = (kind: 'required' | 'optional'): ParameterReflection[] => {
|
||||
const commandParams = cmd[`${kind}Params`];
|
||||
if (!commandParams?.length) {
|
||||
return [];
|
||||
public static rewriteParameters = _.memoize(
|
||||
(cmd: BaseCommandData): ParameterReflection[] | undefined => {
|
||||
if (!cmd.hasCommandParams) {
|
||||
return;
|
||||
}
|
||||
const paramCount = commandParams.length;
|
||||
|
||||
const newParamRefls: ParameterReflection[] = [];
|
||||
for (let i = 0; i < paramCount; i++) {
|
||||
const pRefl = pRefls.shift();
|
||||
if (pRefl) {
|
||||
// if there isn't one, the warning above will have been logged already
|
||||
const newPRefl = new ParameterReflection(
|
||||
commandParams[i],
|
||||
ReflectionKind.CallSignature,
|
||||
sig
|
||||
);
|
||||
_.assign(newPRefl, _.pick(pRefl, PARAMETER_REFLECTION_CLONE_FIELDS));
|
||||
|
||||
// there doesn't seem to be a straightforward way to clone flags.
|
||||
newPRefl.flags = new ReflectionFlags(...pRefl.flags);
|
||||
newPRefl.flags.setFlag(ReflectionFlag.Optional, kind === 'optional');
|
||||
newParamRefls.push(newPRefl);
|
||||
}
|
||||
const signature = findCallSignature(cmd.methodRefl)!;
|
||||
if (!signature || !signature.parameters?.length) {
|
||||
// no parameters
|
||||
return;
|
||||
}
|
||||
|
||||
const newParamRefls = [
|
||||
...createNewParamRefls(signature, {
|
||||
builtinMethods: cmd.knownBuiltinMethods,
|
||||
commandParams: cmd.requiredParams,
|
||||
isPluginCommand: cmd.isPluginCommand,
|
||||
}),
|
||||
...createNewParamRefls(signature, {
|
||||
builtinMethods: cmd.knownBuiltinMethods,
|
||||
commandParams: cmd.optionalParams,
|
||||
isPluginCommand: cmd.isPluginCommand,
|
||||
isOptional: true,
|
||||
}),
|
||||
];
|
||||
|
||||
return newParamRefls;
|
||||
};
|
||||
|
||||
const newParamRefls = [...createNewRefls('required'), ...createNewRefls('optional')];
|
||||
|
||||
if (!newParamRefls.length) {
|
||||
return [];
|
||||
}
|
||||
return newParamRefls;
|
||||
});
|
||||
|
||||
);
|
||||
/**
|
||||
* Rewrites a method's return value for documentation.
|
||||
*
|
||||
@@ -185,50 +130,64 @@ export abstract class BaseCommandData {
|
||||
* Note that the return type of a command's method declaration should always be a `ReferenceType` having
|
||||
* name `Promise`.
|
||||
*/
|
||||
public static unwrapSignatureType = _.memoize((cmd: BaseCommandData) => {
|
||||
const callSig = BaseCommandData.findCallSignature(cmd);
|
||||
if (!callSig) {
|
||||
return;
|
||||
}
|
||||
if (isReferenceType(callSig.type) && callSig.type.name === 'Promise') {
|
||||
const newCallSig = new SignatureReflection(
|
||||
callSig.name,
|
||||
ReflectionKind.CallSignature,
|
||||
cmd.methodRefl!
|
||||
);
|
||||
_.assign(newCallSig, _.pick(callSig, SIGNATURE_REFLECTION_CLONE_FIELDS));
|
||||
|
||||
// this is the actual unwrapping. `Promise` only has a single type argument `T`,
|
||||
// so we can safely use the first one.
|
||||
newCallSig.type = callSig.type.typeArguments?.[0];
|
||||
|
||||
if (!newCallSig.type) {
|
||||
cmd.log.warn(
|
||||
'(%s) No type arg T found for return type Promise<T> in %s; this is a bug',
|
||||
cmd.parentRefl!.name,
|
||||
cmd.methodRefl!.name
|
||||
);
|
||||
public static rewriteSignature = _.memoize(
|
||||
(cmd: BaseCommandData): SignatureReflection | undefined => {
|
||||
const callSig = findCallSignature(cmd.methodRefl);
|
||||
if (!callSig) {
|
||||
return;
|
||||
}
|
||||
return newCallSig;
|
||||
if (isReferenceType(callSig.type) && callSig.type.name === 'Promise') {
|
||||
// this does the actual unwrapping. `Promise` only has a single type argument `T`,
|
||||
// so we can safely use the first one.
|
||||
const newType = _.first(callSig.type.typeArguments);
|
||||
const newCallSig = cloneSignatureReflection(callSig, cmd.methodRefl, newType);
|
||||
|
||||
if (!newCallSig.type) {
|
||||
cmd.log.warn(
|
||||
'(%s) No type arg T found for return type Promise<T> in %s; this is a bug',
|
||||
cmd.parentRefl!.name,
|
||||
cmd.methodRefl.name
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
newCallSig.comment = deriveComment({
|
||||
refl: newCallSig,
|
||||
knownMethods: cmd.knownBuiltinMethods,
|
||||
})?.comment;
|
||||
return newCallSig;
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
/**
|
||||
* The thing which the method is a member of for documentation purposes
|
||||
*/
|
||||
parentRefl?: DeclarationReflection;
|
||||
|
||||
constructor(log: AppiumPluginLogger, command: Command, opts: CommandDataOpts = {}) {
|
||||
constructor(
|
||||
log: AppiumPluginLogger,
|
||||
command: Command,
|
||||
methodRefl: CommandMethodDeclarationReflection,
|
||||
opts: CommandDataOpts = {}
|
||||
) {
|
||||
this.command = command;
|
||||
this.methodRefl = methodRefl;
|
||||
this.log = log;
|
||||
|
||||
this.optionalParams = opts.optionalParams;
|
||||
this.requiredParams = opts.requiredParams;
|
||||
this.comment = opts.comment;
|
||||
|
||||
this.commentSource = opts.commentSource;
|
||||
this.methodRefl = opts.refl;
|
||||
this.parentRefl = opts.parentRefl;
|
||||
this.log = log;
|
||||
this.knownBuiltinMethods = opts.knownBuiltinMethods;
|
||||
this.isPluginCommand = Boolean(opts.isPluginCommand);
|
||||
|
||||
const extractedExamples = extractExamples(opts.comment);
|
||||
if (extractedExamples?.comment) {
|
||||
this.comment = extractedExamples.comment;
|
||||
}
|
||||
if (extractedExamples?.examples) {
|
||||
this.examples = extractedExamples.examples;
|
||||
}
|
||||
|
||||
this.parameters = BaseCommandData.rewriteParameters(this);
|
||||
this.signature = BaseCommandData.rewriteSignature(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -238,20 +197,6 @@ export abstract class BaseCommandData {
|
||||
return Boolean(this.optionalParams?.length || this.requiredParams?.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of function parameters (for use in rendering)
|
||||
*/
|
||||
public get parameters() {
|
||||
return BaseCommandData.rewriteParameters(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the call signature (for use in rendering)
|
||||
*/
|
||||
public get signature() {
|
||||
return BaseCommandData.unwrapSignatureType(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should create a shallow clone of the implementing instance
|
||||
* @param opts New options to pass to the new instance
|
||||
@@ -272,31 +217,24 @@ export interface CommandDataOpts {
|
||||
*
|
||||
* For debugging purposes mainly
|
||||
*/
|
||||
commentSource?: CommentSourceType;
|
||||
|
||||
commentSource?: CommentSource;
|
||||
/**
|
||||
* If `true`, `refl` represents a `PluginCommand`, wherein we will ignore
|
||||
* the first two parameters altogether.
|
||||
*/
|
||||
isPluginCommand?: boolean;
|
||||
|
||||
/**
|
||||
* Known methods in the project
|
||||
*/
|
||||
knownBuiltinMethods?: KnownMethods;
|
||||
/**
|
||||
* List of optional parameter names derived from a method map
|
||||
*/
|
||||
optionalParams?: string[];
|
||||
|
||||
/**
|
||||
* The thing which the method is a member of for documentation purposes
|
||||
*/
|
||||
parentRefl?: DeclarationReflection;
|
||||
|
||||
/**
|
||||
* Actual method reflection.
|
||||
*
|
||||
* @todo Determine if this should be required
|
||||
*/
|
||||
refl?: CommandMethodDeclarationReflection;
|
||||
|
||||
/**
|
||||
* List of required parameter names derived from a method map
|
||||
*/
|
||||
@@ -311,7 +249,6 @@ export class CommandData extends BaseCommandData {
|
||||
* The HTTP method of the route
|
||||
*/
|
||||
public readonly httpMethod: AllowedHttpMethod;
|
||||
|
||||
/**
|
||||
* The route of the command
|
||||
*/
|
||||
@@ -320,11 +257,12 @@ export class CommandData extends BaseCommandData {
|
||||
constructor(
|
||||
log: AppiumPluginLogger,
|
||||
command: Command,
|
||||
methodRefl: CommandMethodDeclarationReflection,
|
||||
httpMethod: AllowedHttpMethod,
|
||||
route: Route,
|
||||
opts: CommandDataOpts = {}
|
||||
) {
|
||||
super(log, command, opts);
|
||||
super(log, command, methodRefl, opts);
|
||||
this.httpMethod = httpMethod;
|
||||
this.route = route;
|
||||
}
|
||||
@@ -338,8 +276,14 @@ export class CommandData extends BaseCommandData {
|
||||
* @param opts Options to apply. _Note:_ you probably want to provide a new `parentRefl`.
|
||||
* @returns Cloned instance
|
||||
*/
|
||||
public override clone(opts: CommandDataOpts = {}): CommandData {
|
||||
return new CommandData(this.log, this.command, this.httpMethod, this.route, {...this, ...opts});
|
||||
public override clone(
|
||||
methodRefl: CommandMethodDeclarationReflection,
|
||||
opts: CommandDataOpts = {}
|
||||
): CommandData {
|
||||
return new CommandData(this.log, this.command, methodRefl, this.httpMethod, this.route, {
|
||||
...this,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,14 +306,12 @@ export class ExecMethodData extends BaseCommandData {
|
||||
constructor(
|
||||
log: AppiumPluginLogger,
|
||||
command: Command,
|
||||
methodRefl: CommandMethodDeclarationReflection,
|
||||
script: string,
|
||||
opts: CommandDataOpts = {}
|
||||
) {
|
||||
super(log, command, opts);
|
||||
super(log, command, methodRefl, opts);
|
||||
this.script = script;
|
||||
if (!this.methodRefl) {
|
||||
this.log.verbose(`No reflection for script ${script}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -381,6 +323,9 @@ export class ExecMethodData extends BaseCommandData {
|
||||
* @returns Cloned instance
|
||||
*/
|
||||
public override clone(opts: CommandDataOpts): ExecMethodData {
|
||||
return new ExecMethodData(this.log, this.command, this.script, {...this, ...opts});
|
||||
return new ExecMethodData(this.log, this.command, this.methodRefl, this.script, {
|
||||
...this,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
import pluralize from 'pluralize';
|
||||
import {Comment, DeclarationReflection, ParameterReflection, SignatureReflection} from 'typedoc';
|
||||
import {
|
||||
CommandMethodDeclarationReflection,
|
||||
CommentSourceType,
|
||||
Example,
|
||||
extractExamples,
|
||||
} from '../../converter';
|
||||
import {CommandMethodDeclarationReflection, CommentSource, Example} from '../../converter';
|
||||
import {isExecMethodData} from '../../guards';
|
||||
import {AppiumPluginLogger} from '../../logger';
|
||||
import {CommandData, ExecMethodData} from '../command-data';
|
||||
import {AllowedHttpMethod, Route} from '../types';
|
||||
import {ExtensionReflection} from './extension';
|
||||
@@ -28,59 +21,48 @@ export const HTTP_METHOD_EXECUTE = 'POST';
|
||||
* Methods may be invoked directly by Handlebars templates.
|
||||
*/
|
||||
export class CommandReflection extends DeclarationReflection {
|
||||
/**
|
||||
* Comment, if any.
|
||||
*/
|
||||
public readonly comment?: Comment;
|
||||
/**
|
||||
* Metadata about where `comment` came from
|
||||
*/
|
||||
public readonly commentSource?: CommentSource;
|
||||
public readonly examples?: Example[];
|
||||
/**
|
||||
* HTTP Method of the command or execute method
|
||||
*/
|
||||
public readonly httpMethod: string;
|
||||
|
||||
/**
|
||||
* Optional parameters, if any
|
||||
*/
|
||||
public readonly optionalParams: string[];
|
||||
|
||||
/**
|
||||
* Required parameters, if any
|
||||
*/
|
||||
public readonly requiredParams: string[];
|
||||
|
||||
/**
|
||||
* Route name
|
||||
*/
|
||||
public readonly route: Route;
|
||||
|
||||
/**
|
||||
* Script name, if any. Only used if kind is `EXECUTE_METHOD`
|
||||
*/
|
||||
public readonly script?: string;
|
||||
|
||||
/**
|
||||
* Comment, if any.
|
||||
*/
|
||||
public readonly comment?: Comment;
|
||||
|
||||
/**
|
||||
* Metadata about where `comment` came from
|
||||
*/
|
||||
public readonly commentSource?: CommentSourceType;
|
||||
/**
|
||||
* Original method declaration
|
||||
*/
|
||||
public readonly refl?: CommandMethodDeclarationReflection;
|
||||
|
||||
/**
|
||||
* Parameters for template display
|
||||
*/
|
||||
public readonly parameters?: ParameterReflection[];
|
||||
|
||||
/**
|
||||
* Original method declaration
|
||||
*/
|
||||
public readonly refl?: CommandMethodDeclarationReflection;
|
||||
/**
|
||||
* Required parameters, if any
|
||||
*/
|
||||
public readonly requiredParams: string[];
|
||||
/**
|
||||
* Route name
|
||||
*/
|
||||
public readonly route: Route;
|
||||
/**
|
||||
* Script name, if any. Only used if kind is `EXECUTE_METHOD`
|
||||
*/
|
||||
public readonly script?: string;
|
||||
/**
|
||||
* Call signature for template display
|
||||
*/
|
||||
public readonly signature?: SignatureReflection;
|
||||
|
||||
public readonly examples?: Example[];
|
||||
|
||||
#log: AppiumPluginLogger;
|
||||
|
||||
/**
|
||||
* Sets props depending on type of `data`
|
||||
* @param data Command or execute method data
|
||||
@@ -90,7 +72,6 @@ export class CommandReflection extends DeclarationReflection {
|
||||
constructor(
|
||||
readonly data: CommandData | ExecMethodData,
|
||||
parent: ExtensionReflection,
|
||||
log: AppiumPluginLogger,
|
||||
route?: Route
|
||||
) {
|
||||
let name: string;
|
||||
@@ -107,6 +88,7 @@ export class CommandReflection extends DeclarationReflection {
|
||||
commentSource,
|
||||
parameters,
|
||||
signature,
|
||||
examples,
|
||||
command,
|
||||
} = data;
|
||||
|
||||
@@ -127,7 +109,6 @@ export class CommandReflection extends DeclarationReflection {
|
||||
|
||||
super(name, kind as any, parent);
|
||||
|
||||
this.#log = log;
|
||||
this.route = route;
|
||||
this.httpMethod = httpMethod;
|
||||
this.requiredParams = requiredParams ?? [];
|
||||
@@ -137,25 +118,17 @@ export class CommandReflection extends DeclarationReflection {
|
||||
this.commentSource = commentSource;
|
||||
this.parameters = parameters;
|
||||
this.signature = signature;
|
||||
const extractedExamples = extractExamples(comment);
|
||||
if (extractedExamples?.examples?.length) {
|
||||
this.#log.verbose(
|
||||
'Extracted %s from comment in %s',
|
||||
pluralize('example', extractedExamples.examples.length, true),
|
||||
this.name
|
||||
);
|
||||
}
|
||||
this.examples = extractedExamples?.examples;
|
||||
this.comment = extractedExamples?.comment;
|
||||
this.examples = examples;
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* If `true`, this command has required parameters
|
||||
* If `true`, this command contains one or more examples
|
||||
*
|
||||
* Used by templates
|
||||
*/
|
||||
public get hasRequiredParams(): boolean {
|
||||
return Boolean(this.requiredParams.length);
|
||||
public get hasExample(): boolean {
|
||||
return Boolean(this.examples?.length);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,6 +140,15 @@ export class CommandReflection extends DeclarationReflection {
|
||||
return Boolean(this.optionalParams.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* If `true`, this command has required parameters
|
||||
*
|
||||
* Used by templates
|
||||
*/
|
||||
public get hasRequiredParams(): boolean {
|
||||
return Boolean(this.requiredParams.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* If `true`, this command contains data about an execute method
|
||||
*
|
||||
@@ -175,13 +157,4 @@ export class CommandReflection extends DeclarationReflection {
|
||||
public get isExecuteMethod(): boolean {
|
||||
return this.kindOf(AppiumPluginReflectionKind.ExecuteMethod as any);
|
||||
}
|
||||
|
||||
/**
|
||||
* If `true`, this command contains one or more examples
|
||||
*
|
||||
* Used by templates
|
||||
*/
|
||||
public get hasExample(): boolean {
|
||||
return Boolean(this.examples?.length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,37 @@
|
||||
* @module
|
||||
*/
|
||||
|
||||
import {addReflectionKind} from './utils';
|
||||
/**
|
||||
* Adapted from `@knodes/typedoc-pluginutils`
|
||||
* @see https://github.com/knodescommunity/typedoc-plugins
|
||||
* Copyright (c) 2022 KnodesCommunity
|
||||
* Licensed MIT
|
||||
* @see https://github.com/KnodesCommunity/typedoc-plugins/blob/05717565fae14357b1c4be8122f3156e1d46d332/LICENSE
|
||||
* @module
|
||||
*/
|
||||
|
||||
import {ReflectionKind} from 'typedoc';
|
||||
|
||||
const getHigherBitMask = () =>
|
||||
Math.max(
|
||||
...Object.values({...ReflectionKind, All: -1})
|
||||
.filter((value) => typeof value === 'number')
|
||||
.map((v) => v.toString(2))
|
||||
.filter((v) => v.match(/^0*10*$/))
|
||||
.map((v) => parseInt(v, 2))
|
||||
);
|
||||
|
||||
function addReflectionKind(name: string, value?: number | null) {
|
||||
const kindAny = ReflectionKind as any;
|
||||
const existingValue = kindAny[name];
|
||||
if (existingValue !== null && existingValue !== undefined) {
|
||||
return existingValue;
|
||||
}
|
||||
const defaultedValue = value ?? getHigherBitMask() * 2;
|
||||
kindAny[name] = defaultedValue;
|
||||
kindAny[defaultedValue] = name;
|
||||
return defaultedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends the TypeDoc's `ReflectionKind` to add namespaced kinds
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
/**
|
||||
* Adapted from `@knodes/typedoc-pluginutils`
|
||||
* @see https://github.com/knodescommunity/typedoc-plugins
|
||||
* Copyright (c) 2022 KnodesCommunity
|
||||
* Licensed MIT
|
||||
* @see https://github.com/KnodesCommunity/typedoc-plugins/blob/05717565fae14357b1c4be8122f3156e1d46d332/LICENSE
|
||||
* @module
|
||||
*/
|
||||
|
||||
import {ReflectionKind} from 'typedoc';
|
||||
|
||||
const getHigherBitMask = () =>
|
||||
Math.max(
|
||||
...Object.values({...ReflectionKind, All: -1})
|
||||
.filter((value) => typeof value === 'number')
|
||||
.map((v) => v.toString(2))
|
||||
.filter((v) => v.match(/^0*10*$/))
|
||||
.map((v) => parseInt(v, 2))
|
||||
);
|
||||
export const addReflectionKind = (name: string, value?: number | null) => {
|
||||
const kindAny = ReflectionKind as any;
|
||||
const existingValue = kindAny[name];
|
||||
if (existingValue !== null && existingValue !== undefined) {
|
||||
return existingValue;
|
||||
}
|
||||
const defaultedValue = value ?? getHigherBitMask() * 2;
|
||||
kindAny[name] = defaultedValue;
|
||||
kindAny[defaultedValue] = name;
|
||||
return defaultedValue;
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Utils used across entire package
|
||||
* @module
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import {DeclarationReflection} from 'typedoc';
|
||||
import {isCallSignatureReflection} from './guards';
|
||||
|
||||
/**
|
||||
* Loops through signatures of the command's method declaration and returns the first that is a
|
||||
* `CallSignatureReflection` (if any). This is what we think of when we think "function signature"
|
||||
*/
|
||||
export const findCallSignature = _.memoize((refl?: DeclarationReflection) =>
|
||||
refl?.getAllSignatures()?.find(isCallSignatureReflection)
|
||||
);
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/typedoc-plugin-appium",
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.0",
|
||||
"description": "TypeDoc plugin for Appium & its extensions",
|
||||
"homepage": "https://appium.io",
|
||||
"license": "Apache-2.0",
|
||||
@@ -24,9 +24,9 @@
|
||||
"types": "./build/lib/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "cpy lib/theme/resources build",
|
||||
"clean": "npx rimraf build/lib/theme/resources",
|
||||
"clean": "npx rimraf build",
|
||||
"test": "run-s test:unit",
|
||||
"test:e2e": "mocha --timeout 20s \"test/e2e/**/*.e2e.spec.ts\"",
|
||||
"test:e2e": "mocha --timeout 20s \"test/e2e/index.e2e.spec.ts\" && mocha --timeout 20s \"test/e2e/converter/builtin-external-driver.e2e.spec.ts\" && mocha --timeout 20s \"test/e2e/converter/builtin-method-map.e2e.spec.ts\" && mocha --timeout 20s \"test/e2e/converter/converter.e2e.spec.ts\" && mocha --timeout 20s \"test/e2e/converter/external.e2e.spec.ts\"",
|
||||
"test:smoke": "node ./index.js",
|
||||
"test:unit": "mocha --timeout 2s \"test/unit/**/*.spec.ts\""
|
||||
},
|
||||
@@ -57,13 +57,13 @@
|
||||
"typedoc": "^0.23.14",
|
||||
"typedoc-plugin-markdown": "^3.14.0",
|
||||
"typedoc-plugin-resolve-crossmodule-references": "~0.3.3",
|
||||
"typescript": "~4.7.0"
|
||||
"typescript": "^4.7.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"handlebars": "4.7.7",
|
||||
"lodash": "4.17.21",
|
||||
"pluralize": "8.0.0",
|
||||
"type-fest": "3.5.6"
|
||||
"type-fest": "3.6.0"
|
||||
},
|
||||
"gitHead": "84b211330dc84849af9cc1f3a5c5ad32e15b2e72"
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@ import {
|
||||
NAME_BUILTIN_COMMAND_MODULE,
|
||||
NAME_TYPES_MODULE,
|
||||
} from '../../../lib/converter';
|
||||
import {BuiltinCommands} from '../../../lib/model/builtin-commands';
|
||||
import {AppiumPluginLogger} from '../../../lib/logger';
|
||||
import {initConverter, NAME_FAKE_DRIVER_MODULE} from '../helpers';
|
||||
import {CommandData} from '../../../lib/model';
|
||||
import {BuiltinCommands} from '../../../lib/model/builtin-commands';
|
||||
import {initConverter, NAME_FAKE_DRIVER_MODULE} from '../helpers';
|
||||
|
||||
describe('@appium/typedoc-plugin-appium', function () {
|
||||
describe('BuiltinMethodMapConverter', function () {
|
||||
@@ -72,10 +72,21 @@ describe('@appium/typedoc-plugin-appium', function () {
|
||||
});
|
||||
|
||||
it('should contain the expected properties in the getSession command data', function () {
|
||||
expect(_.omit(cmdData, 'methodRefl', 'parentRefl', 'comment', 'log')).to.eql({
|
||||
expect(
|
||||
_.omit(
|
||||
cmdData,
|
||||
'methodRefl',
|
||||
'parentRefl',
|
||||
'knownBuiltinMethods',
|
||||
'comment',
|
||||
'log',
|
||||
'parameters',
|
||||
'signature'
|
||||
)
|
||||
).to.eql({
|
||||
command: 'createSession',
|
||||
httpMethod: 'POST',
|
||||
commentSource: 'method-signature',
|
||||
commentSource: 'multiple',
|
||||
requiredParams: [],
|
||||
route: '/session',
|
||||
optionalParams: ['desiredCapabilities', 'requiredCapabilities', 'capabilities'],
|
||||
|
||||
@@ -28,7 +28,7 @@ describe('@appium/typedoc-plugin-appium', function () {
|
||||
entryPoints: [NAME_TYPES_MODULE, NAME_FAKE_DRIVER_MODULE, NAME_BUILTIN_COMMAND_MODULE],
|
||||
});
|
||||
ctx = await new Promise((resolve) => {
|
||||
app.converter.once(Converter.EVENT_RESOLVE_BEGIN, (ctx: Context) => {
|
||||
app.converter.once(Converter.EVENT_RESOLVE_END, (ctx: Context) => {
|
||||
resolve(ctx);
|
||||
});
|
||||
app.convert();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import _ from 'lodash';
|
||||
import {expect} from 'chai';
|
||||
import {createSandbox, SinonSandbox} from 'sinon';
|
||||
import {Comment, Context} from 'typedoc';
|
||||
@@ -9,10 +10,9 @@ import {
|
||||
NAME_BUILTIN_COMMAND_MODULE,
|
||||
NAME_TYPES_MODULE,
|
||||
} from '../../../lib/converter';
|
||||
import {BuiltinCommands} from '../../../lib/model/builtin-commands';
|
||||
import {isCallSignatureReflectionWithArity} from '../../../lib/guards';
|
||||
import {AppiumPluginLogger} from '../../../lib/logger';
|
||||
import {CommandSet, ModuleCommands, ProjectCommands} from '../../../lib/model';
|
||||
import {BuiltinCommands} from '../../../lib/model/builtin-commands';
|
||||
import {initConverter, NAME_FAKE_DRIVER_MODULE} from '../helpers';
|
||||
describe('@appium/typedoc-plugin-appium', function () {
|
||||
describe('ExternalConverter', function () {
|
||||
@@ -61,7 +61,7 @@ describe('@appium/typedoc-plugin-appium', function () {
|
||||
let fakeDriverCmds: ModuleCommands;
|
||||
let sessionCmdSet: CommandSet;
|
||||
|
||||
beforeEach(async function () {
|
||||
before(async function () {
|
||||
const converter = await initConverter(ExternalConverter, NAME_FAKE_DRIVER_MODULE, {
|
||||
extraArgs: [externalDriverMethods, builtinCmdSrc.moduleCmds],
|
||||
});
|
||||
@@ -95,7 +95,7 @@ describe('@appium/typedoc-plugin-appium', function () {
|
||||
});
|
||||
|
||||
it('should use the summary from the driver instead of from builtins', function () {
|
||||
const postRoute = [...sessionCmdSet].find((cmdData) => cmdData.httpMethod === 'POST')!;
|
||||
const postRoute = _.find([...sessionCmdSet], {httpMethod: 'POST'})!;
|
||||
|
||||
expect(Comment.combineDisplayParts(postRoute.comment!.summary)).to.equal(
|
||||
'Comment for `createSession` in `FakeDriver`'
|
||||
@@ -103,43 +103,38 @@ describe('@appium/typedoc-plugin-appium', function () {
|
||||
});
|
||||
|
||||
it('should prefer method map parameters over method parameters', function () {
|
||||
const postRoute = [...sessionCmdSet].find((cmdData) => cmdData.httpMethod === 'POST')!;
|
||||
|
||||
const pRefls = postRoute.methodRefl!.signatures!.find(
|
||||
isCallSignatureReflectionWithArity
|
||||
)!.parameters!;
|
||||
const postRoute = _.find([...sessionCmdSet], {httpMethod: 'POST'})!;
|
||||
|
||||
// the method has 4 parameters, but the method map has 3
|
||||
expect(pRefls).to.have.lengthOf(4);
|
||||
expect(postRoute.parameters).to.have.lengthOf(3);
|
||||
|
||||
// the first parameter is required in the method, but optional in the method map
|
||||
// and the names are different.
|
||||
expect(postRoute.parameters[0])
|
||||
expect(postRoute.parameters![0])
|
||||
.to.deep.include({
|
||||
name: 'desiredCapabilities',
|
||||
})
|
||||
.and.to.have.nested.property('flags.isOptional', true);
|
||||
expect(pRefls[0])
|
||||
.to.deep.include({name: 'w3cCapabilities1'})
|
||||
.and.to.have.nested.property('flags.isOptional', false);
|
||||
|
||||
expect(postRoute.parameters[1])
|
||||
expect(postRoute.parameters![1])
|
||||
.to.deep.include({
|
||||
name: 'requiredCapabilities',
|
||||
})
|
||||
.and.to.have.nested.property('flags.isOptional', true);
|
||||
expect(pRefls[1])
|
||||
.to.deep.include({name: 'w3cCapabilities2'})
|
||||
.and.to.have.nested.property('flags.isOptional', true);
|
||||
expect(postRoute.parameters[2])
|
||||
expect(postRoute.parameters![2])
|
||||
.to.deep.include({
|
||||
name: 'capabilities',
|
||||
})
|
||||
.and.to.have.nested.property('flags.isOptional', true);
|
||||
expect(pRefls[2])
|
||||
.to.deep.include({name: 'w3cCapabilities3'})
|
||||
.and.to.have.nested.property('flags.isOptional', true);
|
||||
});
|
||||
|
||||
it('should contain parameters with comments', function () {
|
||||
const createSessionCmd = _.find([...sessionCmdSet], {httpMethod: 'POST'})!;
|
||||
expect(createSessionCmd.parameters!.every((p) => p.hasComment())).to.be.true;
|
||||
});
|
||||
|
||||
it('should contain a call signature with a contentful @returns tag', function () {
|
||||
const createSessionCmd = _.find([...sessionCmdSet], {httpMethod: 'POST'})!;
|
||||
const tag = _.find(createSessionCmd.signature!.comment!.blockTags!, {tag: '@returns'})!;
|
||||
expect(tag.content).not.to.be.empty;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -130,13 +130,13 @@ async function convert<T, C extends BaseConverter<T>, Args extends readonly any[
|
||||
resolve(new cls(ctx, log));
|
||||
}
|
||||
};
|
||||
app.converter.once(Converter.EVENT_RESOLVE_BEGIN, listener);
|
||||
app.converter.once(Converter.EVENT_RESOLVE_END, listener);
|
||||
try {
|
||||
app.convert();
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
} finally {
|
||||
app.converter.off(Converter.EVENT_RESOLVE_BEGIN, listener);
|
||||
app.converter.off(Converter.EVENT_RESOLVE_END, listener);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,34 +1,29 @@
|
||||
import {expect} from 'chai';
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
Comment,
|
||||
CommentTag,
|
||||
DeclarationReflection,
|
||||
ParameterReflection,
|
||||
ProjectReflection,
|
||||
ReferenceType,
|
||||
ReflectionKind,
|
||||
SignatureReflection,
|
||||
} from 'typedoc';
|
||||
import {
|
||||
KnownMethods,
|
||||
deriveComment,
|
||||
CommandMethodDeclarationReflection,
|
||||
CommentSourceType,
|
||||
AsyncCallSignatureReflection,
|
||||
cloneComment,
|
||||
CommandMethodDeclarationReflection,
|
||||
CommentSource,
|
||||
deriveComment,
|
||||
KnownMethods,
|
||||
} from '../../../lib/converter';
|
||||
import {AppiumPluginReflectionKind, ExtensionReflection, ModuleCommands} from '../../../lib/model';
|
||||
|
||||
describe('@appium/typedoc-plugin-appium', function () {
|
||||
describe('deriveComment()', function () {
|
||||
let refl: CommandMethodDeclarationReflection;
|
||||
let project: ProjectReflection;
|
||||
|
||||
beforeEach(function () {
|
||||
project = new ProjectReflection('my project');
|
||||
refl = new DeclarationReflection(
|
||||
'test',
|
||||
AppiumPluginReflectionKind.Command as any,
|
||||
new ExtensionReflection('somedriver', project, new ModuleCommands())
|
||||
) as CommandMethodDeclarationReflection;
|
||||
});
|
||||
let knownMethods: KnownMethods;
|
||||
let methodRefl: CommandMethodDeclarationReflection;
|
||||
|
||||
describe('when not provided parameters', function () {
|
||||
it('should return undefined', function () {
|
||||
@@ -37,101 +32,278 @@ describe('@appium/typedoc-plugin-appium', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided a CommandMethodDeclarationReflection with Comment with its own summary text', function () {
|
||||
describe('when provided a CommandMethodDeclarationReflection', function () {
|
||||
beforeEach(function () {
|
||||
refl.comment = new Comment([{kind: 'text', text: 'a summary'}]);
|
||||
});
|
||||
|
||||
it('should return CommentData referencing the comment', function () {
|
||||
const commentData = deriveComment({refl});
|
||||
expect(commentData).to.eql({
|
||||
commentSource: CommentSourceType.Method,
|
||||
comment: refl.comment,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided a CommandMethodDeclarationReflection with a Comment with its own block tags', function () {
|
||||
beforeEach(function () {
|
||||
refl.comment = new Comment(undefined, [
|
||||
new CommentTag('@privateRemarks', [{kind: 'text', text: 'secret remarks'}]),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return CommentData referencing the comment', function () {
|
||||
const commentData = deriveComment({refl});
|
||||
expect(commentData).to.eql({
|
||||
commentSource: CommentSourceType.Method,
|
||||
comment: refl.comment,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided a CommandMethodDeclarationReflection with a Comment with its own summary text and its own block tags', function () {
|
||||
beforeEach(function () {
|
||||
refl.comment = new Comment(
|
||||
[{kind: 'text', text: 'a summary'}],
|
||||
[new CommentTag('@privateRemarks', [{kind: 'text', text: 'a funny comment'}])]
|
||||
);
|
||||
});
|
||||
|
||||
it('should return CommentData referencing the comment', function () {
|
||||
const commentData = deriveComment({refl});
|
||||
expect(commentData).to.eql({
|
||||
commentSource: CommentSourceType.Method,
|
||||
comment: refl.comment,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided a CommandMethodDeclarationReflection its own summary and block tags from another source', function () {
|
||||
let knownMethods: KnownMethods;
|
||||
let otherRefl: CommandMethodDeclarationReflection;
|
||||
let otherBlockTags: CommentTag[];
|
||||
beforeEach(function () {
|
||||
refl.comment = new Comment([{kind: 'text', text: 'a funny comment'}]);
|
||||
otherBlockTags = [new CommentTag('@example', [{kind: 'code', text: 'doStuff();'}])];
|
||||
otherBlockTags[0].name = undefined;
|
||||
otherRefl = new DeclarationReflection(
|
||||
refl.name,
|
||||
AppiumPluginReflectionKind.Command as any
|
||||
project = new ProjectReflection('my project');
|
||||
methodRefl = new DeclarationReflection(
|
||||
'test',
|
||||
ReflectionKind.Method,
|
||||
new ExtensionReflection('somedriver', project, new ModuleCommands())
|
||||
) as CommandMethodDeclarationReflection;
|
||||
otherRefl.type = ReferenceType.createBrokenReference('stuff', project);
|
||||
otherRefl.comment = new Comment(undefined, otherBlockTags);
|
||||
knownMethods = new Map([[refl.name, otherRefl]]);
|
||||
});
|
||||
|
||||
it('should return a CommentData containing an abomination of info from multiple sources', function () {
|
||||
const commentData = deriveComment({refl, knownMethods});
|
||||
expect(commentData).to.eql({
|
||||
commentSource: CommentSourceType.Multiple,
|
||||
comment: {...refl.comment, blockTags: otherBlockTags},
|
||||
describe('when reflection has Comment with its own summary text', function () {
|
||||
beforeEach(function () {
|
||||
methodRefl.comment = new Comment([{kind: 'text', text: 'a summary'}]);
|
||||
});
|
||||
|
||||
it('should return CommentData referencing the comment', function () {
|
||||
const commentData = deriveComment({refl: methodRefl});
|
||||
expect(commentData).to.eql({
|
||||
commentSource: CommentSource.Method,
|
||||
comment: methodRefl.comment,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided a reflection with a Comment with its own block tags', function () {
|
||||
beforeEach(function () {
|
||||
methodRefl.comment = new Comment(undefined, [
|
||||
new CommentTag('@privateRemarks', [{kind: 'text', text: 'secret remarks'}]),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return CommentData referencing the comment', function () {
|
||||
methodRefl; //?
|
||||
const commentData = deriveComment({refl: methodRefl});
|
||||
expect(commentData).to.eql({
|
||||
commentSource: CommentSource.Method,
|
||||
comment: methodRefl.comment,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided a reflection with a Comment with its own summary text and its own block tags', function () {
|
||||
beforeEach(function () {
|
||||
methodRefl.comment = new Comment(
|
||||
[{kind: 'text', text: 'a summary'}],
|
||||
[new CommentTag('@privateRemarks', [{kind: 'text', text: 'a funny comment'}])]
|
||||
);
|
||||
});
|
||||
|
||||
it('should return CommentData referencing the comment', function () {
|
||||
const commentData = deriveComment({refl: methodRefl});
|
||||
expect(commentData).to.eql({
|
||||
commentSource: CommentSource.Method,
|
||||
comment: methodRefl.comment,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided a reflection with its own summary and block tags from another source', function () {
|
||||
let otherRefl: CommandMethodDeclarationReflection;
|
||||
let otherBlockTags: CommentTag[];
|
||||
|
||||
beforeEach(function () {
|
||||
methodRefl.comment = new Comment([{kind: 'text', text: 'a funny comment'}]);
|
||||
otherBlockTags = [new CommentTag('@example', [{kind: 'code', text: 'doStuff();'}])];
|
||||
otherBlockTags[0].name = undefined;
|
||||
otherRefl = new DeclarationReflection(
|
||||
methodRefl.name,
|
||||
AppiumPluginReflectionKind.Command as any
|
||||
) as CommandMethodDeclarationReflection;
|
||||
otherRefl.comment = new Comment(undefined, otherBlockTags);
|
||||
knownMethods = new Map([[methodRefl.name, otherRefl]]);
|
||||
});
|
||||
|
||||
it('should return a CommentData containing an abomination of info from multiple sources', function () {
|
||||
const commentData = deriveComment({refl: methodRefl, knownMethods});
|
||||
expect(commentData).to.eql({
|
||||
commentSource: CommentSource.Multiple,
|
||||
comment: {...methodRefl.comment, blockTags: otherBlockTags},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided a reflection with its own summary and block tag and the same block tag from another source', function () {
|
||||
let knownMethods: KnownMethods;
|
||||
let otherRefl: CommandMethodDeclarationReflection;
|
||||
let otherBlockTags: CommentTag[];
|
||||
let blockTags: CommentTag[];
|
||||
beforeEach(function () {
|
||||
blockTags = [new CommentTag('@example', [{kind: 'code', text: 'doThisInstead();'}])];
|
||||
methodRefl.comment = new Comment([{kind: 'text', text: 'a funny comment'}], blockTags);
|
||||
otherBlockTags = [new CommentTag('@example', [{kind: 'code', text: 'doStuff();'}])];
|
||||
otherRefl = new DeclarationReflection(
|
||||
methodRefl.name,
|
||||
AppiumPluginReflectionKind.Command as any
|
||||
) as CommandMethodDeclarationReflection;
|
||||
otherRefl.comment = new Comment(undefined, otherBlockTags);
|
||||
knownMethods = new Map([[methodRefl.name, otherRefl]]);
|
||||
});
|
||||
|
||||
it('should return a CommentData containing only the block tags from the CommandMethodDeclarationReflection', function () {
|
||||
const {comment, commentSource} = deriveComment({refl: methodRefl, knownMethods})!;
|
||||
expect(comment).to.equal(methodRefl.comment);
|
||||
expect(commentSource).to.equal(CommentSource.Method);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided a CommandMethodDeclarationReflection its own summary and block tag and the same block tag from another source', function () {
|
||||
let knownMethods: KnownMethods;
|
||||
let otherRefl: CommandMethodDeclarationReflection;
|
||||
let otherBlockTags: CommentTag[];
|
||||
let blockTags: CommentTag[];
|
||||
beforeEach(function () {
|
||||
blockTags = [new CommentTag('@example', [{kind: 'code', text: 'doThisInstead();'}])];
|
||||
refl.comment = new Comment([{kind: 'text', text: 'a funny comment'}], blockTags);
|
||||
otherBlockTags = [new CommentTag('@example', [{kind: 'code', text: 'doStuff();'}])];
|
||||
otherRefl = new DeclarationReflection(
|
||||
refl.name,
|
||||
AppiumPluginReflectionKind.Command as any
|
||||
) as CommandMethodDeclarationReflection;
|
||||
otherRefl.type = ReferenceType.createBrokenReference('stuff', project);
|
||||
otherRefl.comment = new Comment(undefined, otherBlockTags);
|
||||
knownMethods = new Map([[refl.name, otherRefl]]);
|
||||
describe('when provided a ParameterReflection', function () {
|
||||
let refl: CommandMethodDeclarationReflection;
|
||||
let paramRefl: ParameterReflection;
|
||||
let paramComment: Comment;
|
||||
|
||||
describe('when provided a reflection having a visible comment', function () {
|
||||
beforeEach(function () {
|
||||
refl = new DeclarationReflection(
|
||||
'test',
|
||||
ReflectionKind.Method,
|
||||
new ExtensionReflection('somedriver', project, new ModuleCommands())
|
||||
) as CommandMethodDeclarationReflection;
|
||||
|
||||
// for deriveComment to work for ParameterReflections, the reflection
|
||||
// must have a parent SignatureReflection, and that SignatureReflection
|
||||
// must have a parent (CommandMethod)DeclarationReflection
|
||||
const sig = new SignatureReflection(
|
||||
'test',
|
||||
ReflectionKind.CallSignature,
|
||||
refl
|
||||
) as AsyncCallSignatureReflection;
|
||||
paramRefl = new ParameterReflection('foo', ReflectionKind.Parameter, sig);
|
||||
paramComment = new Comment([{kind: 'text', text: 'a description of the parameter'}]);
|
||||
paramRefl.comment = paramComment;
|
||||
expect(paramRefl.hasComment()).to.be.true;
|
||||
sig.parameters = [paramRefl];
|
||||
refl.signatures = [sig];
|
||||
});
|
||||
|
||||
it('should find the comment in the parameter', function () {
|
||||
expect(deriveComment({refl: paramRefl})).to.eql({
|
||||
comment: paramComment,
|
||||
commentSource: CommentSource.Parameter,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a CommentData containing only the block tags from the CommandMethodDeclarationReflection', function () {
|
||||
const {comment, commentSource} = deriveComment({refl, knownMethods})!;
|
||||
expect(comment).to.equal(refl.comment);
|
||||
expect(commentSource).to.equal(CommentSourceType.Method);
|
||||
describe('when provided a reflection without a visible comment, and an associated reflection of a builtin method having a comment', function () {
|
||||
let knownMethods: KnownMethods;
|
||||
let otherParamComment: Comment;
|
||||
|
||||
beforeEach(function () {
|
||||
const sigRefl = new SignatureReflection(
|
||||
'test',
|
||||
ReflectionKind.CallSignature,
|
||||
refl
|
||||
) as AsyncCallSignatureReflection;
|
||||
paramRefl = new ParameterReflection('foo', ReflectionKind.Parameter, sigRefl);
|
||||
expect(paramRefl.hasComment()).to.be.false;
|
||||
sigRefl.parameters = [paramRefl];
|
||||
refl.signatures = [sigRefl];
|
||||
const otherRefl = new DeclarationReflection(
|
||||
refl.name,
|
||||
AppiumPluginReflectionKind.Command as any
|
||||
) as CommandMethodDeclarationReflection;
|
||||
otherRefl.type = ReferenceType.createBrokenReference('stuff', project);
|
||||
const otherSigRefl = new SignatureReflection(
|
||||
'test',
|
||||
ReflectionKind.CallSignature,
|
||||
otherRefl
|
||||
) as AsyncCallSignatureReflection;
|
||||
const otherParam = new ParameterReflection('foo', ReflectionKind.Parameter, otherSigRefl);
|
||||
otherParamComment = new Comment([{kind: 'text', text: 'a description of the parameter'}]);
|
||||
otherParam.comment = otherParamComment;
|
||||
expect(otherParam.hasComment()).to.be.true;
|
||||
otherSigRefl.parameters = [otherParam];
|
||||
otherRefl.signatures = [otherSigRefl];
|
||||
knownMethods = new Map([[refl.name, otherRefl]]);
|
||||
});
|
||||
|
||||
it('should use the comment from the builtin method', function () {
|
||||
const {comment, commentSource} = deriveComment({refl: paramRefl, knownMethods})!;
|
||||
expect(comment).to.eql(otherParamComment);
|
||||
expect(commentSource).to.equal(CommentSource.BuiltinParameter);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided a SignatureReflection', function () {
|
||||
let sigRefl: SignatureReflection;
|
||||
|
||||
beforeEach(function () {
|
||||
sigRefl = new SignatureReflection(
|
||||
'test',
|
||||
ReflectionKind.CallSignature,
|
||||
methodRefl
|
||||
) as AsyncCallSignatureReflection;
|
||||
});
|
||||
|
||||
describe('when provided a reflection having a visible comment', function () {
|
||||
beforeEach(function () {
|
||||
sigRefl.comment = new Comment([{kind: 'text', text: 'a description of the method'}]);
|
||||
expect(sigRefl.hasComment()).to.be.true;
|
||||
});
|
||||
|
||||
it('should find the comment in the signature', function () {
|
||||
expect(deriveComment({refl: sigRefl})).to.eql({
|
||||
comment: sigRefl.comment,
|
||||
commentSource: CommentSource.Signature,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided a reflection with an explicit comment', function () {
|
||||
let comment: Comment;
|
||||
|
||||
beforeEach(function () {
|
||||
comment = new Comment([{kind: 'text', text: 'a description of the method'}]);
|
||||
});
|
||||
|
||||
it('should return the explicit comment', function () {
|
||||
expect(deriveComment({refl: sigRefl, comment})).to.eql({
|
||||
comment,
|
||||
commentSource: CommentSource.OtherComment,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided a reflection without a comment and an associated method having a signature with a visible comment', function () {
|
||||
let otherSig: AsyncCallSignatureReflection;
|
||||
beforeEach(function () {
|
||||
const otherRefl = new DeclarationReflection(
|
||||
methodRefl.name,
|
||||
ReflectionKind.Method
|
||||
) as CommandMethodDeclarationReflection;
|
||||
otherSig = new SignatureReflection(
|
||||
'test',
|
||||
ReflectionKind.CallSignature,
|
||||
otherRefl
|
||||
) as AsyncCallSignatureReflection;
|
||||
otherSig.comment = new Comment([{kind: 'text', text: 'a description of the method'}]);
|
||||
otherRefl.signatures = [otherSig];
|
||||
knownMethods = new Map([[methodRefl.name, otherRefl]]);
|
||||
});
|
||||
it('should find the comment', function () {
|
||||
expect(deriveComment({refl: sigRefl, knownMethods})).to.eql({
|
||||
comment: otherSig.comment,
|
||||
commentSource: CommentSource.BuiltinSignature,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided a reflection without a comment and no associated method exists', function () {
|
||||
beforeEach(function () {
|
||||
knownMethods = new Map();
|
||||
});
|
||||
|
||||
it('should return undefined', function () {
|
||||
expect(deriveComment({refl: sigRefl, knownMethods})).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided a reflection without a comment, and an associated method exists having no signature', function () {
|
||||
beforeEach(function () {
|
||||
const otherRefl = new DeclarationReflection(
|
||||
methodRefl.name,
|
||||
ReflectionKind.Method
|
||||
) as CommandMethodDeclarationReflection;
|
||||
knownMethods = new Map([[methodRefl.name, otherRefl]]);
|
||||
});
|
||||
|
||||
it('should return undefined', function () {
|
||||
expect(deriveComment({refl: sigRefl, knownMethods})).to.be.undefined;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,24 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [0.10.0](https://github.com/appium/appium/compare/@appium/types@0.9.1...@appium/types@0.10.0) (2023-02-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **types:** base cap webSocketUrl is now a boolean instead of a string. ([24fb515](https://github.com/appium/appium/commit/24fb515a52349058a8c9b69eafa531876880dffd))
|
||||
* **types:** update dependency type-fest to v3.5.7 ([b4416c5](https://github.com/appium/appium/commit/b4416c5c0f40200b36909a1fbb492d8c4a212108))
|
||||
* **types:** update dependency type-fest to v3.6.0 ([08a6f3a](https://github.com/appium/appium/commit/08a6f3a308c7ee162e992629888557b31e50a26e))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **types:** add orientation type; add optional ELEMENT prop of Element ([ab5b285](https://github.com/appium/appium/commit/ab5b285714a804a3a665723e893b82bd93e25854))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.9.1](https://github.com/appium/appium/compare/@appium/types@0.9.0...@appium/types@0.9.1) (2023-02-09)
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user