Compare commits

..
Author SHA1 Message Date
Christopher Hiller 7b49356322 chore: publish
- appium@2.0.0-beta.56
 - @appium/base-driver@9.3.2
 - @appium/base-plugin@2.2.2
 - @appium/doctor@2.0.8
 - @appium/docutils@0.2.2
 - @appium/driver-test-support@0.4.0
 - @appium/execute-driver-plugin@3.0.7
 - @appium/fake-driver@5.2.0
 - @appium/fake-plugin@3.1.6
 - @appium/images-plugin@2.0.7
 - @appium/opencv@2.0.7
 - @appium/plugin-test-support@0.3.8
 - @appium/schema@0.2.5
 - @appium/support@3.1.6
 - @appium/test-support@3.0.8
 - @appium/typedoc-plugin-appium@0.5.0
 - @appium/types@0.10.0
 - @appium/universal-xml-plugin@1.0.6
2023-02-24 13:43:18 -08:00
renovate[bot] bfc3134968 chore(docutils): update dependency mkdocs-material to v9.0.14 2023-02-24 15:37:44 +00:00
renovate[bot] dbaf8d4cae chore(deps): update dependency @types/node to v18.14.1 2023-02-24 10:13:05 +00:00
renovate[bot] eb73be8303 fix(images-plugin): update dependency lru-cache to v7.17.0 2023-02-23 22:18:02 +00:00
renovate[bot] 998514fcce fix(support): update dependency jimp to v0.22.5 2023-02-23 19:21:23 +00:00
renovate[bot] 49f157d63e fix(support): update dependency axios to v1.3.4 2023-02-23 16:23:10 +00:00
renovate[bot] 43b115bb3d chore(deps): update dependency json-schema-to-typescript to v11.0.5 2023-02-23 09:44:48 +00:00
Christopher Hiller 56cf504a2b chore(appium): avoid public class field assignment
This changes `AppiumDriver` to avoid assignment to class properties, and changes the properties on `Schema` to be actually-private instead of pseudoprivate.
2023-02-22 13:40:55 -08:00
Christopher Hiller 2ebb16b077 chore(base-driver): avoid public class field assignment
this should avert potential future bugs when drivers attempt to override these properties
2023-02-22 13:40:55 -08:00
Christopher Hiller c321441451 chore(fake-plugin): avoid public class field assignment 2023-02-22 13:40:55 -08:00
Christopher Hiller 6be7488eef chore(universal-xml-plugin): remove dead code; typecheck all js sources 2023-02-22 13:40:55 -08:00
Christopher Hiller ab5b285714 feat(types): add orientation type; add optional ELEMENT prop of Element 2023-02-22 13:40:55 -08:00
Kazuaki Matsuo 8dc47ae922 docs: add more account in Project Committers (#18236)
* docs: add more
2023-02-22 13:24:20 -08:00
renovate[bot] 91831cf82d fix(support): update dependency @types/uuid to v9.0.1 2023-02-22 21:02:56 +00:00
renovate[bot] 4b10322b80 fix(images-plugin): update dependency lru-cache to v7.16.2 2023-02-22 17:43:23 +00:00
renovate[bot] 2602016eb3 fix(docutils): update dependency yargs to v17.7.1 2023-02-22 14:08:20 +00:00
renovate[bot] 1cb01a5e85 fix(base-driver): update dependency body-parser to v1.20.2 2023-02-22 11:20:27 +00:00
Kazuaki Matsuo e78448dc65 docs: update governance (#18222) 2023-02-21 12:04:52 -08:00
Christopher Hiller 34e0b70982 chore(typedoc-plugin-appium): add a fallback logger
due to the architecture of TypeDoc, it is not straightforward for a random module in a plugin to have access to the logger of the current `Application`.  This change abuses TypeDoc to enable logging for random modules.
2023-02-20 10:05:30 -08:00
Christopher Hiller ebe9477a3c feat(typedoc-plugin-appium): extract descriptions of return values from builtins
If a command is not documented with a `@returns` tag, but a builtin method _does_ have this tag (e.g., `ExternalDriver.createSession()`), then the plugin will use the tag from the builtin.

If a command _does_ have a `@returns` tag, but it provides no description, then the plugin will use the original `@returns` tag (which may have a type) and combine it with the content/description from the builtin (if any).

This requires further inspection of `SignatureReflection` data, which needed a new set of "comment finders", which prompted me to refactor the comment finders into their own files (because it was becoming unwieldy).  The call to `deriveComment` happens in the static `BaseCommandData.rewriteSignature()` method (whereas before it was just a straightforward clone operation).

- Fixed the block tag aggregation to prefer contentful over contentless tags in all cases.
- Modified the `@returns` tag of `FakeDriver.createSession()` for testing
- Moved some types around that were used outside of their sibling modules (e.g. `CommentSource` which used to be called `CommentSourceType`)
- pruned dead imports
2023-02-20 10:05:30 -08:00
Christopher Hiller 3b82c9f1a1 chore(docutils): smol type upgrade
`type-fest`@3.6.0 has a `TsConfigJson` type which no longer contains `unknown` (courtesy of yours truly), so we can use it directly instead of wrapping it in `Jsonify`.
2023-02-20 10:05:13 -08:00
renovate[bot] a9476a67ff chore(deps): update dependency json-schema-to-typescript to v11.0.4 2023-02-20 12:30:24 +00:00
renovate[bot] 24ad9f8741 chore(deps): update dependency @types/node to v18.14.0 2023-02-19 14:01:21 +00:00
renovate[bot] 639d475627 chore(docutils): update dependency mkdocs-material to v9.0.13 2023-02-19 10:43:21 +00:00
renovate[bot] 2fa6bf1e11 fix(images-plugin): update dependency lru-cache to v7.16.1 2023-02-18 15:41:23 +00:00
renovate[bot] d3dc9ce733 chore(deps): update commitlint monorepo to v17.4.4 2023-02-18 14:40:18 +00:00
renovate[bot] 08a6f3a308 fix(types): update dependency type-fest to v3.6.0 2023-02-17 13:47:51 -08:00
Christopher Hiller 24fb515a52 fix(types): base cap webSocketUrl is now a boolean instead of a string.
note that it is a string in the server response, but it is expected to be a boolean in the request.  punting on that for now
2023-02-17 11:03:11 -08:00
Christopher Hiller 893bd888cf feat(driver-test-support): add startStoppableAppium()
This adds a function `startStoppableAppium()` which starts an Appium server (via its `main()` entry point), but resolves with an object having a `stop() => Promise<boolean>` method. This method force-quits the server, even if there are active connections.
2023-02-17 11:02:44 -08:00
renovate[bot] d54901a9c3 fix(images-plugin): update dependency lru-cache to v7.16.0 2023-02-17 18:03:38 +00:00
renovate[bot] 70d65b7d62 fix(docutils): update dependency yargs to v17.7.0 2023-02-17 14:57:25 +00:00
renovate[bot] 33442bd61b chore(deps): update dependency rewiremock to v3.14.5 2023-02-17 11:22:10 +00:00
renovate[bot] 4cf8dbc0d3 fix(images-plugin): update dependency lru-cache to v7.15.0 2023-02-16 12:03:52 +00:00
Christopher Hiller 943dce5eec fix(schema): move output files due to tsc's confusion
It seemed to think I wanted to overwrite the root `index.js` file, and I don't know why. It doesn't do that anymore.
2023-02-15 16:19:30 -08:00
Christopher Hiller 04633e3e6d chore: clean:distfiles script now blasts everything 2023-02-15 16:19:30 -08:00
Christopher Hiller d171219938 chore: cleanup root tsconfig.json 2023-02-15 16:19:30 -08:00
Christopher Hiller 61a0275e29 fix(base-driver): fix type problem w/r/t axios headers 2023-02-15 16:19:30 -08:00
Christopher Hiller 8f9de63e4a fix: update axios to v1.3.3
because renovate cannot seem to do it
2023-02-15 16:19:30 -08:00
Christopher Hiller 90c0d36f2a chore(ci): add cache cleanup workflow on PR close
I noticed we were filling up our allotted cache space.  Reading about it, I found this example to cleanup caches we no longer need (specifically, from closed PRs)
2023-02-15 10:50:36 -08:00
Christopher Hiller 988c088873 chore: force http-cache-semantics to v4.1.1
for https://github.com/advisories/GHSA-rc47-6667-2j5j

this override should be removed when upgrading webdriver to v8+.  unfortunately "npm audit" is so stupid that it does not understand overrides, so we will see warnings anyway.
2023-02-14 15:43:28 -08:00
Christopher Hiller 7c2c642ce7 chore(typedoc-plugin-appium): blast entire build dir on clean due to stale artifacts 2023-02-14 14:31:38 -08:00
Christopher Hiller 6b8eb2cbfe fix(docutils): fix typescript version 2023-02-14 14:31:38 -08:00
Christopher Hiller 242c2ccbdc fix(typedoc-plugin-appium): fix broken references during clone operations
= moves example extraction to conversion phase (from rendering phase)
- add `cloneTypeParameterReflection()` util
- add support for passing explicit comments to `deriveComment` when `refl` is a `ParameterReflection`
- some tests
2023-02-14 14:31:38 -08:00
Christopher Hiller c5dadbbd36 chore(docutils): fix old mike test for wallaby 2023-02-14 14:31:38 -08:00
Christopher Hiller 74800cdf61 chore(docutils): tweaky refactors for new ts 2023-02-14 14:31:38 -08:00
Christopher Hiller 70c98ea12f chore(typedoc-plugin-appium): isolate e2e tests due to memory pressure in CI 2023-02-14 14:31:38 -08:00
Christopher Hiller 592f9adcb1 fix(typedoc-plugin-appium): parameter descriptions work
In #18132, I found that parameter descriptions of commands were being omitted from the output. Those descriptions have to come from a method, because they cannot be expressed in their original location (a method map) via JS or a docstring.

In addition, if an extension's implementation did not describe the parameters (e.g., in `@param` tags), we need to pull those descriptions out of `ExternalDriver` (assuming they exist).

Furthermore, we were executing the conversion _too early_ in TypeDoc's lifecycle, since its internal plugins haven't run yet. One of those plugins actually moves the `@param` comments from the _signature_ into the appropriate parameter. Once those are there, we can find them.

- Added a new list of "comment finders" which are parameter-specific (could probably do the same thing for return statements)
- Moved some functions into different modules and renamed a couple things to be more accurate.
- Update VSCode launch config
- `CommandData` now requires a reflection of a method. This was true before, but not formalized
2023-02-14 14:31:38 -08:00
Christopher Hiller fac31a3d68 chore(typedoc-plugin-appium): move kind-related utils into the kind module
Nothing else will ever use them, so in they go
2023-02-14 14:31:38 -08:00
renovate[bot] 2bbf42ff6b chore(deps): update dependency lerna to v6.5.1 2023-02-14 19:47:29 +00:00
renovate[bot] 1f65161e56 chore(deps): update dependency ws to v8.12.1 2023-02-14 14:36:45 +00:00
renovate[bot] 355bf8e5da chore(deps): update dependency lint-staged to v13.1.2 2023-02-14 11:23:36 +00:00
renovate[bot] 0ca5e7e21b chore(deps): update commitlint monorepo to v17.4.3 2023-02-14 07:37:19 +00:00
Jonathan Lipps 11e7ad0cd4 fix: appium:options should work via --default-capabilities
fix #18191
2023-02-13 16:20:21 -08:00
Christopher Hiller c69b602d1b fix(renovate): do not upgrade "figures" package to a major 2023-02-13 12:24:11 -08:00
Christopher Hiller 4cf13c5bfa chore(deps): upgrade typescript to v4.9.5
anything newer than v4.7.4 fails to ignore an optional arg in `@param {T} [foo]` if `foo` has a default assignment in the function signature.  removing the square brackets fixes the issue.

whether or not this is a bug, I am not entirely sure: it is marked as a bug, but it is also not something the TS team is in a hurry to fix, so whatevs.
2023-02-13 12:09:20 -08:00
renovate[bot] a4c58cc561 fix(docutils): update dependency typedoc to v0.23.25 2023-02-12 14:48:52 +00:00
renovate[bot] 64098c5f1a chore(deps): update dependency eslint to v8.34.0 2023-02-11 15:35:02 +00:00
renovate[bot] b4416c5c0f fix(types): update dependency type-fest to v3.5.7 2023-02-11 09:08:44 +00:00
renovate[bot] 547b8a45c4 fix(execute-driver-plugin): update dependency webdriverio to v7.30.1 2023-02-10 20:21:13 +00:00
renovate[bot] 1bf841736b chore(docutils): update dependency mkdocs-material to v9.0.12 2023-02-10 16:27:51 +00:00
Christopher Hiller 29a5bdac9a chore: publish
- appium@2.0.0-beta.55
 - @appium/docutils@0.2.1
2023-02-09 14:49:28 -08:00
Christopher Hiller ab366f3a1f fix(docutils): fix bad offset in nav updates
Also warn if no TypeDoc output files found
2023-02-09 14:48:20 -08:00
Christopher Hiller 5446e5c7a7 fix(docutils): fix bad option name and ignore most falsy args
- in type `DeployOpts`, `mkDocsYml` should have been `mkdocsYml`
- arguments were only being omitted from being passed to `mike` if they were `false`, but it needed to also consider `undefined` values and empty string values.  `0` is falsy, but we should not disallow this.  thus... "most" falsy values get ignored.
2023-02-09 14:48:20 -08:00
110 changed files with 4176 additions and 3871 deletions
+36
View File
@@ -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 }}
+4 -1
View File
@@ -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"
+6
View File
@@ -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
+1958 -2679
View File
File diff suppressed because it is too large Load Diff
+15 -14
View File
@@ -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"
}
}
+27
View File
@@ -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)
+1 -1
View File
@@ -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,
+19 -7
View File
@@ -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} =
+1 -1
View File
@@ -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} = {}) {
+1 -1
View File
@@ -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
+42 -39
View File
@@ -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
*/
+9 -9
View File
@@ -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"
}
}
+54 -26
View File
@@ -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);
+22
View File
@@ -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
+41 -17
View File
@@ -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 = {
+1
View File
@@ -35,6 +35,7 @@ export {
isStandardCap,
validateCaps,
promoteAppiumOptions,
promoteAppiumOptionsForObject,
} from './basedriver/capabilities';
// Web socket helpers
+7 -7
View File
@@ -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": {
+8
View File
@@ -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
+3 -3
View File
@@ -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",
+12
View File
@@ -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
+3 -3
View File
@@ -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",
+28
View File
@@ -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)
+10 -4
View File
@@ -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
+27 -21
View File
@@ -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};
}
/**
+4 -7
View File
@@ -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>;
+4 -7
View File
@@ -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>;
+6 -11
View File
@@ -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;
+3 -9
View File
@@ -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
+7 -7
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
import {Mike} from '../..';
import {Mike} from '../../lib/mike';
import {expect} from 'chai';
describe('Mike', function () {
+19
View File
@@ -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;
}
+6 -4
View File
@@ -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');
}
});
});
});
+1 -1
View File
@@ -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)
+2 -2
View File
@@ -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"
+11
View File
@@ -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
+1 -1
View File
@@ -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 -1
View File
@@ -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",
+8
View File
@@ -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
+14 -1
View File
@@ -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': {
+3 -3
View File
@@ -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",
+15
View File
@@ -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
+3 -3
View File
@@ -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": {
+11
View File
@@ -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)
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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",
+11
View File
@@ -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
View File
@@ -1 +1 @@
module.exports = require('./build/index.js');
module.exports = require('./build/lib/index.js');
+2 -2
View File
@@ -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`
+4 -3
View File
@@ -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"]
}
+16
View File
@@ -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)
+2 -2
View File
@@ -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');
+6 -6
View File
@@ -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"
+8
View File
@@ -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
+2 -2
View File
@@ -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[];
}
+11 -1
View File
@@ -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;
}
+33 -23
View File
@@ -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)
);
+5 -5
View File
@@ -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;
});
});
});
});
+18
View File
@@ -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