mirror of
https://github.com/appium/appium.git
synced 2026-05-12 22:08:40 -05:00
feat(typedoc-plugin-appium): create appium typedoc plugin
This is a typedoc plugin based on `typedoc-plugin-markdown`. It outputs everything that plugin outputs _plus_ it outputs information about commands (API endpoints) as defined in drivers, plugins, and the builtins in `@appium/base-driver` It's written in TS because that was the easiest way, because a) there was some sort of disagreement between Babel and TS re: exports, and b) everything it's built on is written in TS.
This commit is contained in:
Generated
+112
-74
@@ -155,6 +155,10 @@
|
||||
"resolved": "packages/test-support",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@appium/typedoc-plugin-appium": {
|
||||
"resolved": "packages/typedoc-plugin-appium",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@appium/types": {
|
||||
"resolved": "packages/types",
|
||||
"link": true
|
||||
@@ -2574,15 +2578,7 @@
|
||||
"wrap-ansi": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lerna/cli/node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@lerna/cli/node_modules/string-width": {
|
||||
"node_modules/@lerna/cli/node_modules/cliui/node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
@@ -2595,6 +2591,14 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@lerna/cli/node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@lerna/cli/node_modules/yargs": {
|
||||
"version": "16.2.0",
|
||||
"dev": true,
|
||||
@@ -2620,6 +2624,19 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@lerna/cli/node_modules/yargs/node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@lerna/collect-uncommitted": {
|
||||
"version": "6.1.0",
|
||||
"dev": true,
|
||||
@@ -4101,15 +4118,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@nrwl/cli": {
|
||||
"version": "15.2.3",
|
||||
"version": "15.2.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nx": "15.2.3"
|
||||
"nx": "15.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@nrwl/devkit": {
|
||||
"version": "15.2.3",
|
||||
"version": "15.2.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -4154,11 +4171,11 @@
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/@nrwl/tao": {
|
||||
"version": "15.2.3",
|
||||
"version": "15.2.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nx": "15.2.3"
|
||||
"nx": "15.2.1"
|
||||
},
|
||||
"bin": {
|
||||
"tao": "index.js"
|
||||
@@ -4621,7 +4638,7 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.14.190",
|
||||
"version": "4.14.191",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -5327,6 +5344,29 @@
|
||||
"resolved": "packages/appium",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/appium-adb": {
|
||||
"version": "9.10.17",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@appium/support": "^2.60.0",
|
||||
"@babel/runtime": "^7.0.0",
|
||||
"adbkit-apkreader": "^3.1.2",
|
||||
"async-lock": "^1.0.0",
|
||||
"asyncbox": "^2.6.0",
|
||||
"bluebird": "^3.4.7",
|
||||
"ini": "^3.0.0",
|
||||
"lodash": "^4.0.0",
|
||||
"lru-cache": "^7.3.0",
|
||||
"semver": "^7.0.0",
|
||||
"source-map-support": "^0.x",
|
||||
"teen_process": "^2.0.1",
|
||||
"utf7": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/aproba": {
|
||||
"version": "2.0.0",
|
||||
"license": "ISC"
|
||||
@@ -6321,7 +6361,7 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cacache/node_modules/minimatch": {
|
||||
"version": "5.1.1",
|
||||
"version": "5.1.0",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
@@ -9462,7 +9502,7 @@
|
||||
}
|
||||
},
|
||||
"node_modules/filelist/node_modules/minimatch": {
|
||||
"version": "5.1.1",
|
||||
"version": "5.1.0",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
@@ -12121,7 +12161,6 @@
|
||||
},
|
||||
"node_modules/handlebars": {
|
||||
"version": "4.7.7",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.5",
|
||||
@@ -12506,7 +12545,7 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ignore-walk/node_modules/minimatch": {
|
||||
"version": "5.1.1",
|
||||
"version": "5.1.0",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
@@ -13741,7 +13780,6 @@
|
||||
},
|
||||
"node_modules/jsonc-parser": {
|
||||
"version": "3.2.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jsonfile": {
|
||||
@@ -14847,7 +14885,6 @@
|
||||
},
|
||||
"node_modules/lunr": {
|
||||
"version": "2.3.9",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
@@ -14944,7 +14981,6 @@
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "4.2.3",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
@@ -16021,7 +16057,6 @@
|
||||
},
|
||||
"node_modules/neo-async": {
|
||||
"version": "2.6.2",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/next-tick": {
|
||||
@@ -16419,7 +16454,7 @@
|
||||
}
|
||||
},
|
||||
"node_modules/npm-packlist/node_modules/minimatch": {
|
||||
"version": "5.1.1",
|
||||
"version": "5.1.0",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
@@ -16746,13 +16781,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nx": {
|
||||
"version": "15.2.3",
|
||||
"version": "15.2.1",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nrwl/cli": "15.2.3",
|
||||
"@nrwl/tao": "15.2.3",
|
||||
"@nrwl/cli": "15.2.1",
|
||||
"@nrwl/tao": "15.2.1",
|
||||
"@parcel/watcher": "2.0.4",
|
||||
"@yarnpkg/lockfile": "^1.1.0",
|
||||
"@yarnpkg/parsers": "^3.0.0-rc.18",
|
||||
@@ -17664,6 +17699,8 @@
|
||||
},
|
||||
"node_modules/os-shim": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz",
|
||||
"integrity": "sha512-jd0cvB8qQ5uVt0lvCIexBaROw1KyKm5sbulg2fWOHjETisuCzWyt+eTZKEMs8v6HwzoGs8xik26jg7eCM6pS+A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
@@ -18300,9 +18337,10 @@
|
||||
},
|
||||
"node_modules/pre-commit": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/pre-commit/-/pre-commit-1.2.2.tgz",
|
||||
"integrity": "sha512-qokTiqxD6GjODy5ETAIgzsRgnBWWQHQH2ghy86PU7mIn/wuWeTwF3otyNQZxWBwVn8XNr8Tdzj/QfUXpH+gRZA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cross-spawn": "^5.0.1",
|
||||
"spawn-sync": "^1.0.15",
|
||||
@@ -18311,8 +18349,9 @@
|
||||
},
|
||||
"node_modules/pre-commit/node_modules/cross-spawn": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
|
||||
"integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lru-cache": "^4.0.1",
|
||||
"shebang-command": "^1.2.0",
|
||||
@@ -18321,8 +18360,9 @@
|
||||
},
|
||||
"node_modules/pre-commit/node_modules/lru-cache": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
|
||||
"integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"pseudomap": "^1.0.2",
|
||||
"yallist": "^2.1.2"
|
||||
@@ -18330,8 +18370,9 @@
|
||||
},
|
||||
"node_modules/pre-commit/node_modules/shebang-command": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
||||
"integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"shebang-regex": "^1.0.0"
|
||||
},
|
||||
@@ -18341,16 +18382,18 @@
|
||||
},
|
||||
"node_modules/pre-commit/node_modules/shebang-regex": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
|
||||
"integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pre-commit/node_modules/which": {
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz",
|
||||
"integrity": "sha512-16uPglFkRPzgiUXYMi1Jf8Z5EzN1iB4V0ZtMXcHZnwsBtQhhHeCqoWw7tsUY42hJGNDWtUsVLTjakIa5BgAxCw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
@@ -18360,8 +18403,9 @@
|
||||
},
|
||||
"node_modules/pre-commit/node_modules/yallist": {
|
||||
"version": "2.1.2",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
|
||||
"integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.2.1",
|
||||
@@ -18495,8 +18539,9 @@
|
||||
},
|
||||
"node_modules/pseudomap": {
|
||||
"version": "1.0.2",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
|
||||
"integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/public-encrypt": {
|
||||
"version": "4.0.3",
|
||||
@@ -18863,7 +18908,7 @@
|
||||
}
|
||||
},
|
||||
"node_modules/read-package-json/node_modules/minimatch": {
|
||||
"version": "5.1.1",
|
||||
"version": "5.1.0",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
@@ -20013,7 +20058,6 @@
|
||||
},
|
||||
"node_modules/shiki": {
|
||||
"version": "0.11.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jsonc-parser": "^3.0.0",
|
||||
@@ -20384,9 +20428,10 @@
|
||||
},
|
||||
"node_modules/spawn-sync": {
|
||||
"version": "1.0.15",
|
||||
"resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz",
|
||||
"integrity": "sha512-9DWBgrgYZzNghseho0JOuh+5fg9u6QWhAWa51QC7+U5rCheZ/j1DrEZnyE0RBBRqZ9uEXGPgSSM0nky6burpVw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"concat-stream": "^1.4.7",
|
||||
"os-shim": "^0.1.2"
|
||||
@@ -21690,7 +21735,6 @@
|
||||
},
|
||||
"node_modules/typedoc": {
|
||||
"version": "0.23.21",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"lunr": "^2.3.9",
|
||||
@@ -21710,7 +21754,6 @@
|
||||
},
|
||||
"node_modules/typedoc-plugin-markdown": {
|
||||
"version": "3.13.6",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"handlebars": "^4.7.7"
|
||||
@@ -21735,7 +21778,6 @@
|
||||
},
|
||||
"node_modules/typedoc/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
@@ -21743,7 +21785,6 @@
|
||||
},
|
||||
"node_modules/typedoc/node_modules/minimatch": {
|
||||
"version": "5.1.0",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
@@ -21754,7 +21795,6 @@
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "4.7.4",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
@@ -21783,7 +21823,6 @@
|
||||
},
|
||||
"node_modules/uglify-js": {
|
||||
"version": "3.17.4",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
@@ -22340,12 +22379,10 @@
|
||||
},
|
||||
"node_modules/vscode-oniguruma": {
|
||||
"version": "1.6.2",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vscode-textmate": {
|
||||
"version": "6.0.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/walk-up-path": {
|
||||
@@ -22577,7 +22614,6 @@
|
||||
},
|
||||
"node_modules/wordwrap": {
|
||||
"version": "1.0.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/workerpool": {
|
||||
@@ -23184,29 +23220,6 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"packages/doctor/node_modules/appium-adb": {
|
||||
"version": "9.10.17",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@appium/support": "^2.60.0",
|
||||
"@babel/runtime": "^7.0.0",
|
||||
"adbkit-apkreader": "^3.1.2",
|
||||
"async-lock": "^1.0.0",
|
||||
"asyncbox": "^2.6.0",
|
||||
"bluebird": "^3.4.7",
|
||||
"ini": "^3.0.0",
|
||||
"lodash": "^4.0.0",
|
||||
"lru-cache": "^7.3.0",
|
||||
"semver": "^7.0.0",
|
||||
"source-map-support": "^0.x",
|
||||
"teen_process": "^2.0.1",
|
||||
"utf7": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=8"
|
||||
}
|
||||
},
|
||||
"packages/doctor/node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"license": "MIT",
|
||||
@@ -23339,7 +23352,7 @@
|
||||
}
|
||||
},
|
||||
"packages/driver-test-support/node_modules/@types/lodash": {
|
||||
"version": "4.14.191",
|
||||
"version": "4.14.188",
|
||||
"license": "MIT"
|
||||
},
|
||||
"packages/eslint-config-appium": {
|
||||
@@ -23742,7 +23755,32 @@
|
||||
}
|
||||
},
|
||||
"packages/typedoc-plugin-appium": {
|
||||
"extraneous": true
|
||||
"name": "@appium/typedoc-plugin-appium",
|
||||
"version": "0.1.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"handlebars": "4.7.7",
|
||||
"type-fest": "3.2.0",
|
||||
"typedoc-plugin-markdown": "3.13.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typedoc": ">=0.23.14"
|
||||
}
|
||||
},
|
||||
"packages/typedoc-plugin-appium/node_modules/type-fest": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.2.0.tgz",
|
||||
"integrity": "sha512-Il3wdLRzWvbAEtocgxGQA9YOoRVeVUGOMBtel5LdEpNeEAol6GJTLw8GbX6Z8EIMfvfhoOXs2bwOijtAZdK5og==",
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"packages/types": {
|
||||
"name": "@appium/types",
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# `@appium/typedoc-plugin-appium`
|
||||
|
||||
> TypeDoc plugin for Appium & its extensions
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
const typedocPluginAppium = require('@appium/typedoc-plugin-appium');
|
||||
|
||||
// TODO: DEMONSTRATE API
|
||||
```
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('./build/plugin.js');
|
||||
@@ -0,0 +1,104 @@
|
||||
import {Context, Logger, ReflectionKind} from 'typedoc';
|
||||
import {AppiumPluginLogger} from '../logger';
|
||||
import {
|
||||
CommandInfo,
|
||||
CommandRef,
|
||||
CommandReflection,
|
||||
CommandsReflection,
|
||||
ExecuteCommandRef,
|
||||
NAME_EXECUTE_ROUTE,
|
||||
ParentReflection,
|
||||
ProjectCommands,
|
||||
Route,
|
||||
} from '../model';
|
||||
|
||||
export class CommandTreeBuilder {
|
||||
#log: AppiumPluginLogger;
|
||||
|
||||
constructor(log: AppiumPluginLogger) {
|
||||
this.#log = log.createChildLogger('builder');
|
||||
}
|
||||
|
||||
public createReflections(ctx: Context, commands: ProjectCommands): void {
|
||||
const {project} = ctx;
|
||||
const modules = project.getChildrenByKind(ReflectionKind.Module);
|
||||
const projectCmdInfo = commands.get(project);
|
||||
if (modules.length) {
|
||||
for (const module of modules) {
|
||||
const commandInfo = commands.get(module);
|
||||
if (commandInfo) {
|
||||
this.#createCommandsReflection(module, ctx, commandInfo);
|
||||
}
|
||||
}
|
||||
} else if (projectCmdInfo?.hasCommands) {
|
||||
this.#createCommandsReflection(project, ctx, projectCmdInfo);
|
||||
} else {
|
||||
this.#log.warn(`No commands found in project ${project.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and adds a child {@linkcode CommandReflection} to this reflection
|
||||
* @param ref Command reference
|
||||
* @param route Route
|
||||
* @param parent Commands reflection
|
||||
*/
|
||||
#createCommandReflection(
|
||||
ctx: Context,
|
||||
ref: CommandRef | ExecuteCommandRef,
|
||||
route: Route,
|
||||
parent: CommandsReflection
|
||||
) {
|
||||
const commandReflection = new CommandReflection(ref, parent.parent, route, parent);
|
||||
ctx.postReflectionCreation(commandReflection, undefined, undefined);
|
||||
ctx.finalizeDeclarationReflection(commandReflection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@linkcode CommandsReflection}
|
||||
* @param module - Parent module (or project)
|
||||
* @param ctx - Current context
|
||||
* @param commandInfo - Command information for `module`
|
||||
* @returns New {@linkcode CommandsReflection}
|
||||
*/
|
||||
#createCommandsReflection(
|
||||
module: ParentReflection,
|
||||
ctx: Context,
|
||||
commandInfo: CommandInfo
|
||||
): CommandsReflection {
|
||||
const commandsRef = new CommandsReflection(module.name, ctx.project, commandInfo, module);
|
||||
ctx.postReflectionCreation(commandsRef, undefined, undefined);
|
||||
|
||||
const {routes, executeCommands} = commandInfo;
|
||||
|
||||
// sort routes in alphabetical order
|
||||
const sortedRoutes = new Map([...routes.entries()].sort());
|
||||
for (const [route, commandMap] of sortedRoutes) {
|
||||
for (const ref of commandMap.values()) {
|
||||
this.#createCommandReflection(ctx.withScope(commandsRef), ref, route, commandsRef);
|
||||
}
|
||||
}
|
||||
|
||||
// sort execute commands in alphabetical order
|
||||
const sortedExecuteCommands = new Set([...executeCommands].sort());
|
||||
for (const executeCommandRef of sortedExecuteCommands) {
|
||||
this.#createCommandReflection(
|
||||
ctx.withScope(commandsRef),
|
||||
executeCommandRef,
|
||||
NAME_EXECUTE_ROUTE,
|
||||
commandsRef
|
||||
);
|
||||
}
|
||||
|
||||
ctx.finalizeDeclarationReflection(commandsRef);
|
||||
return commandsRef;
|
||||
}
|
||||
}
|
||||
|
||||
export function createReflections(
|
||||
ctx: Context,
|
||||
log: AppiumPluginLogger,
|
||||
commands: ProjectCommands
|
||||
): void {
|
||||
new CommandTreeBuilder(log).createReflections(ctx, commands);
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
import {Context, DeclarationReflection, Logger, ReflectionKind} from 'typedoc';
|
||||
import {AppiumPluginLogger} from '../logger';
|
||||
import {
|
||||
AllowedHttpMethod,
|
||||
CommandInfo,
|
||||
CommandMap,
|
||||
ExecuteCommandSet,
|
||||
ParentReflection,
|
||||
ProjectCommands,
|
||||
RouteMap,
|
||||
} from '../model';
|
||||
import {
|
||||
isDeclarationReflection,
|
||||
isIntrinsicType,
|
||||
isLiteralType,
|
||||
isReflectionType,
|
||||
isTupleType,
|
||||
isTypeOperatorType,
|
||||
} from '../guards';
|
||||
import {BaseDriverDeclarationReflection, MethodMapDeclarationReflection} from './types';
|
||||
|
||||
/**
|
||||
* Name of the static `newMethodMap` property in a Driver
|
||||
*/
|
||||
export const NAME_NEW_METHOD_MAP = 'newMethodMap';
|
||||
/**
|
||||
* Name of the static `executeMethodMap` property in a Driver
|
||||
*/
|
||||
export const NAME_EXECUTE_METHOD_MAP = 'executeMethodMap';
|
||||
|
||||
/**
|
||||
* Name of the builtin method map in `@appium/base-driver`
|
||||
*/
|
||||
export const NAME_METHOD_MAP = 'METHOD_MAP';
|
||||
|
||||
/**
|
||||
* Name of the field in a method map's parameters prop which contains required parameters
|
||||
*/
|
||||
export const NAME_REQUIRED = 'required';
|
||||
/**
|
||||
* Name of the field in a method map's parameters prop which contains optional parameters
|
||||
*/
|
||||
export const NAME_OPTIONAL = 'optional';
|
||||
/**
|
||||
* Name of the field in an _execute_ method map which contains parameters
|
||||
*/
|
||||
export const NAME_PARAMS = 'params';
|
||||
/**
|
||||
* Name of the command in a method map
|
||||
*/
|
||||
export const NAME_COMMAND = 'command';
|
||||
|
||||
/**
|
||||
* Name of the field in a _regular_ method map which contains parameters
|
||||
*/
|
||||
export const NAME_PAYLOAD_PARAMS = 'payloadParams';
|
||||
|
||||
/**
|
||||
* Name of the module which contains the builtin method map
|
||||
*/
|
||||
export const NAME_BUILTIN_COMMAND_MODULE = '@appium/base-driver';
|
||||
|
||||
function isBaseDriverDeclarationReflection(value: any): value is BaseDriverDeclarationReflection {
|
||||
return (
|
||||
value instanceof DeclarationReflection &&
|
||||
value.name === NAME_BUILTIN_COMMAND_MODULE &&
|
||||
value.kindOf(ReflectionKind.Module)
|
||||
);
|
||||
}
|
||||
|
||||
function isMethodMapDeclarationReflection(value: any): value is MethodMapDeclarationReflection {
|
||||
return (
|
||||
isDeclarationReflection(value) &&
|
||||
((value.name === NAME_NEW_METHOD_MAP && value.flags.isStatic) ||
|
||||
value.name === NAME_METHOD_MAP) &&
|
||||
isReflectionType(value.type) &&
|
||||
isDeclarationReflection(value.type.declaration)
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Converts declarations to information about Appium commands
|
||||
*/
|
||||
export class CommandConverter {
|
||||
#ctx: Context;
|
||||
#log: AppiumPluginLogger;
|
||||
|
||||
constructor(ctx: Context, log: AppiumPluginLogger) {
|
||||
this.#ctx = ctx;
|
||||
this.#log = log.createChildLogger('converter');
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts declarations into command information
|
||||
*
|
||||
* @returns Command info for entire project
|
||||
*/
|
||||
public convert(): ProjectCommands {
|
||||
const ctx = this.#ctx;
|
||||
const {project} = ctx;
|
||||
const projectCommands: ProjectCommands = new Map();
|
||||
|
||||
// handle baseDriver if it's present
|
||||
const baseDriver = project.getChildByName(NAME_BUILTIN_COMMAND_MODULE);
|
||||
if (baseDriver && isBaseDriverDeclarationReflection(baseDriver)) {
|
||||
this.#log.verbose('Found %s', NAME_BUILTIN_COMMAND_MODULE);
|
||||
projectCommands.set(baseDriver, this.#convertBaseDriver(baseDriver));
|
||||
} else {
|
||||
this.#log.verbose('Did not find %s', NAME_BUILTIN_COMMAND_MODULE);
|
||||
}
|
||||
|
||||
// convert all modules (or just project if no modules)
|
||||
const modules = project.getChildrenByKind(ReflectionKind.Module);
|
||||
if (modules.length) {
|
||||
for (const mod of modules) {
|
||||
this.#log.verbose('Converting module %s', mod.name);
|
||||
const cmdInfo = this.#convertModuleClasses(mod);
|
||||
if (cmdInfo.hasCommands) {
|
||||
projectCommands.set(mod, this.#convertModuleClasses(mod));
|
||||
}
|
||||
this.#log.info('Converted module %s', mod.name);
|
||||
}
|
||||
} else {
|
||||
projectCommands.set(project, this.#convertModuleClasses(project));
|
||||
}
|
||||
|
||||
this.#log.info('Found commands in %d module(s)', projectCommands.size);
|
||||
|
||||
return projectCommands;
|
||||
}
|
||||
|
||||
#convertBaseDriver(baseDriver: BaseDriverDeclarationReflection): CommandInfo {
|
||||
const baseDriverRoutes = this.#convertMethodMap(baseDriver);
|
||||
if (!baseDriverRoutes.size) {
|
||||
throw new TypeError(`Could not find any commands in BaseDriver!?`);
|
||||
}
|
||||
|
||||
// no execute commands in BaseDriver
|
||||
return new CommandInfo(baseDriverRoutes);
|
||||
}
|
||||
|
||||
#convertExecuteMethodMap(refl: DeclarationReflection): ExecuteCommandSet {
|
||||
const executeMethodMap = refl.getChildByName(NAME_EXECUTE_METHOD_MAP);
|
||||
const commandRefs: ExecuteCommandSet = new Set();
|
||||
if (
|
||||
executeMethodMap?.flags.isStatic &&
|
||||
isDeclarationReflection(executeMethodMap) &&
|
||||
isReflectionType(executeMethodMap.type)
|
||||
) {
|
||||
for (const newMethodProp of executeMethodMap.type.declaration.getChildrenByKind(
|
||||
ReflectionKind.Property
|
||||
)) {
|
||||
const comment = newMethodProp.comment;
|
||||
const script = newMethodProp.originalName;
|
||||
if (isDeclarationReflection(newMethodProp) && isReflectionType(newMethodProp.type)) {
|
||||
const commandProp = newMethodProp.type.declaration.getChildByName(NAME_COMMAND);
|
||||
if (isDeclarationReflection(commandProp) && isLiteralType(commandProp.type)) {
|
||||
const command = String(commandProp.type.value);
|
||||
const paramsProp = newMethodProp.type.declaration.getChildByName(NAME_PARAMS);
|
||||
let requiredParams: string[] = [];
|
||||
let optionalParams: string[] = [];
|
||||
if (isDeclarationReflection(paramsProp)) {
|
||||
requiredParams = this.#parseRequiredParams(paramsProp);
|
||||
optionalParams = this.#parseOptionalParams(paramsProp);
|
||||
}
|
||||
commandRefs.add({
|
||||
command,
|
||||
requiredParams,
|
||||
optionalParams,
|
||||
script,
|
||||
comment,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return commandRefs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts information about `MethodMap` objects
|
||||
* @param refl - Some reflection we want to inspect. Could refer to a module or a class
|
||||
* @returns Lookup of routes to {@linkcode CommandMap} objects
|
||||
*/
|
||||
#convertMethodMap(refl: DeclarationReflection): RouteMap {
|
||||
const routes: RouteMap = new Map();
|
||||
|
||||
let methodMap: MethodMapDeclarationReflection;
|
||||
const child = isBaseDriverDeclarationReflection(refl)
|
||||
? refl.getChildByName(NAME_METHOD_MAP)
|
||||
: refl.getChildByName(NAME_NEW_METHOD_MAP);
|
||||
|
||||
if (isMethodMapDeclarationReflection(child)) {
|
||||
methodMap = child;
|
||||
const routeProps = methodMap.type.declaration.getChildrenByKind(ReflectionKind.Property);
|
||||
if (!routeProps.length) {
|
||||
this.#log.warn(`No routes found in ${refl.name}`);
|
||||
}
|
||||
for (const routeProp of routeProps) {
|
||||
const route = routeProp.originalName;
|
||||
if (isDeclarationReflection(routeProp) && isReflectionType(routeProp.type)) {
|
||||
const httpMethodProps = routeProp.type.declaration.getChildrenByKind(
|
||||
ReflectionKind.Property
|
||||
);
|
||||
if (!httpMethodProps.length) {
|
||||
this.#log.warn(`No HTTP methods found in route ${refl.name}.${route}`);
|
||||
}
|
||||
for (const httpMethodProp of httpMethodProps) {
|
||||
const comment = httpMethodProp.comment;
|
||||
const httpMethod = httpMethodProp.originalName as AllowedHttpMethod;
|
||||
if (isDeclarationReflection(httpMethodProp) && isReflectionType(httpMethodProp.type)) {
|
||||
const commandProp = httpMethodProp.type.declaration.getChildByName(NAME_COMMAND);
|
||||
// commandProp is optional.
|
||||
if (commandProp) {
|
||||
if (isDeclarationReflection(commandProp) && isLiteralType(commandProp.type)) {
|
||||
const command = String(commandProp.type.value);
|
||||
const payloadParamsProp =
|
||||
httpMethodProp.type.declaration.getChildByName(NAME_PAYLOAD_PARAMS);
|
||||
let requiredParams: string[] = [];
|
||||
let optionalParams: string[] = [];
|
||||
if (isDeclarationReflection(payloadParamsProp)) {
|
||||
requiredParams = this.#parseRequiredParams(payloadParamsProp);
|
||||
optionalParams = this.#parseOptionalParams(payloadParamsProp);
|
||||
}
|
||||
let commandMap: CommandMap = routes.get(route) ?? new Map();
|
||||
commandMap.set(command, {
|
||||
command,
|
||||
requiredParams,
|
||||
optionalParams,
|
||||
httpMethod,
|
||||
route,
|
||||
comment,
|
||||
});
|
||||
routes.set(route, commandMap);
|
||||
} else if (
|
||||
isDeclarationReflection(commandProp) &&
|
||||
isIntrinsicType(commandProp.type)
|
||||
) {
|
||||
this.#log.warn(
|
||||
`Found intrinsic type for ${commandProp.originalName} ("${commandProp.type.name}"). ${refl.name} must be defined "as const" to associate with a method.`
|
||||
);
|
||||
} else {
|
||||
this.#log.warn(
|
||||
`Found unknown type for ${commandProp.originalName}: ${commandProp}`
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.#log.warn(`Invalid {MethodMap} found in ${refl.name}.${route}.${httpMethod}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.#log.warn(`Empty route in ${refl.name}.${route}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.#log.verbose(`No {MethodMap} found in class ${refl.name}`);
|
||||
}
|
||||
|
||||
return routes;
|
||||
}
|
||||
|
||||
#convertModuleClasses(mod: ParentReflection) {
|
||||
let routes: RouteMap = new Map();
|
||||
let executeCommands: ExecuteCommandSet = new Set();
|
||||
|
||||
const classReflections = mod.getChildrenByKind(ReflectionKind.Class);
|
||||
for (const classRef of classReflections) {
|
||||
this.#log.verbose(`Converting class ${classRef.name}`);
|
||||
const newMethodMap = this.#convertMethodMap(classRef);
|
||||
|
||||
if (newMethodMap.size) {
|
||||
routes = new Map([...routes, ...newMethodMap]);
|
||||
}
|
||||
|
||||
const executeMethodMap = this.#convertExecuteMethodMap(classRef);
|
||||
if (executeMethodMap.size) {
|
||||
executeCommands = new Set([...executeCommands, ...executeMethodMap]);
|
||||
}
|
||||
this.#log.verbose(`Converted class ${classRef.name}`);
|
||||
}
|
||||
|
||||
return new CommandInfo(routes, executeCommands);
|
||||
}
|
||||
|
||||
#parseOptionalParams(prop: DeclarationReflection): string[] {
|
||||
return this.#parseParams(prop, NAME_OPTIONAL);
|
||||
}
|
||||
|
||||
#parseParams(prop: DeclarationReflection, name: string): string[] {
|
||||
const params = [];
|
||||
if (isReflectionType(prop.type)) {
|
||||
const requiredProp = prop.type.declaration.getChildByName(name);
|
||||
if (
|
||||
isDeclarationReflection(requiredProp) &&
|
||||
isTypeOperatorType(requiredProp.type) &&
|
||||
isTupleType(requiredProp.type.target)
|
||||
) {
|
||||
for (const reqd of requiredProp.type.target.elements) {
|
||||
if (isLiteralType(reqd)) {
|
||||
params.push(String(reqd.value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
#parseRequiredParams(prop: DeclarationReflection): string[] {
|
||||
return this.#parseParams(prop, NAME_REQUIRED);
|
||||
}
|
||||
}
|
||||
|
||||
export function convertCommands(ctx: Context, log: AppiumPluginLogger): ProjectCommands {
|
||||
return new CommandConverter(ctx, log).convert();
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './converter';
|
||||
export * from './builder';
|
||||
@@ -0,0 +1,11 @@
|
||||
import {DeclarationReflection, ReflectionType} from 'typedoc';
|
||||
import {NAME_BUILTIN_COMMAND_MODULE, NAME_METHOD_MAP, NAME_NEW_METHOD_MAP} from './converter';
|
||||
|
||||
export type MethodMapDeclarationReflection = DeclarationReflection & {
|
||||
name: typeof NAME_METHOD_MAP | typeof NAME_NEW_METHOD_MAP;
|
||||
type: ReflectionType;
|
||||
};
|
||||
|
||||
export type BaseDriverDeclarationReflection = DeclarationReflection & {
|
||||
name: typeof NAME_BUILTIN_COMMAND_MODULE;
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
import {
|
||||
DeclarationReflection,
|
||||
ReflectionType,
|
||||
TypeOperatorType,
|
||||
TupleType,
|
||||
LiteralType,
|
||||
IntrinsicType,
|
||||
} from 'typedoc';
|
||||
|
||||
export function isDeclarationReflection(value: any): value is DeclarationReflection {
|
||||
return value instanceof DeclarationReflection;
|
||||
}
|
||||
export function isReflectionType(value: any): value is ReflectionType {
|
||||
return value instanceof ReflectionType;
|
||||
}
|
||||
export function isTypeOperatorType(value: any): value is TypeOperatorType {
|
||||
return value instanceof TypeOperatorType;
|
||||
}
|
||||
export function isLiteralType(value: any): value is LiteralType {
|
||||
return value instanceof LiteralType;
|
||||
}
|
||||
export function isIntrinsicType(value: any): value is IntrinsicType {
|
||||
return value instanceof IntrinsicType;
|
||||
}
|
||||
export function isTupleType(value: any): value is TupleType {
|
||||
return value instanceof TupleType;
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Adapted from `@knodes/typedoc-pluginutils`
|
||||
*
|
||||
* Portions Copyright (c) 2022 KnodesCommunity
|
||||
* Licensed MIT
|
||||
*
|
||||
* @module
|
||||
* @see https://github.com/KnodesCommunity/typedoc-plugins/blob/ed5e4e87f5d80abf6352e8de353ea376c4f7db6d/packages/pluginutils/src/plugin-logger.ts
|
||||
*
|
||||
*/
|
||||
|
||||
import {format} from 'node:util';
|
||||
import {Logger, LogLevel} from 'typedoc';
|
||||
|
||||
const LogMethods = {
|
||||
[LogLevel.Error]: 'error',
|
||||
[LogLevel.Warn]: 'warn',
|
||||
[LogLevel.Info]: 'info',
|
||||
[LogLevel.Verbose]: 'verbose',
|
||||
} as const;
|
||||
|
||||
export class AppiumPluginLogger extends Logger {
|
||||
/**
|
||||
* Function provided by `AppiumPluginLogger` parent loggers to log through them.
|
||||
*/
|
||||
readonly #logThroughParent?: ParentLogger;
|
||||
/**
|
||||
* Parent logger
|
||||
*/
|
||||
readonly #parent: Logger;
|
||||
|
||||
/**
|
||||
* Namespace to prepend to log messages
|
||||
*/
|
||||
public readonly ns: string;
|
||||
|
||||
public constructor(logger: Logger, ns: string, logThroughParent?: ParentLogger) {
|
||||
super();
|
||||
this.#parent = logger;
|
||||
this.ns = ns;
|
||||
this.level = this.#parent.level;
|
||||
this.#logThroughParent = logThroughParent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the given error message.
|
||||
*
|
||||
* @param text - The error that should be logged.
|
||||
*/
|
||||
public error(text: string, ...args: any[]): void {
|
||||
this.#log(LogLevel.Error, text, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the given info message.
|
||||
*
|
||||
* @param text - The message that should be logged.
|
||||
*/
|
||||
public info(text: string, ...args: any[]): void {
|
||||
this.#log(LogLevel.Info, text, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a log message.
|
||||
*
|
||||
* Does _not_ support `printf`-style syntax for compatibility with {@linkcode Logger}.
|
||||
*
|
||||
* @param text - The message itself.
|
||||
* @param level - The urgency of the log message.
|
||||
*/
|
||||
public log(text: string, level: LogLevel): void {
|
||||
this.#log(level, text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link AppiumPluginLogger} for the given context.
|
||||
*
|
||||
* @param ns - New sub-namespace; will be appended to the current namespace.
|
||||
* @returns the new logger.
|
||||
*/
|
||||
public createChildLogger(ns: string) {
|
||||
const newLogger = new AppiumPluginLogger(
|
||||
this.#parent,
|
||||
`${this.ns}:${ns}`,
|
||||
this.#logThrough.bind(this)
|
||||
);
|
||||
newLogger.level = this.level;
|
||||
return newLogger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the given verbose message.
|
||||
*
|
||||
* @param text - The message that should be logged.
|
||||
*/
|
||||
public verbose(text: string, ...args: any): void {
|
||||
this.#log(LogLevel.Verbose, text, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the given warning message.
|
||||
*
|
||||
* @param text - The warning that should be logged.
|
||||
*/
|
||||
public warn(text: string, ...args: any[]): void {
|
||||
this.#log(LogLevel.Warn, text, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the given message.
|
||||
*
|
||||
* Uses the `util.format` function to format the message.
|
||||
*
|
||||
* @param ns - Namespace
|
||||
* @param message - The message to format.
|
||||
* @returns the formatted message;
|
||||
*/
|
||||
#formatMessage(ns: string, message: string, ...args: any[]) {
|
||||
return format(`[${ns}] ${message}`, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a log message.
|
||||
*
|
||||
* @param text - The message itself.
|
||||
* @param level - The urgency of the log message.
|
||||
*/
|
||||
#log(level: LogLevel, text: string, ...args: any[]): void {
|
||||
if (level < this.level) {
|
||||
return;
|
||||
}
|
||||
this.#logThrough(level, this.ns, text, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass a log message to the parent.
|
||||
*
|
||||
* @param level - The urgency of the log message.
|
||||
* @param message - The message itself.
|
||||
*/
|
||||
#logThrough(level: LogLevel, ns: string, message: string, ...args: any[]) {
|
||||
if (this.#logThroughParent) {
|
||||
this.#logThroughParent(level, ns, message, ...args);
|
||||
} else {
|
||||
const parentMethod = LogMethods[level];
|
||||
this.#parent[parentMethod](this.#formatMessage(ns, message, ...args));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type ParentLogger = (level: LogLevel, message: string, ...args: any[]) => void;
|
||||
@@ -0,0 +1,18 @@
|
||||
import {ExecuteCommandSet, RouteMap} from './types';
|
||||
|
||||
/**
|
||||
* Data structure describing routes and commands for a particular module (or project)
|
||||
*/
|
||||
export class CommandInfo {
|
||||
constructor(
|
||||
public readonly routes: RouteMap,
|
||||
public readonly executeCommands: ExecuteCommandSet = new Set()
|
||||
) {}
|
||||
|
||||
/**
|
||||
* `true` if this instance has some actual data
|
||||
*/
|
||||
public get hasCommands() {
|
||||
return this.executeCommands.size > 0 || this.routes.size > 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './types';
|
||||
export * from './reflection';
|
||||
export * from './command-info';
|
||||
@@ -0,0 +1,66 @@
|
||||
import {Comment} from 'typedoc';
|
||||
import {CommandRef, ExecuteCommandRef, ParentReflection, Route} from '../types';
|
||||
import {CommandsReflection} from './commands';
|
||||
import {AppiumPluginReflectionKind} from './kind';
|
||||
import {AppiumPluginReflection} from './plugin';
|
||||
|
||||
/**
|
||||
* The route will be this
|
||||
*/
|
||||
export const NAME_EXECUTE_ROUTE = '/session/:sessionId/execute';
|
||||
|
||||
export const HTTP_METHOD_EXECUTE = 'POST';
|
||||
|
||||
export class CommandReflection extends AppiumPluginReflection {
|
||||
public readonly httpMethod: string;
|
||||
public readonly optionalParams: string[];
|
||||
public readonly requiredParams: string[];
|
||||
public readonly route: Route;
|
||||
public readonly script?: string;
|
||||
public readonly comment?: Comment;
|
||||
|
||||
constructor(
|
||||
readonly commandRef: CommandRef | ExecuteCommandRef,
|
||||
module: ParentReflection,
|
||||
route: Route,
|
||||
parent: CommandsReflection
|
||||
) {
|
||||
const name = CommandReflection.isExecuteCommandRef(commandRef) ? commandRef.script : route;
|
||||
super(
|
||||
name,
|
||||
(CommandReflection.isExecuteCommandRef(commandRef)
|
||||
? AppiumPluginReflectionKind.EXECUTE_COMMAND
|
||||
: AppiumPluginReflectionKind.COMMAND) as any,
|
||||
module,
|
||||
parent
|
||||
);
|
||||
|
||||
this.route = route;
|
||||
this.httpMethod = 'httpMethod' in commandRef ? commandRef.httpMethod : HTTP_METHOD_EXECUTE;
|
||||
this.requiredParams = commandRef.requiredParams ?? [];
|
||||
this.optionalParams = commandRef.optionalParams ?? [];
|
||||
this.script = 'script' in commandRef ? commandRef.script : undefined;
|
||||
this.comment = commandRef.comment;
|
||||
}
|
||||
|
||||
public get hasRequiredParams(): boolean {
|
||||
return Boolean(this.requiredParams.length);
|
||||
}
|
||||
|
||||
public get hasOptionalParams(): boolean {
|
||||
return Boolean(this.optionalParams.length);
|
||||
}
|
||||
|
||||
public get isExecuteCommand(): boolean {
|
||||
return Boolean(this.script && this.route === NAME_EXECUTE_ROUTE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for execute command refs
|
||||
* @param ref Command reference
|
||||
* @returns `true` if it's an execute command
|
||||
*/
|
||||
public static isExecuteCommandRef(ref: CommandRef | ExecuteCommandRef): ref is ExecuteCommandRef {
|
||||
return 'script' in ref;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import {CommandInfo} from '../command-info';
|
||||
import {ExecuteCommandSet, ParentReflection, RouteMap} from '../types';
|
||||
|
||||
import {AppiumPluginReflectionKind} from './kind';
|
||||
import {AppiumPluginReflection} from './plugin';
|
||||
|
||||
export class CommandsReflection extends AppiumPluginReflection {
|
||||
public readonly executeCommandRefs: ExecuteCommandSet;
|
||||
public readonly routeMap: RouteMap;
|
||||
|
||||
constructor(
|
||||
name: string,
|
||||
module: ParentReflection,
|
||||
commands: CommandInfo,
|
||||
public override parent: ParentReflection = module
|
||||
) {
|
||||
super(name, AppiumPluginReflectionKind.COMMANDS as any, module, parent);
|
||||
this.parent = parent;
|
||||
this.routeMap = commands.routes;
|
||||
this.executeCommandRefs = commands.executeCommands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the module to which this command belongs.
|
||||
*
|
||||
* @see {CommandTreeBuilder.getName}
|
||||
*/
|
||||
public get moduleName(): string {
|
||||
return this.module.name;
|
||||
}
|
||||
|
||||
public get hasExecuteCommands(): boolean {
|
||||
return Boolean(this.executeCommandRefs.size);
|
||||
}
|
||||
|
||||
public get hasRoutes(): boolean {
|
||||
return Boolean(this.routeMap.size);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from './command';
|
||||
export * from './commands';
|
||||
export * from './kind';
|
||||
export * from './plugin';
|
||||
@@ -0,0 +1,27 @@
|
||||
import {ReflectionKind} from 'typedoc';
|
||||
import {addReflectionKind} from './utils';
|
||||
|
||||
export const NS = 'appium';
|
||||
|
||||
/**
|
||||
* Extends the {@link ReflectionKind} to add custom Page, Menu & Any kinds.
|
||||
*/
|
||||
export enum AppiumPluginReflectionKind {
|
||||
ROOT = addReflectionKind(NS, 'Root'),
|
||||
COMMANDS = addReflectionKind(NS, 'Commands'),
|
||||
COMMAND = addReflectionKind(NS, 'Command'),
|
||||
EXECUTE_COMMAND = addReflectionKind(NS, 'ExecuteCommand'),
|
||||
MENU = addReflectionKind(NS, 'Menu'),
|
||||
ANY = addReflectionKind(NS, 'Any', MENU | COMMAND | EXECUTE_COMMAND | COMMANDS),
|
||||
}
|
||||
addReflectionKind(
|
||||
NS,
|
||||
'Root Commands',
|
||||
AppiumPluginReflectionKind.ROOT | AppiumPluginReflectionKind.COMMANDS
|
||||
);
|
||||
addReflectionKind(
|
||||
NS,
|
||||
'Root Menu',
|
||||
AppiumPluginReflectionKind.ROOT | AppiumPluginReflectionKind.MENU
|
||||
);
|
||||
AppiumPluginReflectionKind as unknown as ReflectionKind;
|
||||
@@ -0,0 +1,16 @@
|
||||
import {DeclarationReflection, Reflection} from 'typedoc';
|
||||
import {ParentReflection} from '../types';
|
||||
|
||||
/**
|
||||
* Abstract base class for all reflections defined by this plugin
|
||||
*/
|
||||
export abstract class AppiumPluginReflection extends DeclarationReflection {
|
||||
constructor(
|
||||
name: string,
|
||||
kind: number,
|
||||
public readonly module: ParentReflection,
|
||||
parent: Reflection = module
|
||||
) {
|
||||
super(name, kind, parent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
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 = (ns: string, name: string, value?: number | null) => {
|
||||
const fullName = `${ns}:${name}`;
|
||||
|
||||
const kindAny = ReflectionKind as any;
|
||||
const existingValue = kindAny[fullName];
|
||||
if (existingValue !== null && existingValue !== undefined) {
|
||||
return existingValue;
|
||||
}
|
||||
const defaultedValue = value ?? getHigherBitMask() * 2;
|
||||
kindAny[fullName] = defaultedValue;
|
||||
kindAny[defaultedValue] = fullName;
|
||||
return defaultedValue;
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import {Comment, DeclarationReflection, ProjectReflection} from 'typedoc';
|
||||
import {CommandInfo} from './command-info';
|
||||
import {CommandsReflection} from './reflection';
|
||||
|
||||
/**
|
||||
* I don't think we allow other HTTP methods?
|
||||
*/
|
||||
export type AllowedHttpMethod = 'GET' | 'POST' | 'DELETE';
|
||||
export type Route = string;
|
||||
export type Command = string;
|
||||
|
||||
export type CommandMap = Map<Command, CommandRef>;
|
||||
|
||||
export type ProjectCommands = Map<ParentReflection, CommandInfo>;
|
||||
|
||||
export interface BaseCommandRef {
|
||||
command: string;
|
||||
optionalParams?: string[];
|
||||
requiredParams?: string[];
|
||||
comment?: Comment;
|
||||
}
|
||||
|
||||
export interface CommandRef extends BaseCommandRef {
|
||||
httpMethod: AllowedHttpMethod;
|
||||
route: Route;
|
||||
}
|
||||
|
||||
export interface ExecuteCommandRef extends BaseCommandRef {
|
||||
script: string;
|
||||
}
|
||||
|
||||
export type ParentReflection = DeclarationReflection | ProjectReflection;
|
||||
export type CommandReflectionMap = Map<ParentReflection, CommandsReflection>;
|
||||
|
||||
export type RouteMap = Map<Route, CommandMap>;
|
||||
|
||||
export type ExecuteCommandSet = Set<ExecuteCommandRef>;
|
||||
@@ -0,0 +1 @@
|
||||
export * from './theme';
|
||||
@@ -0,0 +1,57 @@
|
||||
import {ContainerReflection, PageEvent, Renderer} from 'typedoc';
|
||||
import {MarkdownTheme} from 'typedoc-plugin-markdown';
|
||||
import {AppiumPluginLogger} from '../../logger';
|
||||
import {AppiumPluginReflectionKind} from '../../model';
|
||||
import {compileTemplate, registerHelpers, Template} from './utils';
|
||||
|
||||
/**
|
||||
* Name of the theme; used at definition time
|
||||
*/
|
||||
export const THEME_NAME = 'appium';
|
||||
|
||||
/**
|
||||
* Factory for `AppiumTheme` class; needs custom logger otherwise inaccessible
|
||||
* @param log - Custom logger
|
||||
* @returns `AppiumTheme` class
|
||||
*/
|
||||
export function getTheme(log: AppiumPluginLogger): new (renderer: Renderer) => MarkdownTheme {
|
||||
return class AppiumTheme extends MarkdownTheme {
|
||||
#log = log.createChildLogger('theme');
|
||||
|
||||
#commandsTemplateRenderer: TemplateRenderer;
|
||||
|
||||
constructor(renderer: Renderer) {
|
||||
super(renderer);
|
||||
|
||||
this.#commandsTemplateRenderer = this.#getTemplate(Template.Commands);
|
||||
// this ensures we overwrite what MarkdownTheme does
|
||||
registerHelpers();
|
||||
}
|
||||
|
||||
public override get mappings() {
|
||||
return [
|
||||
{
|
||||
kind: [AppiumPluginReflectionKind.COMMANDS as any],
|
||||
isLeaf: true,
|
||||
directory: 'commands',
|
||||
template: this.#commandsTemplateRenderer,
|
||||
},
|
||||
...super.mappings,
|
||||
];
|
||||
}
|
||||
|
||||
#getTemplate(template: Template): TemplateRenderer {
|
||||
const render = compileTemplate(template);
|
||||
return (pageEvent: PageEvent<ContainerReflection>) => {
|
||||
this.#log.verbose('Rendering template for model %s', pageEvent.model.name);
|
||||
return render(pageEvent, {
|
||||
allowProtoMethodsByDefault: true,
|
||||
allowProtoPropertiesByDefault: true,
|
||||
data: {theme: this},
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
type TemplateRenderer = (pageEvent: PageEvent<ContainerReflection>) => string;
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './types';
|
||||
export * from './appium';
|
||||
@@ -0,0 +1,14 @@
|
||||
import {RenderTemplate, RendererEvent, Theme} from 'typedoc';
|
||||
import {CommandReflection} from '../../model';
|
||||
|
||||
export type RenderCommandLinkProps = {page: CommandReflection; label?: string};
|
||||
export interface IAppiumPluginThemeMethods {
|
||||
renderPageLink: RenderTemplate<RenderCommandLinkProps>;
|
||||
}
|
||||
export interface IAppiumPluginTheme extends Theme {
|
||||
appiumPlugin(event: RendererEvent): IAppiumPluginThemeMethods;
|
||||
}
|
||||
|
||||
export function isAppiumPluginTheme(theme: Theme): theme is IAppiumPluginTheme {
|
||||
return 'appiumPlugin' in theme;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import _ from 'lodash';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import Handlebars from 'handlebars';
|
||||
import {ContainerReflection, PageEvent, ReflectionKind} from 'typedoc';
|
||||
import {AppiumPluginReflectionKind} from '../../model';
|
||||
|
||||
const RESOURCES_PATH = path.join(__dirname, '..', '..', '..', 'resources');
|
||||
const TEMPLATE_PATH = path.join(RESOURCES_PATH, 'templates');
|
||||
const PARTIALS_PATH = path.join(RESOURCES_PATH, 'partials');
|
||||
|
||||
const Partials = {
|
||||
command: 'command.hbs',
|
||||
executeCommand: 'execute-command.hbs',
|
||||
} as const;
|
||||
|
||||
function registerPartials() {
|
||||
for (const [name, filename] of Object.entries(Partials)) {
|
||||
Handlebars.registerPartial(name, fs.readFileSync(path.join(PARTIALS_PATH, filename), 'utf8'));
|
||||
}
|
||||
}
|
||||
|
||||
registerPartials();
|
||||
|
||||
export enum Template {
|
||||
Commands = 'commands.hbs',
|
||||
}
|
||||
|
||||
export const compileTemplate = _.memoize((template: Template) => {
|
||||
const templatePath = path.join(TEMPLATE_PATH, template);
|
||||
return Handlebars.compile(fs.readFileSync(templatePath, 'utf8'));
|
||||
});
|
||||
|
||||
export function registerHelpers() {
|
||||
Handlebars.registerHelper('reflectionPath', function (this: PageEvent<ContainerReflection>) {
|
||||
if (this.model) {
|
||||
if (this.model.kind && this.model.kind !== ReflectionKind.Module) {
|
||||
if (this.model.kind === (AppiumPluginReflectionKind.COMMANDS as any)) {
|
||||
return `${this.model.name} Commands`;
|
||||
}
|
||||
const title: string[] = [];
|
||||
if (this.model.parent && this.model.parent.parent) {
|
||||
if (this.model.parent.parent.parent) {
|
||||
title.push(
|
||||
`[${this.model.parent.parent.name}](${Handlebars.helpers.relativeURL(
|
||||
this.model?.parent?.parent.url
|
||||
)})`
|
||||
);
|
||||
}
|
||||
title.push(
|
||||
`[${this.model.parent.name}](${Handlebars.helpers.relativeURL(this.model.parent.url)})`
|
||||
);
|
||||
}
|
||||
title.push(this.model.name);
|
||||
return title.length > 1 ? `${title.join('.')}` : null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import {Application, Context, Converter} from 'typedoc';
|
||||
import {convertCommands, createReflections} from './converter';
|
||||
import {AppiumPluginLogger} from './logger';
|
||||
import {getTheme, THEME_NAME} from './output';
|
||||
|
||||
/**
|
||||
* Loads the Appium TypeDoc plugin
|
||||
* @param app - TypeDoc Application
|
||||
*/
|
||||
export function load(app: Application) {
|
||||
const log = new AppiumPluginLogger(app.logger, 'appium');
|
||||
|
||||
// register our custom theme. the user still has to choose it
|
||||
app.renderer.defineTheme(THEME_NAME, getTheme(log));
|
||||
|
||||
app.converter.on(Converter.EVENT_RESOLVE_BEGIN, (ctx: Context) => {
|
||||
// we don't want to do this work if we're not using the custom theme!
|
||||
if (app.renderer.themeName === THEME_NAME) {
|
||||
// this queries the declarations created by TypeDoc and extracts command information
|
||||
const projectCommands = convertCommands(ctx, log);
|
||||
|
||||
// this creates new custom reflections from the data we gathered and registers them
|
||||
// with TypeDoc
|
||||
createReflections(ctx, log, projectCommands);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"name": "@appium/typedoc-plugin-appium",
|
||||
"version": "0.1.0",
|
||||
"description": "TypeDoc plugin for Appium & its extensions",
|
||||
"homepage": "https://appium.io/",
|
||||
"license": "Apache-2.0",
|
||||
"main": "index.js",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appium/appium.git",
|
||||
"directory": "packages/support"
|
||||
},
|
||||
"keywords": [
|
||||
"typedoc-plugin"
|
||||
],
|
||||
"author": "https://github.com/appium",
|
||||
"types": "./build/plugin.d.ts",
|
||||
"scripts": {
|
||||
"build": "exit 0",
|
||||
"dev": "npm run build -- --watch",
|
||||
"fix": "npm run lint -- --fix",
|
||||
"lint": "eslint -c ../../.eslintrc --ignore-path ../../.eslintignore .",
|
||||
"prepare": "npm run build",
|
||||
"test": "exit 0",
|
||||
"test:e2e": "exit 0",
|
||||
"test:smoke": "node ./index.js",
|
||||
"test:unit": "exit 0"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/appium/appium/issues"
|
||||
},
|
||||
"directories": {
|
||||
"lib": "lib",
|
||||
"test": "test"
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"lib",
|
||||
"build",
|
||||
"resources"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=8"
|
||||
},
|
||||
"typedoc": {
|
||||
"entryPoint": "./lib/plugin.ts"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typedoc": ">=0.23.14"
|
||||
},
|
||||
"dependencies": {
|
||||
"handlebars": "4.7.7",
|
||||
"type-fest": "3.2.0",
|
||||
"typedoc-plugin-markdown": "3.13.6"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{{#unless hasOwnDocument}}
|
||||
|
||||
### {{#ifNamedAnchors}} <a id="{{anchor}}" name="{{this.anchor}}"></a> {{/ifNamedAnchors}}`{{httpMethod}}` `{{name}}`
|
||||
|
||||
{{#if hasComment}}
|
||||
|
||||
{{> comment}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#if hasRequiredParams}}
|
||||
|
||||
#### Required Parameters
|
||||
|
||||
{{#each requiredParams}}
|
||||
|
||||
- `{{this}}`
|
||||
|
||||
{{/each}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#if hasOptionalParams}}
|
||||
|
||||
#### Optional Parameters
|
||||
|
||||
{{#each optionalParams}}
|
||||
- `{{this}}`
|
||||
{{/each}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{/unless}}
|
||||
@@ -0,0 +1,37 @@
|
||||
{{#unless hasOwnDocument}}
|
||||
|
||||
### {{#ifNamedAnchors}} <a id="{{anchor}}" name="{{this.anchor}}"></a> {{/ifNamedAnchors}}Script: `{{name}}`
|
||||
|
||||
{{#if hasComment}}
|
||||
|
||||
{{> comment}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
#### Route
|
||||
|
||||
`{{httpMethod}} {{route}}`
|
||||
|
||||
{{#if hasRequiredParams}}
|
||||
|
||||
#### Required Parameters
|
||||
|
||||
{{#each requiredParams}}
|
||||
|
||||
- `{{this}}`
|
||||
|
||||
{{/each}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#if hasOptionalParams}}
|
||||
|
||||
#### Optional Parameters
|
||||
|
||||
{{#each optionalParams}}
|
||||
- `{{this}}`
|
||||
{{/each}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{/unless}}
|
||||
@@ -0,0 +1,39 @@
|
||||
{{> header}}
|
||||
|
||||
{{#ifShowPageTitle}}
|
||||
|
||||
# {{{reflectionTitle true}}}
|
||||
|
||||
{{/ifShowPageTitle}}
|
||||
|
||||
{{#with model}}
|
||||
|
||||
{{#if hasComment}}
|
||||
|
||||
{{> comment}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{#with model}}
|
||||
|
||||
{{#if hasRoutes}}
|
||||
## Routes
|
||||
{{#each children}}
|
||||
{{#unless isExecuteCommand}}
|
||||
{{> command}}
|
||||
{{/unless}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
|
||||
{{#if hasExecuteCommands}}
|
||||
## Execute Scripts
|
||||
{{#each children}}
|
||||
{{#if isExecuteCommand}}
|
||||
{{> executeCommand}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
|
||||
{{/with}}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "../../config/tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "lib",
|
||||
"outDir": "build",
|
||||
"strict": true,
|
||||
"emitDeclarationOnly": false,
|
||||
"module": "CommonJS",
|
||||
"sourceMap": true,
|
||||
"jsx": "react",
|
||||
"jsxFactory": "JSX.createElement",
|
||||
"jsxFragmentFactory": "JSX.Fragment",
|
||||
"lib": ["ES2022"],
|
||||
"downlevelIteration": true
|
||||
},
|
||||
"include": ["lib/**/*.ts*"],
|
||||
"references": []
|
||||
}
|
||||
@@ -61,6 +61,9 @@
|
||||
},
|
||||
{
|
||||
"path": "packages/fake-driver"
|
||||
},
|
||||
{
|
||||
"path": "packages/typedoc-plugin-appium"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+3
-2
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"$schema": "https://typedoc.org/schema.json",
|
||||
"entryPoints": ["./packages/appium", "./packages/base-driver", "./packages/support", "./packages/types", "./packages/base-plugin"],
|
||||
"entryPoints": ["./packages/appium", "./packages/base-driver", "./packages/support", "./packages/types", "./packages/base-plugin", "./packages/fake-driver", "./packages/typedoc-plugin-appium"],
|
||||
"entryPointStrategy": "packages",
|
||||
"name": "Appium",
|
||||
"includeVersion": false,
|
||||
"tsconfig": "./tsconfig.json",
|
||||
"out": "typedoc-docs",
|
||||
"cleanOutputDir": true,
|
||||
"plugin": ["typedoc-plugin-resolve-crossmodule-references"]
|
||||
"plugin": ["typedoc-plugin-resolve-crossmodule-references", "typedoc-plugin-markdown", "./packages/typedoc-plugin-appium"],
|
||||
"theme": "appium"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user