mirror of
https://github.com/appium/appium.git
synced 2025-12-19 00:49:47 -06:00
fix(docutils): remove @appium/typedoc-plugin-appium and all other uses of typedoc (#19465)
This commit is contained in:
5
.github/labeler.yml
vendored
5
.github/labeler.yml
vendored
@@ -133,11 +133,6 @@ labels:
|
||||
matcher:
|
||||
files: ['packages/tsconfig/**']
|
||||
|
||||
- label: '@appium/typedoc-plugin-appium'
|
||||
sync: true
|
||||
matcher:
|
||||
files: ['packages/typedoc-plugin-appium/**']
|
||||
|
||||
- label: '@appium/types'
|
||||
sync: true
|
||||
matcher:
|
||||
|
||||
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@@ -5,7 +5,6 @@ on:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- '**/typedoc.json'
|
||||
- '**/tsconfig*.json'
|
||||
- '**/package*.json'
|
||||
- '**/*mkdocs*.ya?ml'
|
||||
@@ -21,7 +20,6 @@ on:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- '**/typedoc.json'
|
||||
- '**/tsconfig*.json'
|
||||
- '**/package*.json'
|
||||
- '**/*mkdocs*.ya?ml'
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -60,5 +60,3 @@ appium.zip
|
||||
packages/*/LICENSE
|
||||
local_appium_home
|
||||
*.tgz
|
||||
typedoc-docs
|
||||
packages/appium/docs/*/reference
|
||||
|
||||
16
.vscode/launch.json
vendored
16
.vscode/launch.json
vendored
@@ -44,22 +44,6 @@
|
||||
],
|
||||
"sourceMaps": true,
|
||||
"type": "node"
|
||||
},
|
||||
{
|
||||
"console": "integratedTerminal",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"name": "Typedoc",
|
||||
"program": "${workspaceFolder}/node_modules/.bin/typedoc",
|
||||
"args": ["--logLevel", "Verbose"],
|
||||
"request": "launch",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**",
|
||||
"**/node_modules/lodash/**",
|
||||
"**/node_modules/**/handlebars/**"
|
||||
],
|
||||
"sourceMaps": true,
|
||||
"type": "node"
|
||||
}
|
||||
],
|
||||
"version": "0.2.0"
|
||||
|
||||
8
.vscode/tasks.json
vendored
8
.vscode/tasks.json
vendored
@@ -14,14 +14,6 @@
|
||||
"label": "npm: dev",
|
||||
"detail": "npm run dev",
|
||||
"isBackground": true
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "typedoc",
|
||||
"problemMatcher": [],
|
||||
"label": "npm: typedoc",
|
||||
"detail": "typedoc --logLevel Verbose",
|
||||
"group": "build"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -26,10 +26,6 @@ module.exports = (wallaby) => {
|
||||
'./packages/*/package.json',
|
||||
'./packages/*/tsconfig*.json',
|
||||
'./packages/*/test/**/fixture?(s)/**/*',
|
||||
{
|
||||
instrument: false,
|
||||
pattern: './packages/typedoc-plugin-appium/resources/**/*',
|
||||
},
|
||||
{
|
||||
binary: true,
|
||||
pattern: './packages/support/test/unit/assets/sample_binary.plist',
|
||||
@@ -42,10 +38,6 @@ module.exports = (wallaby) => {
|
||||
instrument: false,
|
||||
pattern: './packages/base-driver/static/**/*',
|
||||
},
|
||||
{
|
||||
instrument: false,
|
||||
pattern: './packages/typedoc-plugin-appium/lib/theme/resources/**',
|
||||
},
|
||||
{
|
||||
instrument: false,
|
||||
pattern: './packages/*/test/**/__snapshots__/**/*',
|
||||
|
||||
226
package-lock.json
generated
226
package-lock.json
generated
@@ -46,7 +46,6 @@
|
||||
"fancy-log": "2.0.0",
|
||||
"finalhandler": "1.2.0",
|
||||
"get-port": "5.1.1",
|
||||
"handlebars": "4.7.8",
|
||||
"husky": "8.0.3",
|
||||
"json-schema-to-typescript": "13.1.1",
|
||||
"lerna": "7.4.2",
|
||||
@@ -68,9 +67,6 @@
|
||||
"through2": "4.0.2",
|
||||
"ts-node": "10.9.1",
|
||||
"tsd": "0.29.0",
|
||||
"typedoc": "0.23.28",
|
||||
"typedoc-plugin-markdown": "3.14.0",
|
||||
"typedoc-plugin-resolve-crossmodule-references": "0.3.3",
|
||||
"typescript": "5.0.4",
|
||||
"validate.js": "0.13.1",
|
||||
"webdriverio": "8.24.1",
|
||||
@@ -178,10 +174,6 @@
|
||||
"resolved": "packages/tsconfig",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@appium/typedoc-plugin-appium": {
|
||||
"resolved": "packages/typedoc-plugin-appium",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@appium/types": {
|
||||
"resolved": "packages/types",
|
||||
"link": true
|
||||
@@ -4785,10 +4777,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-sequence-parser": {
|
||||
"version": "1.1.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"license": "MIT",
|
||||
@@ -10113,6 +10101,7 @@
|
||||
"version": "4.7.8",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
|
||||
"integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.5",
|
||||
"neo-async": "^2.6.2",
|
||||
@@ -12217,6 +12206,7 @@
|
||||
},
|
||||
"node_modules/jsonc-parser": {
|
||||
"version": "3.2.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jsonfile": {
|
||||
@@ -13684,10 +13674,6 @@
|
||||
"es5-ext": "~0.10.2"
|
||||
}
|
||||
},
|
||||
"node_modules/lunr": {
|
||||
"version": "2.3.9",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
"version": "2.1.0",
|
||||
"dev": true,
|
||||
@@ -13765,16 +13751,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "4.2.12",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/marky": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz",
|
||||
@@ -14908,6 +14884,7 @@
|
||||
},
|
||||
"node_modules/neo-async": {
|
||||
"version": "2.6.2",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nested-error-stacks": {
|
||||
@@ -18697,16 +18674,6 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/shiki": {
|
||||
"version": "0.14.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-sequence-parser": "^1.1.0",
|
||||
"jsonc-parser": "^3.2.0",
|
||||
"vscode-oniguruma": "^1.7.0",
|
||||
"vscode-textmate": "^8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.0.4",
|
||||
"license": "MIT",
|
||||
@@ -20569,68 +20536,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/typedoc": {
|
||||
"version": "0.23.28",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"lunr": "^2.3.9",
|
||||
"marked": "^4.2.12",
|
||||
"minimatch": "^7.1.3",
|
||||
"shiki": "^0.14.1"
|
||||
},
|
||||
"bin": {
|
||||
"typedoc": "bin/typedoc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x"
|
||||
}
|
||||
},
|
||||
"node_modules/typedoc-plugin-markdown": {
|
||||
"version": "3.14.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"handlebars": "^4.7.7"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typedoc": ">=0.23.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typedoc-plugin-resolve-crossmodule-references": {
|
||||
"version": "0.3.3",
|
||||
"license": "Apache-2.0",
|
||||
"workspaces": [
|
||||
"test/packages/*"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typedoc": ">=0.22 <=0.23"
|
||||
}
|
||||
},
|
||||
"node_modules/typedoc/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typedoc/node_modules/minimatch": {
|
||||
"version": "7.4.2",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz",
|
||||
@@ -20664,6 +20569,7 @@
|
||||
},
|
||||
"node_modules/uglify-js": {
|
||||
"version": "3.17.4",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
@@ -20978,14 +20884,6 @@
|
||||
"node": ">=6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vscode-oniguruma": {
|
||||
"version": "1.7.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vscode-textmate": {
|
||||
"version": "8.0.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/wait-port": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/wait-port/-/wait-port-1.0.4.tgz",
|
||||
@@ -21934,6 +21832,7 @@
|
||||
},
|
||||
"node_modules/wordwrap": {
|
||||
"version": "1.0.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/workerpool": {
|
||||
@@ -22389,7 +22288,7 @@
|
||||
"dependencies": {
|
||||
"@appium/base-driver": "^9.4.3",
|
||||
"@appium/base-plugin": "^2.2.24",
|
||||
"@appium/docutils": "^0.4.13",
|
||||
"@appium/docutils": "^1.0.0",
|
||||
"@appium/schema": "^0.4.2",
|
||||
"@appium/support": "^4.1.10",
|
||||
"@appium/types": "^0.14.3",
|
||||
@@ -22637,19 +22536,17 @@
|
||||
},
|
||||
"packages/docutils": {
|
||||
"name": "@appium/docutils",
|
||||
"version": "0.4.13",
|
||||
"version": "1.0.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@appium/support": "^4.1.10",
|
||||
"@appium/tsconfig": "^0.x",
|
||||
"@appium/typedoc-plugin-appium": "^0.x",
|
||||
"@sliphua/lilconfig-ts-loader": "3.2.2",
|
||||
"@types/which": "3.0.1",
|
||||
"chalk": "4.1.2",
|
||||
"consola": "2.15.3",
|
||||
"diff": "5.1.0",
|
||||
"figures": "3.2.0",
|
||||
"find-up": "5.0.0",
|
||||
"json5": "2.2.3",
|
||||
"lilconfig": "2.1.0",
|
||||
"lodash": "4.17.21",
|
||||
@@ -22660,9 +22557,6 @@
|
||||
"source-map-support": "0.5.21",
|
||||
"teen_process": "2.0.101",
|
||||
"type-fest": "3.13.1",
|
||||
"typedoc": "0.23.28",
|
||||
"typedoc-plugin-markdown": "3.14.0",
|
||||
"typedoc-plugin-resolve-crossmodule-references": "0.3.3",
|
||||
"typescript": "5.0.4",
|
||||
"yaml": "2.3.4",
|
||||
"yargs": "17.7.2",
|
||||
@@ -23278,28 +23172,6 @@
|
||||
"npm": ">=8"
|
||||
}
|
||||
},
|
||||
"packages/typedoc-plugin-appium": {
|
||||
"name": "@appium/typedoc-plugin-appium",
|
||||
"version": "0.6.6",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"handlebars": "4.7.8",
|
||||
"lodash": "4.17.21",
|
||||
"pluralize": "8.0.0",
|
||||
"type-fest": "3.13.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
|
||||
"npm": ">=8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"appium": "^2.0.0-beta.48",
|
||||
"typedoc": "~0.23.14",
|
||||
"typedoc-plugin-markdown": "3.14.0",
|
||||
"typedoc-plugin-resolve-crossmodule-references": "~0.3.3",
|
||||
"typescript": "^4.7.0 || ^5.0.0"
|
||||
}
|
||||
},
|
||||
"packages/types": {
|
||||
"name": "@appium/types",
|
||||
"version": "0.14.3",
|
||||
@@ -23432,14 +23304,12 @@
|
||||
"requires": {
|
||||
"@appium/support": "^4.1.10",
|
||||
"@appium/tsconfig": "^0.x",
|
||||
"@appium/typedoc-plugin-appium": "^0.x",
|
||||
"@sliphua/lilconfig-ts-loader": "3.2.2",
|
||||
"@types/which": "3.0.1",
|
||||
"chalk": "4.1.2",
|
||||
"consola": "2.15.3",
|
||||
"diff": "5.1.0",
|
||||
"figures": "3.2.0",
|
||||
"find-up": "5.0.0",
|
||||
"json5": "2.2.3",
|
||||
"lilconfig": "2.1.0",
|
||||
"lodash": "4.17.21",
|
||||
@@ -23450,9 +23320,6 @@
|
||||
"source-map-support": "0.5.21",
|
||||
"teen_process": "2.0.101",
|
||||
"type-fest": "3.13.1",
|
||||
"typedoc": "0.23.28",
|
||||
"typedoc-plugin-markdown": "3.14.0",
|
||||
"typedoc-plugin-resolve-crossmodule-references": "0.3.3",
|
||||
"typescript": "5.0.4",
|
||||
"yaml": "2.3.4",
|
||||
"yargs": "17.7.2",
|
||||
@@ -23825,15 +23692,6 @@
|
||||
"@tsconfig/node14": "14.1.0"
|
||||
}
|
||||
},
|
||||
"@appium/typedoc-plugin-appium": {
|
||||
"version": "file:packages/typedoc-plugin-appium",
|
||||
"requires": {
|
||||
"handlebars": "4.7.8",
|
||||
"lodash": "4.17.21",
|
||||
"pluralize": "8.0.0",
|
||||
"type-fest": "3.13.1"
|
||||
}
|
||||
},
|
||||
"@appium/types": {
|
||||
"version": "file:packages/types",
|
||||
"requires": {
|
||||
@@ -27118,9 +26976,6 @@
|
||||
"ansi-regex": {
|
||||
"version": "5.0.1"
|
||||
},
|
||||
"ansi-sequence-parser": {
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"requires": {
|
||||
@@ -27143,7 +26998,7 @@
|
||||
"requires": {
|
||||
"@appium/base-driver": "^9.4.3",
|
||||
"@appium/base-plugin": "^2.2.24",
|
||||
"@appium/docutils": "^0.4.13",
|
||||
"@appium/docutils": "^1.0.0",
|
||||
"@appium/schema": "^0.4.2",
|
||||
"@appium/support": "^4.1.10",
|
||||
"@appium/types": "^0.14.3",
|
||||
@@ -30899,6 +30754,7 @@
|
||||
"version": "4.7.8",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
|
||||
"integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "^1.2.5",
|
||||
"neo-async": "^2.6.2",
|
||||
@@ -32257,7 +32113,8 @@
|
||||
"version": "2.2.3"
|
||||
},
|
||||
"jsonc-parser": {
|
||||
"version": "3.2.0"
|
||||
"version": "3.2.0",
|
||||
"dev": true
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.1.0",
|
||||
@@ -33310,9 +33167,6 @@
|
||||
"es5-ext": "~0.10.2"
|
||||
}
|
||||
},
|
||||
"lunr": {
|
||||
"version": "2.3.9"
|
||||
},
|
||||
"make-dir": {
|
||||
"version": "2.1.0",
|
||||
"dev": true,
|
||||
@@ -33367,9 +33221,6 @@
|
||||
"version": "4.3.0",
|
||||
"dev": true
|
||||
},
|
||||
"marked": {
|
||||
"version": "4.2.12"
|
||||
},
|
||||
"marky": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz",
|
||||
@@ -34128,7 +33979,8 @@
|
||||
"version": "0.6.3"
|
||||
},
|
||||
"neo-async": {
|
||||
"version": "2.6.2"
|
||||
"version": "2.6.2",
|
||||
"dev": true
|
||||
},
|
||||
"nested-error-stacks": {
|
||||
"version": "2.1.1",
|
||||
@@ -36720,15 +36572,6 @@
|
||||
"shell-quote": {
|
||||
"version": "1.8.1"
|
||||
},
|
||||
"shiki": {
|
||||
"version": "0.14.1",
|
||||
"requires": {
|
||||
"ansi-sequence-parser": "^1.1.0",
|
||||
"jsonc-parser": "^3.2.0",
|
||||
"vscode-oniguruma": "^1.7.0",
|
||||
"vscode-textmate": "^8.0.0"
|
||||
}
|
||||
},
|
||||
"side-channel": {
|
||||
"version": "1.0.4",
|
||||
"requires": {
|
||||
@@ -38013,39 +37856,6 @@
|
||||
"version": "0.0.6",
|
||||
"dev": true
|
||||
},
|
||||
"typedoc": {
|
||||
"version": "0.23.28",
|
||||
"requires": {
|
||||
"lunr": "^2.3.9",
|
||||
"marked": "^4.2.12",
|
||||
"minimatch": "^7.1.3",
|
||||
"shiki": "^0.14.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "7.4.2",
|
||||
"requires": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"typedoc-plugin-markdown": {
|
||||
"version": "3.14.0",
|
||||
"requires": {
|
||||
"handlebars": "^4.7.7"
|
||||
}
|
||||
},
|
||||
"typedoc-plugin-resolve-crossmodule-references": {
|
||||
"version": "0.3.3",
|
||||
"requires": {}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz",
|
||||
@@ -38058,6 +37868,7 @@
|
||||
},
|
||||
"uglify-js": {
|
||||
"version": "3.17.4",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"unbox-primitive": {
|
||||
@@ -38279,12 +38090,6 @@
|
||||
"acorn-walk": "^8.2.0"
|
||||
}
|
||||
},
|
||||
"vscode-oniguruma": {
|
||||
"version": "1.7.0"
|
||||
},
|
||||
"vscode-textmate": {
|
||||
"version": "8.0.0"
|
||||
},
|
||||
"wait-port": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/wait-port/-/wait-port-1.0.4.tgz",
|
||||
@@ -38975,7 +38780,8 @@
|
||||
}
|
||||
},
|
||||
"wordwrap": {
|
||||
"version": "1.0.0"
|
||||
"version": "1.0.0",
|
||||
"dev": true
|
||||
},
|
||||
"workerpool": {
|
||||
"version": "6.2.1"
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
"sync-pkgs": "run-p sync-pkgs:*",
|
||||
"sync-pkgs:appium-readme": "sync-monorepo-packages --force --no-package-json --packages=packages/appium README.md",
|
||||
"sync-pkgs:common-fields": "sync-monorepo-packages --fields=author,license,bugs,homepage,engines",
|
||||
"sync-pkgs:keywords": "sync-monorepo-packages --field=keywords --packages=\"packages/*\" --packages=\"!packages/eslint-config-appium\" --packages=\"!packages/types\" --packages=\"!packages/typedoc-plugin-appium\" --packages=\"!packages/eslint-config-appium-ts\" ",
|
||||
"sync-pkgs:keywords": "sync-monorepo-packages --field=keywords --packages=\"packages/*\" --packages=\"!packages/eslint-config-appium\" --packages=\"!packages/types\" --packages=\"!packages/eslint-config-appium-ts\" ",
|
||||
"sync-pkgs:license": "sync-monorepo-packages --force --no-package-json LICENSE",
|
||||
"test": "run-s test:quick",
|
||||
"test:ci": "run-s test:smoke test:unit test:types test:e2e",
|
||||
@@ -73,7 +73,6 @@
|
||||
"test:smoke": "smoker --all test:smoke",
|
||||
"test:types": "lerna run test:types",
|
||||
"test:unit": "lerna run test",
|
||||
"typedoc": "typedoc --logLevel Verbose",
|
||||
"upload": "gulp github-upload",
|
||||
"zip": "zip -qr ./appium.zip ./packages/appium",
|
||||
"zip-and-upload": "run-s zip upload"
|
||||
@@ -118,7 +117,6 @@
|
||||
"fancy-log": "2.0.0",
|
||||
"finalhandler": "1.2.0",
|
||||
"get-port": "5.1.1",
|
||||
"handlebars": "4.7.8",
|
||||
"husky": "8.0.3",
|
||||
"json-schema-to-typescript": "13.1.1",
|
||||
"lerna": "7.4.2",
|
||||
@@ -140,9 +138,6 @@
|
||||
"through2": "4.0.2",
|
||||
"ts-node": "10.9.1",
|
||||
"tsd": "0.29.0",
|
||||
"typedoc": "0.23.28",
|
||||
"typedoc-plugin-markdown": "3.14.0",
|
||||
"typedoc-plugin-resolve-crossmodule-references": "0.3.3",
|
||||
"typescript": "5.0.4",
|
||||
"validate.js": "0.13.1",
|
||||
"webdriverio": "8.24.1",
|
||||
|
||||
@@ -5,23 +5,9 @@ title: Building Docs for Appium Extensions
|
||||
Once you've [built a driver](./build-drivers.md) or [built a plugin](./build-plugins.md) for Appium,
|
||||
you will hopefully want to document how that extension works for your users. The most basic way of
|
||||
doing this is to write up a quick `README.md` and keep it in the root of your project's repository.
|
||||
However, this can involve a lot of duplication of effort, especially when documenting things like
|
||||
Appium commands.
|
||||
However, this can involve a lot of effort.
|
||||
|
||||
Let's say your driver implements ~25 of the standard WebDriver protocol commands. You could write
|
||||
up a description of these commands, how they map to the protocol, what parameters they take, and
|
||||
what behaviour will result on your particular platform. But this information is already more or
|
||||
less stored in your code, as the command implementation (and any docstrings or comments). Having
|
||||
this information in two places creates an opportunity for the docs to get out of sync with the
|
||||
reality of the code. Wouldn't it be nice to generate command reference documentation straight from
|
||||
the code?
|
||||
|
||||
Another problem with the basic single file `README.md` approach is that many extensions might want a
|
||||
whole set of documents including longer prose guides (like this one). It might be nice to have code
|
||||
examples where you can toggle between different programming languages. It might be nice to be able
|
||||
to add a project-specific logo. And so on.
|
||||
|
||||
The Appium project has built tools to do all these things, and we've packaged up these tools so our
|
||||
The Appium project has built tools to help with this, and we've packaged up these tools so our
|
||||
ecosystem developers building drivers and plugins can _also_ use them. The best way to get going
|
||||
with these tools is probably to look at an existing Appium driver repo to see how it's done, for
|
||||
example the [XCUITest driver repo](https://github.com/appium/appium-xcuitest-driver). But this guide
|
||||
@@ -35,22 +21,12 @@ for our purposes. You can adjust this, but by default Appium's utilities also as
|
||||
using the [mkdocs-material](https://squidfunk.github.io/mkdocs-material/) theme/extension for
|
||||
MkDocs.
|
||||
|
||||
From here, building a basic docs site is as easy as collecting your Markdown files together and
|
||||
creating a sort of manifest file defining how you want them to be organized.
|
||||
|
||||
The other main piece is automatic documentation generation from your code files. Appium maintains
|
||||
a plugin for [TypeDoc](https://typedoc.org/). This plugin is incorporated into our doc utility.
|
||||
When you give it an entrypoint for you driver or plugin, it will scan and parse all your code files
|
||||
looking for Appium command implementations. A set of Markdown reference files will be generated for these
|
||||
commands, which will then be included in your docs site.
|
||||
|
||||
> Note: Implementing an extension in TypeScript is _not_ a requirement for generating documentation,
|
||||
> but for automated doc generation to work, you will need to apply TypeScript-supported JSDoc-style
|
||||
> docstrings to your JS codebase. See ["JS Projects Utilizing TypeScript"](https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html) for more information.
|
||||
|
||||
In order to make different versions of your docs available (one for each minor release of your
|
||||
extension, typically), we also bundle [Mike](https://github.com/jimporter/mike).
|
||||
|
||||
From here, building a basic docs site is as easy as collecting your Markdown files together and
|
||||
defining how you want them to be organized.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
To take advantage of Appium's documentation utilities, you'll need to install:
|
||||
@@ -68,156 +44,25 @@ To take advantage of Appium's documentation utilities, you'll need to install:
|
||||
To prepare your extension for generating documentation, run the following command:
|
||||
|
||||
```bash
|
||||
npx appium-docs init --entry-point <my-entry-point.js>
|
||||
npx appium-docs init
|
||||
```
|
||||
|
||||
...where `<my-entry-point.js>` is the **source entry point** to your extension. If you _are not_ transpiling your code via TypeScript, Babel, etc., this is typically the same as the value of the `main` property in `package.json`. If you _are_ transpiling, this is typically different. For example, your `main` property may be `dist/index.js`, but your **source entry point** is `src/index.ts`.
|
||||
|
||||
This will:
|
||||
|
||||
1. Create a `tsconfig.json` if one does not already exist. This is necessary even if your extension is not written in TypeScript.
|
||||
2. Create a `typedoc.json` with the necessary configuration for TypeDoc.
|
||||
3. Create a `mkdocs.yml` with the necessary configuration for MkDocs.
|
||||
4. Modify your `package.json` to add a `typedoc.entryPoint` property with a value of your entry point (as specified above).
|
||||
1. Create a `tsconfig.json` if one does not already exist. This is necessary even if your extension
|
||||
is not written in TypeScript.
|
||||
2. Create a `mkdocs.yml` with the necessary configuration for MkDocs.
|
||||
|
||||
### Documenting Your Extension
|
||||
|
||||
At this point, you can begin documenting your extension. You don't need to do this all at once, but you should make the following changes, at minimum.
|
||||
At this point, you can begin documenting your extension. By default, MkDocs will look for Markdown
|
||||
files in the `docs` directory. You can therefore create your Markdown documentation files, place
|
||||
them in `docs`, and add links to these files in `mkdocs.yml`.
|
||||
|
||||
#### `newMethodMap` and `executeMethodMap`
|
||||
Refer to the [MkDocs documentation](https://www.mkdocs.org/user-guide/writing-your-docs/) for
|
||||
information on how to organize and structure your documentation.
|
||||
|
||||
The static properties `newMethodMap` and `executeMethodMap` may be present on your extension's main class. If they are not, then you can skip to the next section. If they are, you will need to make the following changes, depending on your extension's language.
|
||||
|
||||
##### JavaScript
|
||||
|
||||
```js
|
||||
// note: this is equivalent to the TypeScript example below
|
||||
|
||||
class MyExtension {
|
||||
static newMethodMap = /** @type {const} */({
|
||||
// ...
|
||||
});
|
||||
|
||||
static executeMethodMap = /** @type {const} */({
|
||||
// ...
|
||||
});
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
##### TypeScript
|
||||
|
||||
```ts
|
||||
class MyExtension {
|
||||
static newMethodMap = {
|
||||
// ...
|
||||
} as const;
|
||||
|
||||
static executeMethodMap = {
|
||||
// ...
|
||||
} as const;
|
||||
}
|
||||
```
|
||||
|
||||
#### Driver Constraints
|
||||
|
||||
> Note: Plugin authors can skip this section.
|
||||
|
||||
Your driver may have a property `desiredCapConstraints`. It should also follow the same pattern as `newMethodMap` and `executeMethodMap` above. For example:
|
||||
|
||||
```js
|
||||
class MyExtension {
|
||||
desiredCapConstraints = /** @type {const} */({
|
||||
myCapability: {
|
||||
presence: true,
|
||||
isString: true
|
||||
},
|
||||
myOtherCap: {
|
||||
isBoolean: true
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
(For extensions written in TypeScript, use `as const` as before.)
|
||||
|
||||
#### Commands
|
||||
|
||||
The documentation for a command, as defined in your extension, comes from multiple places. These sources are then combined as needed into the final output.
|
||||
|
||||
In Appium, new commands are defined in `newMethodMap` and execute methods are defined in `executeMethodMap`. **The value of these properties are used to build your documentation.** In particular, parameter names and optional/required status _override_ whatever method implementation does. So for example, if your `newMethodMap` contains:
|
||||
|
||||
```js
|
||||
class MyExtension {
|
||||
static newMethodMap = /** @type {const} */({
|
||||
'/session/:sessionId/myThing': {
|
||||
/**
|
||||
* Does my thing
|
||||
*/
|
||||
GET: {command: 'doMyThing', payloadParams: {required: ['a', 'b']}},
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
...and your `doMyThing` method implementation looks like this:
|
||||
|
||||
```js
|
||||
class MyExtension {
|
||||
/**
|
||||
* Doesn't do my thing
|
||||
* @param {any} a - Whatever
|
||||
* @param {number} d - Some number
|
||||
* @param {boolean} c - Some boolean
|
||||
* @returns {Promise<boolean>} Some other boolean
|
||||
*/
|
||||
async doMyThing(a, d = 1, c = false) {
|
||||
// ...
|
||||
return true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The documentation will show that the `doMyThing` method accepts required parameters `a` and `b`. Even though `b` is named `d` in the implementation--and it's optional--it will be ignored. Likewise, since the definition in `newMethodMap` knows nothing about `c`, it too is ignored.
|
||||
|
||||
In addition, the _description_ from the docstring in `newMethodMap` overrides the description in the method implementation; it will describe the command `doMyThing` as "Does my thing".
|
||||
|
||||
The `@param` tags and `@returns` tag from the method's docstring provide information about the expected and returned types, as well as a description of each. This is not expressible via `newMethodMap` and `executeMethodMap` alone; it provides more information for your extension's users.
|
||||
|
||||
> Note for TypeScript users: while the _types_ will already be present, providing `@param` and `@returns` tags is still useful for providing descriptions.
|
||||
|
||||
All commands must be `async`, so they will return `Promise<T>` where type `T` is the type of whatever the `Promise` fulfills with. In the generated documentation, the `Promise` is ignored, and only `T` is reported. So for `doMyThing`, the return type of the command, as output in the documentation, will be `boolean`.
|
||||
|
||||
This is because while extensions must be written in JavaScript, we're documenting an API which can be called from any language; that language likely won't have a concept of a `Promise`. Likewise, `undefined` or `void` types will be output as `null` (since that is a concept that translates well to multiple languages).
|
||||
|
||||
#### Optional: `README.md`
|
||||
|
||||
If you have a `README.md`, it will be pulled in to the generated docs site automatically. This behavior can be disabled by adding the following to `typedoc.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"readme": "none"
|
||||
}
|
||||
```
|
||||
|
||||
#### Next Steps
|
||||
|
||||
Appium provides type definitions for extension authors; these are available via Appium itself and the [`@appium/types`](https://npm.im/@appium/types) package.
|
||||
|
||||
Of note, drivers should both extend `BaseDriver` _and_ implement the `ExternalDriver` interface exported by `@appium/types`. This will help ensure that your driver's implementation is correct and usable by different clients.
|
||||
|
||||
You're encouraged to look at the official test/example extensions, [`@appium/fake-driver`](https://npm.im/@appium/fake-driver) and [`@appium/fake-plugin`](https://npm.im/@appium/fake-plugin) for canonical examples of how to use these types.
|
||||
|
||||
#### Resources
|
||||
|
||||
How to type more complex return values or parameters is beyond the scope of this document. For more information, see:
|
||||
|
||||
- For extensions written in JS, the [TypeScript documentation](https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html)
|
||||
- For tags in addition to what TS natively recognizes, see the [TypeDoc documentation](https://typedoc.org/guides/doccomments/)
|
||||
|
||||
Likewise, refer to the [MkDocs documentation](https://www.mkdocs.org/user-guide/writing-your-docs/) for further information on how to customize your MkDocs output.
|
||||
|
||||
### Usage
|
||||
### Building the Docs
|
||||
|
||||
At this point, you can use the `appium-docs` CLI tool. Run this tool with no arguments to get the
|
||||
full help output and see all the available subcommands and parameters. Here are a few usage
|
||||
|
||||
517
packages/appium/docs/en/reference/commands/base-driver.md
Normal file
517
packages/appium/docs/en/reference/commands/base-driver.md
Normal file
@@ -0,0 +1,517 @@
|
||||
# Driver: base-driver
|
||||
|
||||
### `createSession`
|
||||
|
||||
`POST` **`/session`**
|
||||
|
||||
Historically the first two arguments were reserved for JSONWP capabilities.
|
||||
Appium 2 has dropped the support of these, so now we only accept capability
|
||||
objects in W3C format and thus allow any of the three arguments to represent
|
||||
the latter.
|
||||
|
||||
**`See`**
|
||||
|
||||
[https://w3c.github.io/webdriver/#new-session](https://w3c.github.io/webdriver/#new-session)
|
||||
|
||||
<!-- comment source: multiple -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `desiredCapabilities?` | `W3CDriverCaps`<`C`\> | the new session capabilities |
|
||||
| `requiredCapabilities?` | `W3CDriverCaps`<`C`\> | another place the new session capabilities could be sent (typically left undefined) |
|
||||
| `capabilities?` | `W3CDriverCaps`<`C`\> | another place the new session capabilities could be sent (typically left undefined) |
|
||||
|
||||
#### Response
|
||||
|
||||
`CreateResult`
|
||||
|
||||
The capabilities object representing the created session
|
||||
|
||||
### `deleteSession`
|
||||
|
||||
`DELETE` **`/session/:sessionId`**
|
||||
|
||||
Returns capabilities for the session and event history (if applicable)
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Response
|
||||
|
||||
`SingularSessionData`<`C`, `SessionData`\>
|
||||
|
||||
A session data object
|
||||
|
||||
### `getSession`
|
||||
|
||||
`GET` **`/session/:sessionId`**
|
||||
|
||||
Returns capabilities for the session and event history (if applicable)
|
||||
|
||||
<!-- comment source: multiple -->
|
||||
|
||||
#### Response
|
||||
|
||||
`SingularSessionData`<`C`, `SessionData`\>
|
||||
|
||||
A session data object
|
||||
|
||||
### `findElement`
|
||||
|
||||
`POST` **`/session/:sessionId/element`**
|
||||
|
||||
Find a UI element given a locator strategy and a selector, erroring if it can't be found
|
||||
|
||||
**`See`**
|
||||
|
||||
[https://w3c.github.io/webdriver/#find-element](https://w3c.github.io/webdriver/#find-element)
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `using` | `string` | the locator strategy |
|
||||
| `value` | `string` | the selector to combine with the strategy to find the specific element |
|
||||
|
||||
#### Response
|
||||
|
||||
`Element`<`string`\>
|
||||
|
||||
The element object encoding the element id which can be used in element-related
|
||||
commands
|
||||
|
||||
### `findElementFromElement`
|
||||
|
||||
`POST` **`/session/:sessionId/element/:elementId/element`**
|
||||
|
||||
Find a UI element given a locator strategy and a selector, erroring if it can't be found. Only
|
||||
look for elements among the set of descendants of a given element
|
||||
|
||||
**`See`**
|
||||
|
||||
[https://w3c.github.io/webdriver/#find-element-from-element](https://w3c.github.io/webdriver/#find-element-from-element)
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `using` | `string` | the locator strategy |
|
||||
| `value` | `string` | the selector to combine with the strategy to find the specific element |
|
||||
|
||||
#### Response
|
||||
|
||||
`Element`<`string`\>
|
||||
|
||||
The element object encoding the element id which can be used in element-related
|
||||
commands
|
||||
|
||||
### `findElementFromShadowRoot`
|
||||
|
||||
`POST` **`/session/:sessionId/shadow/:shadowId/element`**
|
||||
|
||||
Find an element from a shadow root
|
||||
|
||||
**`See`**
|
||||
|
||||
[https://w3c.github.io/webdriver/#find-element-from-shadow-root](https://w3c.github.io/webdriver/#find-element-from-shadow-root)
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `using` | `string` | the locator strategy |
|
||||
| `value` | `string` | the selector to combine with the strategy to find the specific elements |
|
||||
|
||||
#### Response
|
||||
|
||||
`Element`<`string`\>
|
||||
|
||||
The element inside the shadow root matching the selector
|
||||
|
||||
### `findElements`
|
||||
|
||||
`POST` **`/session/:sessionId/elements`**
|
||||
|
||||
Find a a list of all UI elements matching a given a locator strategy and a selector
|
||||
|
||||
**`See`**
|
||||
|
||||
[https://w3c.github.io/webdriver/#find-elements](https://w3c.github.io/webdriver/#find-elements)
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `using` | `string` | the locator strategy |
|
||||
| `value` | `string` | the selector to combine with the strategy to find the specific elements |
|
||||
|
||||
#### Response
|
||||
|
||||
`Element`<`string`\>[]
|
||||
|
||||
A possibly-empty list of element objects
|
||||
|
||||
### `findElementsFromElement`
|
||||
|
||||
`POST` **`/session/:sessionId/element/:elementId/elements`**
|
||||
|
||||
Find a a list of all UI elements matching a given a locator strategy and a selector. Only
|
||||
look for elements among the set of descendants of a given element
|
||||
|
||||
**`See`**
|
||||
|
||||
[https://w3c.github.io/webdriver/#find-elements-from-element](https://w3c.github.io/webdriver/#find-elements-from-element)
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `using` | `string` | the locator strategy |
|
||||
| `value` | `string` | the selector to combine with the strategy to find the specific elements |
|
||||
|
||||
#### Response
|
||||
|
||||
`Element`<`string`\>[]
|
||||
|
||||
A possibly-empty list of element objects
|
||||
|
||||
### `findElementsFromShadowRoot`
|
||||
|
||||
`POST` **`/session/:sessionId/shadow/:shadowId/elements`**
|
||||
|
||||
Find elements from a shadow root
|
||||
|
||||
**`See`**
|
||||
|
||||
[https://w3c.github.io/webdriver/#find-element-from-shadow-root](https://w3c.github.io/webdriver/#find-element-from-shadow-root)
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `using` | `string` | the locator strategy |
|
||||
| `value` | `string` | the selector to combine with the strategy to find the specific elements |
|
||||
|
||||
#### Response
|
||||
|
||||
`Element`<`string`\>[]
|
||||
|
||||
A possibly empty list of elements inside the shadow root matching the selector
|
||||
|
||||
### `getLog`
|
||||
|
||||
`POST` **`/session/:sessionId/log`**
|
||||
|
||||
Get the log for a given log type.
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `type` | `string` | Name/key of log type as defined in ILogCommands.supportedLogTypes. |
|
||||
|
||||
#### Response
|
||||
|
||||
`any`
|
||||
|
||||
### `getLog`
|
||||
|
||||
`POST` **`/session/:sessionId/se/log`**
|
||||
|
||||
Get the log for a given log type.
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `type` | `string` | Name/key of log type as defined in ILogCommands.supportedLogTypes. |
|
||||
|
||||
#### Response
|
||||
|
||||
`any`
|
||||
|
||||
### `getLogEvents`
|
||||
|
||||
`POST` **`/session/:sessionId/appium/events`**
|
||||
|
||||
Get a list of events that have occurred in the current session
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `type?` | `string` \| `string`[] | filter the returned events by including one or more types |
|
||||
|
||||
#### Response
|
||||
|
||||
`EventHistory` \| `Record`<`string`, `number`\>
|
||||
|
||||
The event history for the session
|
||||
|
||||
### `getLogTypes`
|
||||
|
||||
`GET` **`/session/:sessionId/log/types`**
|
||||
|
||||
Get available log types as a list of strings
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Response
|
||||
|
||||
`string`[]
|
||||
|
||||
### `getLogTypes`
|
||||
|
||||
`GET` **`/session/:sessionId/se/log/types`**
|
||||
|
||||
Get available log types as a list of strings
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Response
|
||||
|
||||
`string`[]
|
||||
|
||||
### `getPageSource`
|
||||
|
||||
`GET` **`/session/:sessionId/source`**
|
||||
|
||||
Get the current page/app source as HTML/XML
|
||||
|
||||
**`See`**
|
||||
|
||||
[https://w3c.github.io/webdriver/#get-page-source](https://w3c.github.io/webdriver/#get-page-source)
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Response
|
||||
|
||||
`string`
|
||||
|
||||
The UI hierarchy in a platform-appropriate format (e.g., HTML for a web page)
|
||||
|
||||
### `getSessions`
|
||||
|
||||
`GET` **`/sessions`**
|
||||
|
||||
Get data for all sessions running on an Appium server
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Response
|
||||
|
||||
`MultiSessionData`<`C`\>[]
|
||||
|
||||
A list of session data objects
|
||||
|
||||
### `getSettings`
|
||||
|
||||
`GET` **`/session/:sessionId/appium/settings`**
|
||||
|
||||
Update the session's settings dictionary with a new settings object
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Response
|
||||
|
||||
``null``
|
||||
|
||||
### `updateSettings`
|
||||
|
||||
`POST` **`/session/:sessionId/appium/settings`**
|
||||
|
||||
Update the session's settings dictionary with a new settings object
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `settings` | `Settings` | A key-value map of setting names to values. Settings not named in the map will not have their value adjusted. |
|
||||
|
||||
#### Response
|
||||
|
||||
``null``
|
||||
|
||||
### `getStatus`
|
||||
|
||||
`GET` **`/status`**
|
||||
|
||||
**`Summary`**
|
||||
|
||||
Retrieve the server's current status.
|
||||
|
||||
**`Description`**
|
||||
|
||||
Returns information about whether a remote end is in a state in which it can create new sessions and can additionally include arbitrary meta information that is specific to the implementation.
|
||||
|
||||
The readiness state is represented by the ready property of the body, which is false if an attempt to create a session at the current time would fail. However, the value true does not guarantee that a New Session command will succeed.
|
||||
|
||||
Implementations may optionally include additional meta information as part of the body, but the top-level properties ready and message are reserved and must not be overwritten.
|
||||
|
||||
<!-- comment source: builtin-interface -->
|
||||
|
||||
#### Examples
|
||||
|
||||
<!-- BEGIN:EXAMPLES -->
|
||||
##### JavaScript
|
||||
<!-- BEGIN:EXAMPLE lang=JavaScript -->
|
||||
|
||||
```js
|
||||
// webdriver.io example
|
||||
await driver.status();
|
||||
```
|
||||
|
||||
<!-- END:EXAMPLE -->
|
||||
##### Python
|
||||
<!-- BEGIN:EXAMPLE lang=Python -->
|
||||
|
||||
```python
|
||||
driver.get_status()
|
||||
```
|
||||
|
||||
<!-- END:EXAMPLE -->
|
||||
##### Java
|
||||
<!-- BEGIN:EXAMPLE lang=Java -->
|
||||
|
||||
```java
|
||||
driver.getStatus();
|
||||
```
|
||||
|
||||
<!-- END:EXAMPLE -->
|
||||
##### Ruby
|
||||
<!-- BEGIN:EXAMPLE lang=Ruby -->
|
||||
|
||||
```ruby
|
||||
# ruby_lib example
|
||||
remote_status
|
||||
|
||||
# ruby_lib_core example
|
||||
@driver.remote_status
|
||||
```
|
||||
|
||||
<!-- END:EXAMPLE -->
|
||||
<!-- END:EXAMPLES -->
|
||||
|
||||
#### Response
|
||||
|
||||
`Object`
|
||||
|
||||
### `getTimeouts`
|
||||
|
||||
`GET` **`/session/:sessionId/timeouts`**
|
||||
|
||||
Set the various timeouts associated with a session
|
||||
|
||||
**`See`**
|
||||
|
||||
[https://w3c.github.io/webdriver/#set-timeouts](https://w3c.github.io/webdriver/#set-timeouts)
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Response
|
||||
|
||||
``null``
|
||||
|
||||
### `timeouts`
|
||||
|
||||
`POST` **`/session/:sessionId/timeouts`**
|
||||
|
||||
Set the various timeouts associated with a session
|
||||
|
||||
**`See`**
|
||||
|
||||
[https://w3c.github.io/webdriver/#set-timeouts](https://w3c.github.io/webdriver/#set-timeouts)
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `type?` | `string` | used only for the old (JSONWP) command, the type of the timeout |
|
||||
| `ms?` | `string` \| `number` | used only for the old (JSONWP) command, the ms for the timeout |
|
||||
| `script?` | `number` | the number in ms for the script timeout, used for the W3C command |
|
||||
| `pageLoad?` | `number` | the number in ms for the pageLoad timeout, used for the W3C command |
|
||||
| `implicit?` | `string` \| `number` | the number in ms for the implicit wait timeout, used for the W3C command |
|
||||
|
||||
#### Response
|
||||
|
||||
``null``
|
||||
|
||||
### `implicitWait`
|
||||
|
||||
`POST` **`/session/:sessionId/timeouts/implicit_wait`**
|
||||
|
||||
Set the implicit wait timeout
|
||||
|
||||
**`Deprecated`**
|
||||
|
||||
Use `timeouts` instead
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `ms` | `string` \| `number` | the timeout in ms |
|
||||
|
||||
#### Response
|
||||
|
||||
``null``
|
||||
|
||||
### `logCustomEvent`
|
||||
|
||||
`POST` **`/session/:sessionId/appium/log_event`**
|
||||
|
||||
Add a custom-named event to the Appium event log
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `vendor` | `string` | the name of the vendor or tool the event belongs to, to namespace the event |
|
||||
| `event` | `string` | the name of the event itself |
|
||||
|
||||
#### Response
|
||||
|
||||
``null``
|
||||
|
||||
### `reset`
|
||||
|
||||
`POST` **`/session/:sessionId/appium/app/reset`**
|
||||
|
||||
Reset the current session (run the delete session and create session subroutines)
|
||||
|
||||
**`Deprecated`**
|
||||
|
||||
Use explicit session management commands instead
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Response
|
||||
|
||||
``null``
|
||||
@@ -0,0 +1,22 @@
|
||||
# Plugin: execute-driver
|
||||
|
||||
### `executeDriverScript`
|
||||
|
||||
`POST` **`/session/:sessionId/appium/execute_driver`**
|
||||
|
||||
Implementation of a command within a plugin
|
||||
|
||||
At minimum, `D` must be `ExternalDriver`, but a plugin can be more narrow about which drivers it supports.
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `script` | [script: string, scriptType: string, timeoutMs: number] |
|
||||
| `type?` | [script: string, scriptType: string, timeoutMs: number] |
|
||||
|
||||
#### Response
|
||||
|
||||
`unknown`
|
||||
650
packages/appium/docs/en/reference/commands/fake-driver.md
Normal file
650
packages/appium/docs/en/reference/commands/fake-driver.md
Normal file
@@ -0,0 +1,650 @@
|
||||
# Driver: fake-driver
|
||||
|
||||
## Commands
|
||||
|
||||
### `callDeprecatedCommand`
|
||||
|
||||
`POST` **`/session/:sessionId/deprecated`**
|
||||
|
||||
This is a command that exists just to be an example of a deprecated command
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Response
|
||||
|
||||
``null``
|
||||
|
||||
### `doubleClick`
|
||||
|
||||
`POST` **`/session/:sessionId/doubleclick`**
|
||||
|
||||
Double-click the current mouse location
|
||||
|
||||
**`Deprecated`**
|
||||
|
||||
Use the Actions API instead
|
||||
|
||||
<!-- comment source: builtin-interface -->
|
||||
|
||||
#### Response
|
||||
|
||||
``null``
|
||||
|
||||
### `getFakeThing`
|
||||
|
||||
`GET` **`/session/:sessionId/fakedriver`**
|
||||
|
||||
#### Response
|
||||
|
||||
``null`` \| `Thing`
|
||||
|
||||
### `setFakeThing`
|
||||
|
||||
`POST` **`/session/:sessionId/fakedriver`**
|
||||
|
||||
Set the 'thing' value (so that it can be retrieved later)
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `thing` | `Thing` |
|
||||
|
||||
#### Response
|
||||
|
||||
``null``
|
||||
|
||||
### `getFakeDriverArgs`
|
||||
|
||||
`GET` **`/session/:sessionId/fakedriverargs`**
|
||||
|
||||
Get the driver args that were sent in via the CLI
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Response
|
||||
|
||||
`StringRecord` & `ServerArgs`
|
||||
|
||||
### `createSession`
|
||||
|
||||
`POST` **`/session`**
|
||||
|
||||
Comment for `createSession` in `FakeDriver`
|
||||
|
||||
**`See`**
|
||||
|
||||
[https://w3c.github.io/webdriver/#new-session](https://w3c.github.io/webdriver/#new-session)
|
||||
|
||||
<!-- comment source: multiple -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `desiredCapabilities?` | `W3CFakeDriverCaps` | W3C Capabilities |
|
||||
| `requiredCapabilities?` | `W3CFakeDriverCaps` | W3C Capabilities |
|
||||
| `capabilities?` | `W3CFakeDriverCaps` | W3C Capabilities |
|
||||
|
||||
#### Response
|
||||
|
||||
[`string`, `FakeDriverCaps`]
|
||||
|
||||
The capabilities object representing the created session
|
||||
|
||||
### `deleteSession`
|
||||
|
||||
`DELETE` **`/session/:sessionId`**
|
||||
|
||||
Returns capabilities for the session and event history (if applicable)
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Response
|
||||
|
||||
`SingularSessionData`<{ `app`: { `isString`: ``true`` = true; `presence`: ``true`` = true } ; `uniqueApp`: { `isBoolean`: ``true`` = true } }, `StringRecord`\>
|
||||
|
||||
A session data object
|
||||
|
||||
### `getSession`
|
||||
|
||||
`GET` **`/session/:sessionId`**
|
||||
|
||||
Returns capabilities for the session and event history (if applicable)
|
||||
|
||||
<!-- comment source: multiple -->
|
||||
|
||||
#### Response
|
||||
|
||||
`SingularSessionData`<{ `app`: { `isString`: ``true`` = true; `presence`: ``true`` = true } ; `uniqueApp`: { `isBoolean`: ``true`` = true } }, `StringRecord`\>
|
||||
|
||||
A session data object
|
||||
|
||||
### `findElement`
|
||||
|
||||
`POST` **`/session/:sessionId/element`**
|
||||
|
||||
Find a UI element given a locator strategy and a selector, erroring if it can't be found
|
||||
|
||||
**`See`**
|
||||
|
||||
[https://w3c.github.io/webdriver/#find-element](https://w3c.github.io/webdriver/#find-element)
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `using` | `string` | the locator strategy |
|
||||
| `value` | `string` | the selector to combine with the strategy to find the specific element |
|
||||
|
||||
#### Response
|
||||
|
||||
`Element`<`string`\>
|
||||
|
||||
The element object encoding the element id which can be used in element-related
|
||||
commands
|
||||
|
||||
### `findElementFromElement`
|
||||
|
||||
`POST` **`/session/:sessionId/element/:elementId/element`**
|
||||
|
||||
Find a UI element given a locator strategy and a selector, erroring if it can't be found. Only
|
||||
look for elements among the set of descendants of a given element
|
||||
|
||||
**`See`**
|
||||
|
||||
[https://w3c.github.io/webdriver/#find-element-from-element](https://w3c.github.io/webdriver/#find-element-from-element)
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `using` | `string` | the locator strategy |
|
||||
| `value` | `string` | the selector to combine with the strategy to find the specific element |
|
||||
|
||||
#### Response
|
||||
|
||||
`Element`<`string`\>
|
||||
|
||||
The element object encoding the element id which can be used in element-related
|
||||
commands
|
||||
|
||||
### `findElementFromShadowRoot`
|
||||
|
||||
`POST` **`/session/:sessionId/shadow/:shadowId/element`**
|
||||
|
||||
Find an element from a shadow root
|
||||
|
||||
**`See`**
|
||||
|
||||
[https://w3c.github.io/webdriver/#find-element-from-shadow-root](https://w3c.github.io/webdriver/#find-element-from-shadow-root)
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `using` | `string` | the locator strategy |
|
||||
| `value` | `string` | the selector to combine with the strategy to find the specific elements |
|
||||
|
||||
#### Response
|
||||
|
||||
`Element`<`string`\>
|
||||
|
||||
The element inside the shadow root matching the selector
|
||||
|
||||
### `findElements`
|
||||
|
||||
`POST` **`/session/:sessionId/elements`**
|
||||
|
||||
Find a a list of all UI elements matching a given a locator strategy and a selector
|
||||
|
||||
**`See`**
|
||||
|
||||
[https://w3c.github.io/webdriver/#find-elements](https://w3c.github.io/webdriver/#find-elements)
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `using` | `string` | the locator strategy |
|
||||
| `value` | `string` | the selector to combine with the strategy to find the specific elements |
|
||||
|
||||
#### Response
|
||||
|
||||
`Element`<`string`\>[]
|
||||
|
||||
A possibly-empty list of element objects
|
||||
|
||||
### `findElementsFromElement`
|
||||
|
||||
`POST` **`/session/:sessionId/element/:elementId/elements`**
|
||||
|
||||
Find a a list of all UI elements matching a given a locator strategy and a selector. Only
|
||||
look for elements among the set of descendants of a given element
|
||||
|
||||
**`See`**
|
||||
|
||||
[https://w3c.github.io/webdriver/#find-elements-from-element](https://w3c.github.io/webdriver/#find-elements-from-element)
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `using` | `string` | the locator strategy |
|
||||
| `value` | `string` | the selector to combine with the strategy to find the specific elements |
|
||||
|
||||
#### Response
|
||||
|
||||
`Element`<`string`\>[]
|
||||
|
||||
A possibly-empty list of element objects
|
||||
|
||||
### `findElementsFromShadowRoot`
|
||||
|
||||
`POST` **`/session/:sessionId/shadow/:shadowId/elements`**
|
||||
|
||||
Find elements from a shadow root
|
||||
|
||||
**`See`**
|
||||
|
||||
[https://w3c.github.io/webdriver/#find-element-from-shadow-root](https://w3c.github.io/webdriver/#find-element-from-shadow-root)
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `using` | `string` | the locator strategy |
|
||||
| `value` | `string` | the selector to combine with the strategy to find the specific elements |
|
||||
|
||||
#### Response
|
||||
|
||||
`Element`<`string`\>[]
|
||||
|
||||
A possibly empty list of elements inside the shadow root matching the selector
|
||||
|
||||
### `getLog`
|
||||
|
||||
`POST` **`/session/:sessionId/log`**
|
||||
|
||||
Get the log for a given log type.
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `type` | `string` | Name/key of log type as defined in ILogCommands.supportedLogTypes. |
|
||||
|
||||
#### Response
|
||||
|
||||
`any`
|
||||
|
||||
### `getLog`
|
||||
|
||||
`POST` **`/session/:sessionId/se/log`**
|
||||
|
||||
Get the log for a given log type.
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `type` | `string` | Name/key of log type as defined in ILogCommands.supportedLogTypes. |
|
||||
|
||||
#### Response
|
||||
|
||||
`any`
|
||||
|
||||
### `getLogEvents`
|
||||
|
||||
`POST` **`/session/:sessionId/appium/events`**
|
||||
|
||||
Get a list of events that have occurred in the current session
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `type?` | `string` \| `string`[] | filter the returned events by including one or more types |
|
||||
|
||||
#### Response
|
||||
|
||||
`EventHistory` \| `Record`<`string`, `number`\>
|
||||
|
||||
The event history for the session
|
||||
|
||||
### `getLogTypes`
|
||||
|
||||
`GET` **`/session/:sessionId/log/types`**
|
||||
|
||||
Get available log types as a list of strings
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Response
|
||||
|
||||
`string`[]
|
||||
|
||||
### `getLogTypes`
|
||||
|
||||
`GET` **`/session/:sessionId/se/log/types`**
|
||||
|
||||
Get available log types as a list of strings
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Response
|
||||
|
||||
`string`[]
|
||||
|
||||
### `getPageSource`
|
||||
|
||||
`GET` **`/session/:sessionId/source`**
|
||||
|
||||
Get the current page/app source as HTML/XML
|
||||
|
||||
**`See`**
|
||||
|
||||
[https://w3c.github.io/webdriver/#get-page-source](https://w3c.github.io/webdriver/#get-page-source)
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Response
|
||||
|
||||
`string`
|
||||
|
||||
The UI hierarchy in a platform-appropriate format (e.g., HTML for a web page)
|
||||
|
||||
### `getSessions`
|
||||
|
||||
`GET` **`/sessions`**
|
||||
|
||||
Get data for all sessions running on an Appium server
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Response
|
||||
|
||||
`MultiSessionData`<{ `app`: { `isString`: ``true`` = true; `presence`: ``true`` = true } ; `uniqueApp`: { `isBoolean`: ``true`` = true } }\>[]
|
||||
|
||||
A list of session data objects
|
||||
|
||||
### `getSettings`
|
||||
|
||||
`GET` **`/session/:sessionId/appium/settings`**
|
||||
|
||||
Update the session's settings dictionary with a new settings object
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Response
|
||||
|
||||
``null``
|
||||
|
||||
### `updateSettings`
|
||||
|
||||
`POST` **`/session/:sessionId/appium/settings`**
|
||||
|
||||
Update the session's settings dictionary with a new settings object
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `settings` | `StringRecord` | A key-value map of setting names to values. Settings not named in the map will not have their value adjusted. |
|
||||
|
||||
#### Response
|
||||
|
||||
``null``
|
||||
|
||||
### `getStatus`
|
||||
|
||||
`GET` **`/status`**
|
||||
|
||||
**`Summary`**
|
||||
|
||||
Retrieve the server's current status.
|
||||
|
||||
**`Description`**
|
||||
|
||||
Returns information about whether a remote end is in a state in which it can create new sessions and can additionally include arbitrary meta information that is specific to the implementation.
|
||||
|
||||
The readiness state is represented by the ready property of the body, which is false if an attempt to create a session at the current time would fail. However, the value true does not guarantee that a New Session command will succeed.
|
||||
|
||||
Implementations may optionally include additional meta information as part of the body, but the top-level properties ready and message are reserved and must not be overwritten.
|
||||
|
||||
<!-- comment source: builtin-interface -->
|
||||
|
||||
#### Examples
|
||||
|
||||
<!-- BEGIN:EXAMPLES -->
|
||||
##### JavaScript
|
||||
<!-- BEGIN:EXAMPLE lang=JavaScript -->
|
||||
|
||||
```js
|
||||
// webdriver.io example
|
||||
await driver.status();
|
||||
```
|
||||
|
||||
<!-- END:EXAMPLE -->
|
||||
##### Python
|
||||
<!-- BEGIN:EXAMPLE lang=Python -->
|
||||
|
||||
```python
|
||||
driver.get_status()
|
||||
```
|
||||
|
||||
<!-- END:EXAMPLE -->
|
||||
##### Java
|
||||
<!-- BEGIN:EXAMPLE lang=Java -->
|
||||
|
||||
```java
|
||||
driver.getStatus();
|
||||
```
|
||||
|
||||
<!-- END:EXAMPLE -->
|
||||
##### Ruby
|
||||
<!-- BEGIN:EXAMPLE lang=Ruby -->
|
||||
|
||||
```ruby
|
||||
# ruby_lib example
|
||||
remote_status
|
||||
|
||||
# ruby_lib_core example
|
||||
@driver.remote_status
|
||||
```
|
||||
|
||||
<!-- END:EXAMPLE -->
|
||||
<!-- END:EXAMPLES -->
|
||||
|
||||
#### Response
|
||||
|
||||
`Object`
|
||||
|
||||
### `getTimeouts`
|
||||
|
||||
`GET` **`/session/:sessionId/timeouts`**
|
||||
|
||||
Set the various timeouts associated with a session
|
||||
|
||||
**`See`**
|
||||
|
||||
[https://w3c.github.io/webdriver/#set-timeouts](https://w3c.github.io/webdriver/#set-timeouts)
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Response
|
||||
|
||||
``null``
|
||||
|
||||
### `timeouts`
|
||||
|
||||
`POST` **`/session/:sessionId/timeouts`**
|
||||
|
||||
Set the various timeouts associated with a session
|
||||
|
||||
**`See`**
|
||||
|
||||
[https://w3c.github.io/webdriver/#set-timeouts](https://w3c.github.io/webdriver/#set-timeouts)
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `type?` | `string` | used only for the old (JSONWP) command, the type of the timeout |
|
||||
| `ms?` | `string` \| `number` | used only for the old (JSONWP) command, the ms for the timeout |
|
||||
| `script?` | `number` | the number in ms for the script timeout, used for the W3C command |
|
||||
| `pageLoad?` | `number` | the number in ms for the pageLoad timeout, used for the W3C command |
|
||||
| `implicit?` | `string` \| `number` | the number in ms for the implicit wait timeout, used for the W3C command |
|
||||
|
||||
#### Response
|
||||
|
||||
``null``
|
||||
|
||||
### `implicitWait`
|
||||
|
||||
`POST` **`/session/:sessionId/timeouts/implicit_wait`**
|
||||
|
||||
Set the implicit wait timeout
|
||||
|
||||
**`Deprecated`**
|
||||
|
||||
Use `timeouts` instead
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `ms` | `string` \| `number` | the timeout in ms |
|
||||
|
||||
#### Response
|
||||
|
||||
``null``
|
||||
|
||||
### `logCustomEvent`
|
||||
|
||||
`POST` **`/session/:sessionId/appium/log_event`**
|
||||
|
||||
Add a custom-named event to the Appium event log
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `vendor` | `string` | the name of the vendor or tool the event belongs to, to namespace the event |
|
||||
| `event` | `string` | the name of the event itself |
|
||||
|
||||
#### Response
|
||||
|
||||
``null``
|
||||
|
||||
### `reset`
|
||||
|
||||
`POST` **`/session/:sessionId/appium/app/reset`**
|
||||
|
||||
Reset the current session (run the delete session and create session subroutines)
|
||||
|
||||
**`Deprecated`**
|
||||
|
||||
Use explicit session management commands instead
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Response
|
||||
|
||||
``null``
|
||||
|
||||
## Execute Methods
|
||||
|
||||
### `fake: addition`
|
||||
|
||||
#### Route
|
||||
|
||||
`POST /session/:sessionId/execute`
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `num1` | `number` |
|
||||
| `num2` | `number` |
|
||||
| `num3?` | `number` |
|
||||
|
||||
#### Response
|
||||
|
||||
`number`
|
||||
|
||||
### `fake: getDeprecatedCommandsCalled`
|
||||
|
||||
This is a command that will return a list of deprecated command names called
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Route
|
||||
|
||||
`POST /session/:sessionId/execute`
|
||||
|
||||
#### Response
|
||||
|
||||
`string`[]
|
||||
|
||||
### `fake: getThing`
|
||||
|
||||
Gets a thing (a fake thing)
|
||||
|
||||
<!-- comment source: other-comment -->
|
||||
|
||||
#### Route
|
||||
|
||||
`POST /session/:sessionId/execute`
|
||||
|
||||
#### Response
|
||||
|
||||
``null`` \| `Thing`
|
||||
|
||||
### `fake: setThing`
|
||||
|
||||
Set the 'thing' value (so that it can be retrieved later)
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Route
|
||||
|
||||
`POST /session/:sessionId/execute`
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `thing` | `Thing` |
|
||||
|
||||
#### Response
|
||||
|
||||
``null``
|
||||
67
packages/appium/docs/en/reference/commands/images-plugin.md
Normal file
67
packages/appium/docs/en/reference/commands/images-plugin.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# Plugin: images
|
||||
|
||||
### `compareImages`
|
||||
|
||||
`POST` **`/session/:sessionId/appium/compare_images`**
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------ | :------ |
|
||||
| `mode` | `any`[] |
|
||||
| `options?` | `any`[] |
|
||||
|
||||
#### Response
|
||||
|
||||
`ComparisonResult`
|
||||
|
||||
### `findElement`
|
||||
|
||||
`POST` **`/session/:sessionId/element`**
|
||||
|
||||
Find a UI element given a locator strategy and a selector, erroring if it can't be found
|
||||
|
||||
**`See`**
|
||||
|
||||
[https://w3c.github.io/webdriver/#find-element](https://w3c.github.io/webdriver/#find-element)
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `using` | `any` | the locator strategy |
|
||||
| `value` | `any` | the selector to combine with the strategy to find the specific element |
|
||||
|
||||
#### Response
|
||||
|
||||
`any`
|
||||
|
||||
The element object encoding the element id which can be used in element-related
|
||||
commands
|
||||
|
||||
### `findElements`
|
||||
|
||||
`POST` **`/session/:sessionId/elements`**
|
||||
|
||||
Find a a list of all UI elements matching a given a locator strategy and a selector
|
||||
|
||||
**`See`**
|
||||
|
||||
[https://w3c.github.io/webdriver/#find-elements](https://w3c.github.io/webdriver/#find-elements)
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `using` | `any` | the locator strategy |
|
||||
| `value` | `any` | the selector to combine with the strategy to find the specific elements |
|
||||
|
||||
#### Response
|
||||
|
||||
`any`
|
||||
|
||||
A possibly-empty list of element objects
|
||||
@@ -0,0 +1,27 @@
|
||||
# Plugin: relaxed-caps
|
||||
|
||||
### `createSession`
|
||||
|
||||
`POST` **`/session`**
|
||||
|
||||
Start a new automation session
|
||||
|
||||
**`See`**
|
||||
|
||||
[https://w3c.github.io/webdriver/#new-session](https://w3c.github.io/webdriver/#new-session)
|
||||
|
||||
<!-- comment source: multiple -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `desiredCapabilities?` | `any` | the new session capabilities |
|
||||
| `requiredCapabilities?` | `any` | another place the new session capabilities could be sent (typically left undefined) |
|
||||
| `capabilities?` | `any` | another place the new session capabilities could be sent (typically left undefined) |
|
||||
|
||||
#### Response
|
||||
|
||||
`any`
|
||||
|
||||
The capabilities object representing the created session
|
||||
@@ -0,0 +1,68 @@
|
||||
# Plugin: universal-xml
|
||||
|
||||
### `findElement`
|
||||
|
||||
`POST` **`/session/:sessionId/element`**
|
||||
|
||||
Find a UI element given a locator strategy and a selector, erroring if it can't be found
|
||||
|
||||
**`See`**
|
||||
|
||||
[https://w3c.github.io/webdriver/#find-element](https://w3c.github.io/webdriver/#find-element)
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `using` | `any`[] | the locator strategy |
|
||||
|
||||
#### Response
|
||||
|
||||
`any`
|
||||
|
||||
The element object encoding the element id which can be used in element-related
|
||||
commands
|
||||
|
||||
### `findElements`
|
||||
|
||||
`POST` **`/session/:sessionId/elements`**
|
||||
|
||||
Find a a list of all UI elements matching a given a locator strategy and a selector
|
||||
|
||||
**`See`**
|
||||
|
||||
[https://w3c.github.io/webdriver/#find-elements](https://w3c.github.io/webdriver/#find-elements)
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :------ | :------ | :------ |
|
||||
| `using` | `any`[] | the locator strategy |
|
||||
|
||||
#### Response
|
||||
|
||||
`any`
|
||||
|
||||
A possibly-empty list of element objects
|
||||
|
||||
### `getPageSource`
|
||||
|
||||
`GET` **`/session/:sessionId/source`**
|
||||
|
||||
Get the current page/app source as HTML/XML
|
||||
|
||||
**`See`**
|
||||
|
||||
[https://w3c.github.io/webdriver/#get-page-source](https://w3c.github.io/webdriver/#get-page-source)
|
||||
|
||||
<!-- comment source: method-signature -->
|
||||
|
||||
#### Response
|
||||
|
||||
`string`
|
||||
|
||||
The UI hierarchy in a platform-appropriate format (e.g., HTML for a web page)
|
||||
@@ -44,9 +44,6 @@ nav:
|
||||
- guides/log-filters.md
|
||||
- guides/grid.md
|
||||
- guides/caching.md
|
||||
- Developer Reference:
|
||||
- BaseDriver Commands: reference/commands/base-driver.md
|
||||
- FakeDriver Commands: reference/commands/fake-driver.md
|
||||
- Ecosystem:
|
||||
- ecosystem/index.md
|
||||
- Developing Appium Extensions:
|
||||
@@ -58,10 +55,10 @@ nav:
|
||||
- contributing/config-system.md
|
||||
- More Appium Resources: resources.md
|
||||
- Issues: https://github.com/appium/appium/issues
|
||||
- Reference:
|
||||
- Command Reference:
|
||||
- reference/commands/base-driver.md
|
||||
- reference/commands/execute-driver-plugin.md
|
||||
- reference/commands/fake-driver.md
|
||||
- reference/commands/execute-driver-plugin.md
|
||||
- reference/commands/images-plugin.md
|
||||
- reference/commands/relaxed-caps-plugin.md
|
||||
- reference/commands/universal-xml-plugin.md
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
/* eslint-disable promise/prefer-await-to-callbacks */
|
||||
/* eslint-disable promise/prefer-await-to-then */
|
||||
|
||||
const {buildReferenceDocs, deploy, updateNav, buildSite} = require('@appium/docutils');
|
||||
const {deploy, buildSite} = require('@appium/docutils');
|
||||
const {
|
||||
log,
|
||||
LANGS,
|
||||
@@ -36,11 +36,6 @@ async function main() {
|
||||
log.info(`Building Appium docs and committing to ${DOCS_BRANCH}`);
|
||||
|
||||
await copyAssets();
|
||||
await buildReferenceDocs();
|
||||
|
||||
for (const lang of LANGS) {
|
||||
await updateNav({mkdocsYml: path.join(DOCS_DIR, `mkdocs-${lang}.yml`)});
|
||||
}
|
||||
|
||||
const semVersion = semver.parse(version);
|
||||
if (!semVersion) {
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
"dependencies": {
|
||||
"@appium/base-driver": "^9.4.3",
|
||||
"@appium/base-plugin": "^2.2.24",
|
||||
"@appium/docutils": "^0.4.13",
|
||||
"@appium/docutils": "^1.0.0",
|
||||
"@appium/schema": "^0.4.2",
|
||||
"@appium/support": "^4.1.10",
|
||||
"@appium/types": "^0.14.3",
|
||||
@@ -103,8 +103,5 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "3fa73a1d1a95e3868fbceb005f8b1deb4ac53eaf",
|
||||
"typedoc": {
|
||||
"entryPoint": "./lib/main.js"
|
||||
}
|
||||
"gitHead": "3fa73a1d1a95e3868fbceb005f8b1deb4ac53eaf"
|
||||
}
|
||||
|
||||
@@ -82,9 +82,6 @@
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "8480a85ce2fa466360e0fb1a7f66628331907f02",
|
||||
"typedoc": {
|
||||
"entryPoint": "./lib/index.js"
|
||||
},
|
||||
"tsd": {
|
||||
"directory": "test/types"
|
||||
}
|
||||
|
||||
@@ -53,8 +53,5 @@
|
||||
"gitHead": "8480a85ce2fa466360e0fb1a7f66628331907f02",
|
||||
"tags": [
|
||||
"appium"
|
||||
],
|
||||
"typedoc": {
|
||||
"entryPoint": "./lib/plugin.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,5 +5,3 @@
|
||||
|
||||
export * from './deploy';
|
||||
export * from './site';
|
||||
export * from './reference';
|
||||
export * from './nav';
|
||||
|
||||
@@ -1,402 +0,0 @@
|
||||
/**
|
||||
* Handles updating/adding the `nav` property of `mkdocs.yml`, based on the output of `typedoc`;
|
||||
* specifically, the command documentation generated by `@appium/typedoc-plugin-appium`.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
import {fs} from '@appium/support';
|
||||
import _ from 'lodash';
|
||||
import path from 'node:path';
|
||||
import {
|
||||
DEFAULT_NAV_HEADER,
|
||||
DEFAULT_REL_TYPEDOC_OUT_PATH,
|
||||
NAME_BIN,
|
||||
NAME_MKDOCS_YML,
|
||||
NAME_TYPEDOC_JSON,
|
||||
} from '../constants';
|
||||
import {DocutilsError} from '../error';
|
||||
import {
|
||||
findDirsIn,
|
||||
findMkDocsYml,
|
||||
findTypeDocJsonPath,
|
||||
readTypedocJson,
|
||||
readYaml,
|
||||
safeWriteFile,
|
||||
stringifyYaml,
|
||||
} from '../fs';
|
||||
import {getLogger} from '../logger';
|
||||
import {MkDocsYml, MkDocsYmlNav} from '../model';
|
||||
import {relative} from '../util';
|
||||
|
||||
const log = getLogger('builder:nav');
|
||||
|
||||
/**
|
||||
* Gets a list of `.md` files relative to `docs_dir`
|
||||
* @param targetDir Directory ostensibly containing Markdown files; must be absolute
|
||||
* @param mkDocsDocsDir The path to the `docs_dir` in the `mkdocs.yml` file; must be absolute
|
||||
* @returns List of Markdown files relative to the `docs_dir` in the `mkdocs.yml` file
|
||||
*/
|
||||
async function findRelativeMarkdownFiles(
|
||||
targetDir: string,
|
||||
mkDocsDocsDir: string
|
||||
): Promise<string[]> {
|
||||
if (!path.isAbsolute(targetDir)) {
|
||||
throw new DocutilsError(`Expected absolute path, got '${targetDir}'`);
|
||||
}
|
||||
if (!path.isAbsolute(mkDocsDocsDir)) {
|
||||
throw new DocutilsError(`Expected absolute path, got '${mkDocsDocsDir}'`);
|
||||
}
|
||||
const relDir = path.relative(mkDocsDocsDir, targetDir);
|
||||
const dirEnts = await fs.readdir(targetDir, {withFileTypes: true});
|
||||
return dirEnts
|
||||
.filter((ent) => ent.isFile() && ent.name.endsWith('.md'))
|
||||
.map((ent) => path.join(relDir, ent.name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Because the `nav` property of `mkdocs.yml` is both a recursive type and a kind of awful one, it's
|
||||
* easier to work with it if we rewrite the data into a flat array of objects. We keep a `keypath`
|
||||
* prop which represents the deep/nested location within the `nav` object.
|
||||
*
|
||||
* @privateRemarks This function is not recursive; instead it loops over a queue of items to process
|
||||
* data, and we append to that queue while processing if needed.
|
||||
* @param nav Contents of the `nav` prop of `mkdocs.yml`
|
||||
* @returns A list of objects, each with a `keypath` property and a `fileOrUrl` property (and maybe
|
||||
* a `name` property)
|
||||
*/
|
||||
export function parseNav(nav: MkDocsYmlNav): ParsedNavData[] {
|
||||
let parsedNav: ParsedNavData[] = [];
|
||||
const entries = Object.entries(nav);
|
||||
type QueueItem = {
|
||||
entries: typeof entries;
|
||||
keypath: string;
|
||||
};
|
||||
const queue: QueueItem[] = [{entries, keypath: ''}];
|
||||
|
||||
while (queue.length) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const {entries, keypath} = queue.shift()!;
|
||||
for (const [key, item] of entries) {
|
||||
if (_.isString(item)) {
|
||||
const navData: ParsedNavData = {
|
||||
keypath: keypath ? `${keypath}.${key}` : key,
|
||||
fileOrUrl: item,
|
||||
};
|
||||
|
||||
// if the key is not convertible to a number, it's a name
|
||||
// which was manually put there by somebody.
|
||||
if (Number.isNaN(Number(key))) {
|
||||
navData.name = key;
|
||||
}
|
||||
parsedNav = [...parsedNav, navData];
|
||||
} else if (_.isObject(item)) {
|
||||
const subEntries = Object.entries(item);
|
||||
queue.push({entries: subEntries, keypath: keypath ? `${keypath}.${key}` : key});
|
||||
}
|
||||
}
|
||||
}
|
||||
return parsedNav;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all items within the list of parsed nav data which correpsond to the header.
|
||||
*
|
||||
* This is imperfect, as it's possible for the header string to appear in multiple places in the
|
||||
* nav, but let's just ignore that until we can't.
|
||||
* @param navData Some parsed nav data
|
||||
* @param header Header name
|
||||
*/
|
||||
function filterHeaderItems(navData: ParsedNavData[], header: string) {
|
||||
return _.filter(navData, (item) => _.toPath(item.keypath).includes(header));
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a root header keypath (like a prefix), a numeric offset, and some parsed nav data, compute
|
||||
* a keypath
|
||||
*
|
||||
* @param rootHeaderKeypath Root keypath as determined by {@linkcode getRootHeaderKeypath}
|
||||
* @param offset Numeric offset within the array of items for the header
|
||||
* @param data Any parsed nav data found. For new items, this will be `undefined`. If not new, it
|
||||
* may or may not have a `name` prop, and if it does, we want to retain it.
|
||||
* @returns Complete keypath for a nav item beginning with the root keypath
|
||||
*/
|
||||
function getKeypathForHeaderItem(rootHeaderKeypath: string, offset: number, data?: ParsedNavData) {
|
||||
return data?.name
|
||||
? `${rootHeaderKeypath}.${offset}.${data.name}`
|
||||
: `${rootHeaderKeypath}.${offset}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two sets of nav data and determines if they are different.
|
||||
*
|
||||
* This does not compare the entire `nav` object with a new one; it works per-header
|
||||
* @param newNavData Nav data as computed by {@linkcode getNavItemsForDir} corresponding to a
|
||||
* particular header
|
||||
* @param navData Subset of original nav data as parsed by {@linkcode parseNav}, corresponding to
|
||||
* the same header
|
||||
*/
|
||||
function navDataDidChange(
|
||||
newNavData: Array<Omit<ParsedNavData, 'name'>>,
|
||||
navData: ParsedNavData[]
|
||||
): boolean {
|
||||
// the result should be the items from newNavData which either don't appear in
|
||||
// navdata, or the items which do appear in navData but have a different fileOrUrl
|
||||
const matchedKeypaths = _.intersectionBy(newNavData, navData, 'keypath');
|
||||
const diff = _.xorBy(newNavData, matchedKeypaths, 'fileOrUrl');
|
||||
return !_.isEmpty(diff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the "root" keypath for a particular header
|
||||
*
|
||||
* @param headerItems Some subset of the nav data having keypaths corresponding to `header`
|
||||
* @param header Header string
|
||||
* @returns The keypath up to the header string, inclusive
|
||||
*/
|
||||
function getRootHeaderKeypath(headerItems: ParsedNavData[], header: string) {
|
||||
// these are the parts of the keypath of the first item, which will contain the header string. it
|
||||
// dosn't matter whether we pick the first one or any one; they will all contain the same root
|
||||
// keypath by definition.
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const rootHeaderKeypathParts = _.toPath(_.first(headerItems)!.keypath);
|
||||
|
||||
// this is the keypath up to the header string, inclusive.
|
||||
// we append indices or names to this keypath
|
||||
return rootHeaderKeypathParts.slice(0, rootHeaderKeypathParts.indexOf(header) + 1).join('.');
|
||||
}
|
||||
|
||||
/**
|
||||
* This examines the `navData` looking for items matching the header string (which _may_ be defined
|
||||
* by `dir`; if `all` is true, we just use the default header, because we will be returning a whole
|
||||
* lot of headers).
|
||||
*
|
||||
* @param dir Abs path to directory containing markdown files generated by TypeDoc
|
||||
* @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
|
||||
*/
|
||||
async function getNavItemsForDir(
|
||||
dir: string,
|
||||
mkDocsDocsDir: string,
|
||||
navData: ParsedNavData[],
|
||||
nav: MkDocsYmlNav,
|
||||
all = false
|
||||
) {
|
||||
let dataChanged = false;
|
||||
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 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 (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(navHeaderItems, navHeader);
|
||||
|
||||
for (const fileOrUrl of referenceOutputFilepaths) {
|
||||
const offset = navHeaderItems.findIndex((item) => item.fileOrUrl === fileOrUrl);
|
||||
const newOffset = offset >= 0 ? offset : navHeaderItems.length;
|
||||
const data = navHeaderItems[offset];
|
||||
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(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', navHeader);
|
||||
}
|
||||
} else {
|
||||
log.debug('No items found in header %s', navHeader);
|
||||
const navOffset = nav.length;
|
||||
for (const [idx, newRefFilepath] of referenceOutputFilepaths.entries()) {
|
||||
newNavHeaderItems.push({
|
||||
keypath: `${navOffset}.${navHeader}.${idx}`,
|
||||
fileOrUrl: newRefFilepath,
|
||||
});
|
||||
}
|
||||
log.debug('Will create nav data for header %s', navHeader);
|
||||
dataChanged = true;
|
||||
}
|
||||
return {data: newNavHeaderItems, changed: dataChanged};
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the changes in `newData` to the `mkDocsYml` object.
|
||||
*
|
||||
* **This function mutates `mkDocsYml`.**
|
||||
* @param newData New nav data
|
||||
* @param mkDocsYml Original `mkdocs.yml`
|
||||
*/
|
||||
function applyNavData(newData: ParsedNavData[], mkDocsYml: MkDocsYml) {
|
||||
for (const {keypath, fileOrUrl} of newData) {
|
||||
_.set(mkDocsYml, `nav.${keypath}`, fileOrUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the `nav` property of `mkdocs.yml` with a list of "command" files generated by TypeDoc via
|
||||
* `@appium/typedoc-plugin-appium`.
|
||||
*
|
||||
* To be clear, this function **modifies the MkDocs config file (`mkdocs.yml`) in place**; it is
|
||||
* typically under version control, so if this function makes any changes, you'll want to commit them.
|
||||
* @param opts - Options
|
||||
* @todo implement `dryRun` option
|
||||
*/
|
||||
export async function updateNav({
|
||||
cwd = process.cwd(),
|
||||
mkdocsYml: mkDocsYmlPath,
|
||||
typedocJson: typeDocJsonPath,
|
||||
all = false,
|
||||
}: UpdateNavOpts = {}) {
|
||||
// we need `mkdocs.yml` to update
|
||||
// and we need `typedoc.json` to know where to look for the command docs
|
||||
[mkDocsYmlPath, typeDocJsonPath] = await Promise.all([
|
||||
mkDocsYmlPath ?? findMkDocsYml(cwd),
|
||||
typeDocJsonPath ?? findTypeDocJsonPath(cwd),
|
||||
]);
|
||||
if (!mkDocsYmlPath) {
|
||||
throw new DocutilsError(
|
||||
`Could not find ${NAME_MKDOCS_YML} from ${cwd}; run "${NAME_BIN} init" to create it`
|
||||
);
|
||||
}
|
||||
if (!typeDocJsonPath) {
|
||||
throw new DocutilsError(
|
||||
`Could not find ${NAME_TYPEDOC_JSON} from ${cwd}; run "${NAME_BIN} init" to create it`
|
||||
);
|
||||
}
|
||||
const relativePath = relative(cwd);
|
||||
const relMkDocsYmlPath = relativePath(mkDocsYmlPath);
|
||||
const typeDocJson = readTypedocJson(typeDocJsonPath);
|
||||
const mkDocsYml = (await readYaml(mkDocsYmlPath)) as MkDocsYml;
|
||||
|
||||
/**
|
||||
* Absolute path to `typedoc.json`
|
||||
*/
|
||||
const absTypeDocJsonPath = path.isAbsolute(typeDocJsonPath)
|
||||
? typeDocJsonPath
|
||||
: path.resolve(cwd, typeDocJsonPath);
|
||||
|
||||
/**
|
||||
* Absolute path to TypeDoc's output directory (`out`)
|
||||
*/
|
||||
const typeDocOutDir = path.resolve(
|
||||
path.dirname(absTypeDocJsonPath),
|
||||
typeDocJson.out ? typeDocJson.out : DEFAULT_REL_TYPEDOC_OUT_PATH
|
||||
);
|
||||
|
||||
/**
|
||||
* Absolute path to `mkdocs.yml`
|
||||
*/
|
||||
const absMkdocsYmlPath = path.isAbsolute(mkDocsYmlPath)
|
||||
? mkDocsYmlPath
|
||||
: path.resolve(cwd, mkDocsYmlPath);
|
||||
|
||||
const {docs_dir: docsDir, nav = []} = mkDocsYml;
|
||||
/**
|
||||
* Absolute path to the directory containing MkDocs input docs
|
||||
*/
|
||||
const mkDocsDocsDir = path.resolve(path.dirname(absMkdocsYmlPath), docsDir ?? 'docs');
|
||||
|
||||
/**
|
||||
* @todo
|
||||
* `commands` is a dirname configurable via the `commandsDir` option added by
|
||||
* `@appium/typedoc-plugin-appium`. this lives in `typedoc.json`, but in order for it to be parsed
|
||||
* using TypeDoc's facilities, we have to load plugins before reading `typedoc.json`, which is
|
||||
* slow. we will probably have to support this in the future, but for now, we can just hardcode it
|
||||
*/
|
||||
const dirs = all ? await findDirsIn(typeDocOutDir) : [path.join(typeDocOutDir, 'commands')];
|
||||
|
||||
let shouldWriteMkDocsYml = false;
|
||||
|
||||
const navData = parseNav(nav);
|
||||
|
||||
// this is the thing which will be assigned to the `nav` prop
|
||||
// and thus written to `mkdocs.yml` if there were any changes.
|
||||
// we don't need the `name` prop, since the name is already present in the keypath.
|
||||
const newData: Omit<ParsedNavData, 'name'>[] = [];
|
||||
|
||||
for await (const dir of dirs) {
|
||||
const {data, changed} = await getNavItemsForDir(dir, mkDocsDocsDir, navData, nav, all);
|
||||
if (changed) {
|
||||
shouldWriteMkDocsYml = true;
|
||||
}
|
||||
newData.push(...data);
|
||||
}
|
||||
|
||||
if (shouldWriteMkDocsYml) {
|
||||
applyNavData(newData, mkDocsYml);
|
||||
const yaml = stringifyYaml(mkDocsYml);
|
||||
log.debug('Writing to %s:\n%s', mkDocsYmlPath, yaml);
|
||||
await safeWriteFile(mkDocsYmlPath, yaml, true);
|
||||
log.success(
|
||||
'Updated MkDocs navigation config for reference docs; please run "git add -A %s" and commit this change',
|
||||
relMkDocsYmlPath
|
||||
);
|
||||
} else {
|
||||
log.info('No changes needed for MkDocs config at %s', relMkDocsYmlPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@linkcode updateNav}
|
||||
*/
|
||||
export interface UpdateNavOpts {
|
||||
/**
|
||||
* Current working directory
|
||||
*/
|
||||
cwd?: string;
|
||||
/**
|
||||
* Path to `mkdocs.yml`
|
||||
*/
|
||||
mkdocsYml?: string;
|
||||
/**
|
||||
* Path to `package.json`
|
||||
*/
|
||||
packageJson?: string;
|
||||
/**
|
||||
* Path to `typedoc.json`
|
||||
*/
|
||||
typedocJson?: string;
|
||||
/**
|
||||
* If `true`, do not write any files
|
||||
* @remarks Not yet implemented
|
||||
*/
|
||||
dryRun?: boolean;
|
||||
|
||||
/**
|
||||
* If `true`, add _all_ reference documentation to the navigation config (not just commands)
|
||||
*/
|
||||
all?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used internally by {@linkcode updatedNav}
|
||||
* @see {@linkcode parseNav}
|
||||
*/
|
||||
interface ParsedNavData {
|
||||
/**
|
||||
* Keypath within `nav` for some file or URL
|
||||
*/
|
||||
keypath: string;
|
||||
/**
|
||||
* A filepath (usually) or a URL.
|
||||
* This is considered the "index" of the data, and should be unique within its parent. If it's not
|
||||
* unique, then it will probably end up that way after updating...
|
||||
*/
|
||||
fileOrUrl: string;
|
||||
/**
|
||||
* If this file or url has a proper name, this would be it. Most don't.
|
||||
*/
|
||||
name?: string;
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
/**
|
||||
* Builds reference documentation via TypeDoc. The output is _markdown_, intended to be imported into MkDocs.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
import {fs} from '@appium/support';
|
||||
import _ from 'lodash';
|
||||
import path from 'node:path';
|
||||
import {Application, ArgumentsReader, TypeDocOptions, TypeDocReader} from 'typedoc';
|
||||
import {
|
||||
DEFAULT_LOG_LEVEL,
|
||||
DEFAULT_REL_TYPEDOC_OUT_PATH,
|
||||
NAME_BIN,
|
||||
NAME_TYPEDOC_JSON,
|
||||
} from '../constants';
|
||||
import {DocutilsError} from '../error';
|
||||
import {findTypeDocJsonPath, readTypedocJson} from '../fs';
|
||||
import {getLogger} from '../logger';
|
||||
import {argify, relative, stopwatch} from '../util';
|
||||
|
||||
const log = getLogger('builder:reference');
|
||||
|
||||
/**
|
||||
* Executes TypeDoc _in the current process_
|
||||
*
|
||||
* You will probably want to run `updateNav()` after this.
|
||||
*
|
||||
* @privateRemarks Monkeypatches TypeDoc's homebrew "glob" implementation because it is broken
|
||||
* @parma typeDocJsonPath - Path to `typedoc.json`
|
||||
* @param opts - TypeDoc options
|
||||
*/
|
||||
export async function runTypedoc(typeDocJsonPath: string, opts: Record<string, string>) {
|
||||
const args = argify(opts);
|
||||
log.debug('TypeDoc args:', args);
|
||||
const app = new Application();
|
||||
app.options.setValue('plugin', [
|
||||
'typedoc-plugin-markdown',
|
||||
'typedoc-plugin-resolve-crossmodule-references',
|
||||
'@appium/typedoc-plugin-appium',
|
||||
]);
|
||||
app.options.addReader(new TypeDocReader());
|
||||
app.options.addReader(new ArgumentsReader(100, args));
|
||||
app.bootstrap({options: path.dirname(typeDocJsonPath)});
|
||||
const out = app.options.getValue('out');
|
||||
const project = app.convert();
|
||||
if (project) {
|
||||
return await app.generateDocs(project, out);
|
||||
}
|
||||
|
||||
throw new DocutilsError('TypeDoc found nothing to document. Is your package empty?');
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@linkcode buildReferenceDocs}
|
||||
*/
|
||||
export interface BuildReferenceOptions {
|
||||
/**
|
||||
* Path to `typedoc.json`
|
||||
*/
|
||||
typedocJson?: string;
|
||||
/**
|
||||
* Current working directory
|
||||
*/
|
||||
cwd?: string;
|
||||
/**
|
||||
* Path to `package.json`
|
||||
*/
|
||||
packageJson?: string;
|
||||
/**
|
||||
* Path to `tsconfig.json`
|
||||
*/
|
||||
tsconfigJson?: string;
|
||||
/**
|
||||
* "Title" for generated docs; this corresponds to {@linkcode typedoc.TypeDocOptionMap.name}
|
||||
*/
|
||||
title?: string;
|
||||
/**
|
||||
* This is here because we pass it thru to TypeDoc
|
||||
*/
|
||||
logLevel?: LogLevelName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log level names as supported by this package
|
||||
*
|
||||
* Used to convert our log level to TypeDoc's
|
||||
*/
|
||||
type LogLevelName = 'debug' | 'info' | 'error' | 'warn';
|
||||
|
||||
/**
|
||||
* Mapping of whatever our log level is to whatever TypeDoc's should be.
|
||||
*
|
||||
* TypeDoc's "info" is too verbose for our needs, and it's our default, so
|
||||
* we map it to "warn".
|
||||
*/
|
||||
const TypeDocLogLevelMap: Record<LogLevelName, string> = {
|
||||
debug: 'Verbose',
|
||||
info: 'Warn',
|
||||
warn: 'Warn',
|
||||
error: 'Error',
|
||||
};
|
||||
|
||||
/**
|
||||
* Build reference documentation via TypeDoc
|
||||
* @param opts - Options
|
||||
*/
|
||||
export async function buildReferenceDocs({
|
||||
typedocJson: typeDocJsonPath,
|
||||
cwd = process.cwd(),
|
||||
tsconfigJson: tsconfig,
|
||||
logLevel = DEFAULT_LOG_LEVEL,
|
||||
title,
|
||||
}: BuildReferenceOptions = {}) {
|
||||
const stop = stopwatch('buildReferenceDocs');
|
||||
typeDocJsonPath = typeDocJsonPath
|
||||
? path.resolve(cwd, typeDocJsonPath)
|
||||
: await findTypeDocJsonPath(cwd);
|
||||
if (!typeDocJsonPath) {
|
||||
throw new DocutilsError(
|
||||
`Could not find ${NAME_TYPEDOC_JSON} from ${cwd}; run "${NAME_BIN}" to create it`
|
||||
);
|
||||
}
|
||||
const pkgRoot = fs.findRoot(cwd);
|
||||
const relativePath = relative(cwd);
|
||||
const relativeTypeDocJsonPath = relativePath(typeDocJsonPath);
|
||||
log.debug(`Using ${relativeTypeDocJsonPath} as typedoc.json`);
|
||||
|
||||
let typeDocJson: Readonly<Partial<TypeDocOptions>>;
|
||||
// we only need typedoc.json to make sure we have a custom "out" path.
|
||||
try {
|
||||
typeDocJson = readTypedocJson(typeDocJsonPath);
|
||||
log.debug('Contents of %s: %O', relativeTypeDocJsonPath, typeDocJson);
|
||||
} catch (err) {
|
||||
log.error(err);
|
||||
throw new DocutilsError(
|
||||
`Could not read ${relativeTypeDocJsonPath}; run "${NAME_BIN} init" to create it`
|
||||
);
|
||||
}
|
||||
|
||||
// if for some reason "out" is not in typedoc.json, we want to use our default path.
|
||||
// otherwise, typedoc's default behavior is to write to the "docs" dir, which is the same dir that
|
||||
// we use (by default) as a source dir for the mkdocs site--which might contain files under vcs.
|
||||
let out: string | undefined;
|
||||
if (typeDocJson.out) {
|
||||
log.debug(`Found "out" option in ${NAME_TYPEDOC_JSON}: ${typeDocJson.out}`);
|
||||
} else {
|
||||
out = path.relative(
|
||||
path.dirname(typeDocJsonPath),
|
||||
path.join(pkgRoot, DEFAULT_REL_TYPEDOC_OUT_PATH)
|
||||
);
|
||||
log.debug('Setting "out" option to %s', out);
|
||||
}
|
||||
|
||||
const extraTypedocOpts = _.pickBy(
|
||||
{tsconfig, name: title, out, logLevel: TypeDocLogLevelMap[logLevel]},
|
||||
Boolean
|
||||
) as Record<string, string>;
|
||||
|
||||
try {
|
||||
await runTypedoc(typeDocJsonPath, extraTypedocOpts);
|
||||
const finalOut = (typeDocJson.out ?? out) as string;
|
||||
log.success(
|
||||
'Reference docs built at %s (%dms)',
|
||||
path.isAbsolute(finalOut) ? relativePath(finalOut) : finalOut,
|
||||
stop()
|
||||
);
|
||||
} catch (err) {
|
||||
log.error(err);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Runs `mkdocs`, pulling in reference markdown from TypeDoc and any other documentation from the
|
||||
* `docs_dir` directory (as configured in `mkdocs.yml`).
|
||||
* Runs `mkdocs`, pulling in documentation from the `docs_dir` directory
|
||||
* (as configured in `mkdocs.yml`).
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
@@ -25,7 +25,7 @@ const log = getLogger('mkdocs');
|
||||
async function doServe(
|
||||
pythonPath: string,
|
||||
args: string[] = [],
|
||||
opts: SpawnBackgroundProcessOpts = {}
|
||||
opts: SpawnBackgroundProcessOpts = {},
|
||||
) {
|
||||
const finalArgs = ['-m', NAME_MKDOCS, 'serve', ...args];
|
||||
log.debug('Executing %s via: %s, %O', NAME_MKDOCS, pythonPath, finalArgs);
|
||||
@@ -61,7 +61,7 @@ export async function buildSite({
|
||||
const pythonPath = await findPython();
|
||||
if (!pythonPath) {
|
||||
throw new DocutilsError(
|
||||
`Could not find ${NAME_PYTHON}3/${NAME_PYTHON} executable in PATH; please install Python v3`
|
||||
`Could not find ${NAME_PYTHON}3/${NAME_PYTHON} executable in PATH; please install Python v3`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ export async function buildSite({
|
||||
: await findMkDocsYml(cwd);
|
||||
if (!mkDocsYmlPath) {
|
||||
throw new DocutilsError(
|
||||
`Could not find ${NAME_MKDOCS_YML} from ${cwd}; run "${NAME_BIN} init" to create it`
|
||||
`Could not find ${NAME_MKDOCS_YML} from ${cwd}; run "${NAME_BIN} init" to create it`,
|
||||
);
|
||||
}
|
||||
const mkdocsArgs = ['-f', mkDocsYmlPath];
|
||||
@@ -95,7 +95,7 @@ export async function buildSite({
|
||||
log.warn(
|
||||
'No site_dir specified in args or %s; using default site_dir: %s',
|
||||
NAME_MKDOCS_YML,
|
||||
DEFAULT_SITE_DIR
|
||||
DEFAULT_SITE_DIR,
|
||||
);
|
||||
relSiteDir = relative(cwd, DEFAULT_SITE_DIR);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import path from 'node:path';
|
||||
import type {CommandModule, InferredOptionTypes, Options} from 'yargs';
|
||||
import {buildReferenceDocs, buildSite, deploy, updateNav} from '../../builder';
|
||||
import {buildSite, deploy} from '../../builder';
|
||||
import {NAME_BIN} from '../../constants';
|
||||
import {getLogger} from '../../logger';
|
||||
import {stopwatch} from '../../util';
|
||||
@@ -21,12 +21,6 @@ enum BuildCommandGroup {
|
||||
}
|
||||
|
||||
const opts = {
|
||||
reference: {
|
||||
describe: 'Run TypeDoc command API reference build (Markdown)',
|
||||
group: BuildCommandGroup.Build,
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
site: {
|
||||
describe: 'Run MkDocs build (HTML)',
|
||||
group: BuildCommandGroup.Build,
|
||||
@@ -55,24 +49,6 @@ const opts = {
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
},
|
||||
title: {
|
||||
defaultDescription: '(extension package name)',
|
||||
describe: 'Title of the API reference',
|
||||
group: BuildCommandGroup.Build,
|
||||
nargs: 1,
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
},
|
||||
'tsconfig-json': {
|
||||
defaultDescription: './tsconfig.json',
|
||||
describe: 'Path to tsconfig.json',
|
||||
group: BuildCommandGroup.BuildPaths,
|
||||
nargs: 1,
|
||||
normalize: true,
|
||||
requiresArg: true,
|
||||
coerce: path.resolve,
|
||||
type: 'string',
|
||||
},
|
||||
'mkdocs-yml': {
|
||||
defaultDescription: './mkdocs.yml',
|
||||
description: 'Path to mkdocs.yml',
|
||||
@@ -83,22 +59,6 @@ const opts = {
|
||||
coerce: path.resolve,
|
||||
type: 'string',
|
||||
},
|
||||
'typedoc-json': {
|
||||
defaultDescription: './typedoc.json',
|
||||
describe: 'Path to typedoc.json',
|
||||
group: BuildCommandGroup.BuildPaths,
|
||||
nargs: 1,
|
||||
normalize: true,
|
||||
requiresArg: true,
|
||||
coerce: path.resolve,
|
||||
type: 'string',
|
||||
},
|
||||
all: {
|
||||
describe: 'Output all reference docs (not just Appium comands)',
|
||||
group: BuildCommandGroup.Build,
|
||||
implies: 'site',
|
||||
type: 'boolean',
|
||||
},
|
||||
deploy: {
|
||||
describe: 'Commit HTML output to a branch using mike',
|
||||
group: BuildCommandGroup.Deploy,
|
||||
@@ -204,7 +164,7 @@ type BuildOptions = InferredOptionTypes<typeof opts>;
|
||||
|
||||
export default {
|
||||
command: 'build',
|
||||
describe: 'Build Appium extension documentation using TypeDoc & MkDocs',
|
||||
describe: 'Build Appium extension documentation using MkDocs',
|
||||
builder(yargs) {
|
||||
return yargs
|
||||
.options(opts)
|
||||
@@ -217,24 +177,14 @@ export default {
|
||||
return await checkMissingPaths(opts, BuildCommandGroup.BuildPaths, argv);
|
||||
})
|
||||
.epilog(
|
||||
'For help with further configuration, see:\n - MkDocs: https://www.mkdocs.org\n - TypeDoc: https://typedoc.org\n - Mike: https://github.com/jimporter/mike'
|
||||
'For help with further configuration, see:\n - MkDocs: https://www.mkdocs.org\n - Mike: https://github.com/jimporter/mike',
|
||||
);
|
||||
},
|
||||
async handler(args) {
|
||||
log.info('Building docs...');
|
||||
const stop = stopwatch('build');
|
||||
log.debug('Build command called with args: %O', args);
|
||||
if (!args.site && !args.reference) {
|
||||
// specifically not a DocUtils error
|
||||
throw new Error(
|
||||
'Cannot use both --no-site (--site=false) and --no-reference (--reference=false)'
|
||||
);
|
||||
}
|
||||
if (args.reference) {
|
||||
await buildReferenceDocs(args);
|
||||
}
|
||||
if (args.site) {
|
||||
await updateNav(args);
|
||||
if (args.deploy) {
|
||||
await deploy(args);
|
||||
} else {
|
||||
|
||||
@@ -149,33 +149,6 @@ const opts = {
|
||||
type: 'string',
|
||||
implies: 'typescript',
|
||||
},
|
||||
typedoc: {
|
||||
default: true,
|
||||
description: 'Create typedoc.json if needed',
|
||||
group: InitCommandGroup.Behavior,
|
||||
type: 'boolean',
|
||||
implies: 'entry-point',
|
||||
},
|
||||
'typedoc-json': {
|
||||
defaultDescription: './typedoc.json',
|
||||
describe: 'Path to new or existing typedoc.json',
|
||||
group: InitCommandGroup.Behavior,
|
||||
nargs: 1,
|
||||
normalize: true,
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
implies: 'typedoc',
|
||||
},
|
||||
'entry-point': {
|
||||
alias: 'e',
|
||||
describe: 'Source entry point of extension',
|
||||
group: InitCommandGroup.Paths,
|
||||
nargs: 1,
|
||||
normalize: false,
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
implies: 'typedoc',
|
||||
},
|
||||
typescript: {
|
||||
default: true,
|
||||
description: 'Create tsconfig.json if needed',
|
||||
|
||||
@@ -66,30 +66,6 @@ const opts = {
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
},
|
||||
typedoc: {
|
||||
default: true,
|
||||
description: 'Validate TypoDoc environment',
|
||||
group: ValidateCommandGroup.Behavior,
|
||||
type: 'boolean',
|
||||
},
|
||||
'typedoc-json': {
|
||||
defaultDescription: './typedoc.json',
|
||||
describe: 'Path to typedoc.json',
|
||||
group: ValidateCommandGroup.Paths,
|
||||
nargs: 1,
|
||||
normalize: true,
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
},
|
||||
'typedoc-path': {
|
||||
defaultDescription: '(derived from shell)',
|
||||
description: 'Path to typedoc executable',
|
||||
group: ValidateCommandGroup.Paths,
|
||||
nargs: 1,
|
||||
normalize: true,
|
||||
requiresArg: true,
|
||||
type: 'string',
|
||||
},
|
||||
typescript: {
|
||||
default: true,
|
||||
description: 'Validate TypeScript environment',
|
||||
@@ -105,8 +81,8 @@ export default {
|
||||
describe: 'Validate Environment',
|
||||
builder(yargs) {
|
||||
return yargs.options(opts).check(async (argv) => {
|
||||
if (!argv.python && !argv.typedoc && !argv.typescript && !argv.mkdocs) {
|
||||
return 'No validation targets specified; one or more of --python, --typescript, --typedoc or --mkdocs must be provided';
|
||||
if (!argv.python && !argv.typescript && !argv.mkdocs) {
|
||||
return 'No validation targets specified; one or more of --python, --typescript or --mkdocs must be provided';
|
||||
}
|
||||
return checkMissingPaths(opts, ValidateCommandGroup.Paths, argv);
|
||||
});
|
||||
@@ -131,7 +107,7 @@ export default {
|
||||
|
||||
if (errorCount) {
|
||||
throw new DocutilsError(
|
||||
`Validation failed with ${errorCount} ${util.pluralize('error', errorCount)}`
|
||||
`Validation failed with ${errorCount} ${util.pluralize('error', errorCount)}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -26,10 +26,6 @@ export const NAME_TSCONFIG_JSON = 'tsconfig.json';
|
||||
* `python` executable
|
||||
*/
|
||||
export const NAME_PYTHON = 'python';
|
||||
/**
|
||||
* Default name of the `typedoc.json` config file
|
||||
*/
|
||||
export const NAME_TYPEDOC_JSON = 'typedoc.json';
|
||||
/**
|
||||
* It's `package.json`!
|
||||
*/
|
||||
@@ -53,11 +49,6 @@ export const NAME_MKDOCS = 'mkdocs';
|
||||
*/
|
||||
export const NAME_MIKE = 'mike';
|
||||
|
||||
/**
|
||||
* Name of the `typedoc` executable
|
||||
*/
|
||||
export const NAME_TYPEDOC = 'typedoc';
|
||||
|
||||
/**
|
||||
* Name of the `pip` module.
|
||||
*
|
||||
@@ -104,7 +95,7 @@ export const PKG_ROOT_DIR = fs.findRoot(__dirname);
|
||||
*/
|
||||
|
||||
export const DOCUTILS_PKG: PackageJson = JSON.parse(
|
||||
readFileSync(path.join(PKG_ROOT_DIR, NAME_PACKAGE_JSON), 'utf8')
|
||||
readFileSync(path.join(PKG_ROOT_DIR, NAME_PACKAGE_JSON), 'utf8'),
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -113,11 +104,6 @@ export const DOCUTILS_PKG: PackageJson = JSON.parse(
|
||||
|
||||
export const REQUIREMENTS_TXT_PATH = path.join(PKG_ROOT_DIR, NAME_REQUIREMENTS_TXT);
|
||||
|
||||
/**
|
||||
* The default output path for Typedoc, computed relative to the consuming package's root
|
||||
*/
|
||||
export const DEFAULT_REL_TYPEDOC_OUT_PATH = path.join('docs', 'reference');
|
||||
|
||||
/**
|
||||
* The default branch to deploy to
|
||||
*/
|
||||
@@ -149,11 +135,6 @@ export const LogLevelMap = {
|
||||
debug: LogLevel.Debug,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Default site nav header text
|
||||
*/
|
||||
export const DEFAULT_NAV_HEADER = 'Reference';
|
||||
|
||||
/**
|
||||
* If the user does not specify a site directory _and_ the `mkdocs.yml` doesn't either, use this dir.
|
||||
*/
|
||||
|
||||
@@ -4,28 +4,18 @@
|
||||
*/
|
||||
|
||||
import {fs} from '@appium/support';
|
||||
import findUp from 'find-up';
|
||||
import * as JSON5 from 'json5';
|
||||
import _ from 'lodash';
|
||||
import path from 'node:path';
|
||||
import _pkgDir from 'pkg-dir';
|
||||
import readPkg, {NormalizedPackageJson, PackageJson} from 'read-pkg';
|
||||
import {JsonValue} from 'type-fest';
|
||||
import {Application, TypeDocReader} from 'typedoc';
|
||||
import YAML from 'yaml';
|
||||
import {
|
||||
NAME_MIKE,
|
||||
NAME_MKDOCS_YML,
|
||||
NAME_NPM,
|
||||
NAME_PACKAGE_JSON,
|
||||
NAME_PYTHON,
|
||||
NAME_TYPEDOC,
|
||||
NAME_TYPEDOC_JSON,
|
||||
} from './constants';
|
||||
import {NAME_MIKE, NAME_MKDOCS_YML, NAME_NPM, NAME_PACKAGE_JSON, NAME_PYTHON} from './constants';
|
||||
import {DocutilsError} from './error';
|
||||
import {getLogger} from './logger';
|
||||
import {MkDocsYml} from './model';
|
||||
import { exec } from 'teen_process';
|
||||
import {exec} from 'teen_process';
|
||||
|
||||
const log = getLogger('fs');
|
||||
|
||||
@@ -44,7 +34,7 @@ export const findPkgDir = _.memoize(_pkgDir);
|
||||
export const stringifyYaml: (value: JsonValue) => string = _.partialRight(
|
||||
YAML.stringify,
|
||||
{indent: 2},
|
||||
undefined
|
||||
undefined,
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -65,17 +55,17 @@ export const stringifyJson5: (value: JsonValue) => string = _.partialRight(JSON5
|
||||
export const stringifyJson: (value: JsonValue) => string = _.partialRight(
|
||||
JSON.stringify,
|
||||
2,
|
||||
undefined
|
||||
undefined,
|
||||
);
|
||||
|
||||
/**
|
||||
* Reads a YAML file, parses it and caches the result
|
||||
*/
|
||||
export const readYaml = _.memoize(async (filepath: string) =>
|
||||
const readYaml = _.memoize(async (filepath: string) =>
|
||||
YAML.parse(await fs.readFile(filepath, 'utf8'), {
|
||||
prettyErrors: false,
|
||||
logLevel: 'silent',
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -87,7 +77,7 @@ export const readYaml = _.memoize(async (filepath: string) =>
|
||||
*/
|
||||
export async function findInPkgDir(
|
||||
filename: string,
|
||||
cwd = process.cwd()
|
||||
cwd = process.cwd(),
|
||||
): Promise<string | undefined> {
|
||||
const pkgDir = await findPkgDir(cwd);
|
||||
if (!pkgDir) {
|
||||
@@ -96,19 +86,6 @@ export async function findInPkgDir(
|
||||
return path.join(pkgDir, filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a `typedoc.json`, expected to be a sibling of `package.json`
|
||||
*
|
||||
* Caches the result.
|
||||
* @param cwd - Current working directory
|
||||
* @returns Path to `typedoc.json`
|
||||
*/
|
||||
export const findTypeDocJsonPath = _.memoize(async (cwd = process.cwd()) => {
|
||||
const filepath = await findUp(NAME_TYPEDOC_JSON, {cwd, type: 'file'});
|
||||
log.debug('Found `typedoc.json` at %s', filepath);
|
||||
return filepath;
|
||||
});
|
||||
|
||||
/**
|
||||
* Finds an `mkdocs.yml`, expected to be a sibling of `package.json`
|
||||
*
|
||||
@@ -126,20 +103,20 @@ export const findMkDocsYml = _.memoize(_.partial(findInPkgDir, NAME_MKDOCS_YML))
|
||||
*/
|
||||
async function _readPkgJson(
|
||||
cwd: string,
|
||||
normalize: true
|
||||
normalize: true,
|
||||
): Promise<{pkgPath: string; pkg: NormalizedPackageJson}>;
|
||||
async function _readPkgJson(
|
||||
cwd: string,
|
||||
normalize?: false
|
||||
normalize?: false,
|
||||
): Promise<{pkgPath: string; pkg: PackageJson}>;
|
||||
async function _readPkgJson(
|
||||
cwd: string,
|
||||
normalize?: boolean
|
||||
normalize?: boolean,
|
||||
): Promise<{pkgPath: string; pkg: PackageJson | NormalizedPackageJson}> {
|
||||
const pkgDir = await findPkgDir(cwd);
|
||||
if (!pkgDir) {
|
||||
throw new DocutilsError(
|
||||
`Could not find a ${NAME_PACKAGE_JSON} near ${cwd}; please create it before using this utility`
|
||||
`Could not find a ${NAME_PACKAGE_JSON} near ${cwd}; please create it before using this utility`,
|
||||
);
|
||||
}
|
||||
const pkgPath = path.join(pkgDir, NAME_PACKAGE_JSON);
|
||||
@@ -158,27 +135,12 @@ async function _readPkgJson(
|
||||
*/
|
||||
export const readPackageJson = _.memoize(_readPkgJson);
|
||||
|
||||
/**
|
||||
* Reads a `typedoc.json` file and returns its parsed contents.
|
||||
*
|
||||
* TypeDoc expands the "extends" field, which is why we use its facilities. It, unfortunately, is a
|
||||
* blocking operation.
|
||||
*/
|
||||
export const readTypedocJson = _.memoize((typedocJsonPath: string) => {
|
||||
const app = new Application();
|
||||
app.options.setValue('plugin', 'none');
|
||||
app.options.setValue('logger', 'none');
|
||||
app.options.addReader(new TypeDocReader());
|
||||
app.bootstrap({options: path.dirname(typedocJsonPath)});
|
||||
return app.options.getRawValues();
|
||||
});
|
||||
|
||||
/**
|
||||
* Reads a JSON5 file and parses it
|
||||
*/
|
||||
export const readJson5 = _.memoize(
|
||||
async <T extends JsonValue>(filepath: string): Promise<T> =>
|
||||
JSON5.parse(await fs.readFile(filepath, 'utf8'))
|
||||
JSON5.parse(await fs.readFile(filepath, 'utf8')),
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -186,7 +148,7 @@ export const readJson5 = _.memoize(
|
||||
*/
|
||||
export const readJson = _.memoize(
|
||||
async <T extends JsonValue>(filepath: string): Promise<T> =>
|
||||
JSON.parse(await fs.readFile(filepath, 'utf8'))
|
||||
JSON.parse(await fs.readFile(filepath, 'utf8')),
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -225,11 +187,6 @@ const whichPython = _.partial(cachedWhich, NAME_PYTHON, {nothrow: true});
|
||||
*/
|
||||
const whichPython3 = _.partial(cachedWhich, `${NAME_PYTHON}3`, {nothrow: true});
|
||||
|
||||
/**
|
||||
* Finds `typedoc` executable
|
||||
*/
|
||||
const whichTypeDoc = _.partial(cachedWhich, NAME_TYPEDOC, {nothrow: true});
|
||||
|
||||
/**
|
||||
* `mike` cannot be invoked via `python -m`, so we need to find the script.
|
||||
*/
|
||||
@@ -254,21 +211,7 @@ export const findMike = _.partial(async () => {
|
||||
return mikePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {}
|
||||
});
|
||||
|
||||
/**
|
||||
* Finds the `typedoc` executable.
|
||||
*
|
||||
* Looks in the `node_modules/.bin` dir from the current working directory, and if this fails, in the `node_modules` dir for the file which `.bin/typedoc` should be a symlink of, and if _that_ fails, try the `PATH`.
|
||||
*/
|
||||
export const findTypeDoc = _.memoize(async (cwd = process.cwd()): Promise<string | undefined> => {
|
||||
// .cmd is for win32, of course. note that glob _always_ uses posix dir separators.
|
||||
const globResult =
|
||||
(await fs.glob('node_modules/.bin/typedoc?(.cmd)', {cwd, nodir: true})) ??
|
||||
(await fs.glob('node_modules/**/typedoc/bin/typedoc', {cwd, nodir: true, follow: true}));
|
||||
return _.first(globResult) ?? (await whichTypeDoc());
|
||||
} catch {}
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -277,7 +220,7 @@ export const findTypeDoc = _.memoize(async (cwd = process.cwd()): Promise<string
|
||||
* `python3` is preferred over `python`, since the latter could be Python 2.
|
||||
*/
|
||||
export const findPython = _.memoize(
|
||||
async (): Promise<string | undefined> => (await whichPython3()) ?? (await whichPython())
|
||||
async (): Promise<string | undefined> => (await whichPython3()) ?? (await whichPython()),
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -313,16 +256,5 @@ export const readMkDocsYml = _.memoize(
|
||||
}
|
||||
}
|
||||
return mkDocsYml;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Given an abs path to a directory, return a list of all abs paths of all directories in it
|
||||
*/
|
||||
export const findDirsIn = _.memoize(async (dirpath: string): Promise<string[]> => {
|
||||
if (!path.isAbsolute(dirpath)) {
|
||||
throw new DocutilsError(`Expected absolute path, got '${dirpath}'`);
|
||||
}
|
||||
const dirEnts = await fs.readdir(dirpath, {withFileTypes: true});
|
||||
return dirEnts.filter((ent) => ent.isDirectory()).map((ent) => path.join(dirpath, ent.name));
|
||||
});
|
||||
|
||||
@@ -5,21 +5,14 @@
|
||||
*/
|
||||
|
||||
import * as JSON5 from 'json5';
|
||||
import {
|
||||
NAME_MKDOCS_YML,
|
||||
NAME_TSCONFIG_JSON,
|
||||
NAME_PYTHON,
|
||||
REQUIREMENTS_TXT_PATH,
|
||||
NAME_TYPEDOC_JSON,
|
||||
NAME_PACKAGE_JSON,
|
||||
} from './constants';
|
||||
import {NAME_MKDOCS_YML, NAME_TSCONFIG_JSON, NAME_PYTHON, REQUIREMENTS_TXT_PATH} from './constants';
|
||||
import YAML from 'yaml';
|
||||
import {exec} from 'teen_process';
|
||||
import {PackageJson, Simplify} from 'type-fest';
|
||||
import {Simplify} from 'type-fest';
|
||||
import {DocutilsError} from './error';
|
||||
import {createScaffoldTask, ScaffoldTaskOptions} from './scaffold';
|
||||
import {getLogger} from './logger';
|
||||
import {MkDocsYml, TsConfigJson, TypeDocJson} from './model';
|
||||
import {MkDocsYml, TsConfigJson} from './model';
|
||||
import _ from 'lodash';
|
||||
import {findPython, stringifyJson5, stringifyYaml} from './fs';
|
||||
|
||||
@@ -32,18 +25,6 @@ const BASE_MKDOCS_YML: Readonly<MkDocsYml> = Object.freeze({
|
||||
site_dir: 'site',
|
||||
});
|
||||
|
||||
/**
|
||||
* Data for the base `typedoc.json` file
|
||||
*/
|
||||
const BASE_TYPEDOC_JSON: Readonly<TypeDocJson> = Object.freeze({
|
||||
$schema: 'https://typedoc.org/schema.json',
|
||||
cleanOutputDir: true,
|
||||
entryPointStrategy: 'packages',
|
||||
theme: 'appium',
|
||||
readme: 'none',
|
||||
entryPoints: ['.'],
|
||||
});
|
||||
|
||||
/**
|
||||
* Data for the base `tsconfig.json` file
|
||||
*/
|
||||
@@ -55,13 +36,6 @@ const BASE_TSCONFIG_JSON: Readonly<TsConfigJson> = Object.freeze({
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Data for the base `package.json` file.
|
||||
* We expect `package.json` to exist, and we are not in the business of creating it.
|
||||
* However, we will need to add a `typedoc.entryPoint` prop to it.
|
||||
*/
|
||||
const BASE_PACKAGE_JSON: Readonly<PackageJson> = Object.freeze({});
|
||||
|
||||
const log = getLogger('init');
|
||||
const dryRunLog = getLogger('dry-run', log);
|
||||
|
||||
@@ -96,36 +70,7 @@ export const initTsConfigJson = createScaffoldTask<InitTsConfigOptions, TsConfig
|
||||
},
|
||||
deserialize: JSON5.parse,
|
||||
serialize: stringifyJson5,
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Function which scaffolds a `typedoc.json` file
|
||||
*/
|
||||
export const initTypeDocJson = createScaffoldTask<InitTypeDocOptions, TypeDocJson>(
|
||||
NAME_TYPEDOC_JSON,
|
||||
BASE_TYPEDOC_JSON,
|
||||
'TypeDoc configuration'
|
||||
);
|
||||
|
||||
/**
|
||||
* Function which scaffolds a `package.json` file
|
||||
*
|
||||
* This only amends prop `typedoc.entryPoint` to the `package.json` file.
|
||||
*
|
||||
* If, strangely, `package.json` did not exist, then it will now contain _only_ that prop.
|
||||
*/
|
||||
export const initTypeDocPkgJson = createScaffoldTask<InitTypeDocOptions, PackageJson>(
|
||||
NAME_PACKAGE_JSON,
|
||||
BASE_PACKAGE_JSON,
|
||||
'Package configuration for TypeDoc',
|
||||
{
|
||||
transform: (content, opts) =>
|
||||
({
|
||||
...content,
|
||||
typedoc: {entryPoint: opts.typeDocEntryPoint},
|
||||
} as PackageJson),
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -181,7 +126,7 @@ export const initMkDocs = createScaffoldTask<InitMkDocsOptions, MkDocsYml>(
|
||||
site_description: siteDescription,
|
||||
};
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -212,7 +157,7 @@ export async function initPython({
|
||||
}
|
||||
} catch (err) {
|
||||
throw new DocutilsError(
|
||||
`Could not install Python dependencies. Reason: ${(err as Error).message}`
|
||||
`Could not install Python dependencies. Reason: ${(err as Error).message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -238,7 +183,6 @@ export interface InitMkDocsOptions extends ScaffoldTaskOptions {
|
||||
*/
|
||||
export async function init({
|
||||
typescript,
|
||||
typedoc,
|
||||
python,
|
||||
tsconfigJson: tsconfigJsonPath,
|
||||
packageJson: packageJsonPath,
|
||||
@@ -254,15 +198,7 @@ export async function init({
|
||||
cwd,
|
||||
pythonPath,
|
||||
upgrade,
|
||||
typedocJson: typeDocJsonPath,
|
||||
entryPoint: typeDocEntryPoint,
|
||||
}: InitOptions = {}): Promise<void> {
|
||||
if (!typescript && typedoc) {
|
||||
log.warn(
|
||||
'Initialization of tsconfig.json disabled. TypeDoc requires a tsconfig.json; please ensure it exists'
|
||||
);
|
||||
}
|
||||
|
||||
if (typescript && !upgrade) {
|
||||
await initTsConfigJson({
|
||||
dest: tsconfigJsonPath,
|
||||
@@ -274,23 +210,6 @@ export async function init({
|
||||
});
|
||||
}
|
||||
|
||||
if (typedoc && !upgrade) {
|
||||
await initTypeDocJson({
|
||||
dest: typeDocJsonPath,
|
||||
packageJson: packageJsonPath,
|
||||
overwrite,
|
||||
dryRun,
|
||||
cwd,
|
||||
});
|
||||
await initTypeDocPkgJson({
|
||||
packageJson: packageJsonPath,
|
||||
overwrite: true, // <-- always overwrite
|
||||
dryRun,
|
||||
cwd,
|
||||
typeDocEntryPoint,
|
||||
});
|
||||
}
|
||||
|
||||
if (python) {
|
||||
await initPython({pythonPath, dryRun, upgrade});
|
||||
}
|
||||
@@ -310,10 +229,6 @@ export async function init({
|
||||
}
|
||||
}
|
||||
|
||||
export interface InitTypeDocOptions extends ScaffoldTaskOptions {
|
||||
typeDocEntryPoint?: string;
|
||||
}
|
||||
|
||||
export interface InitTsConfigOptions extends ScaffoldTaskOptions {
|
||||
/**
|
||||
* List of source files (globs supported); typically `src` or `lib`
|
||||
@@ -338,15 +253,11 @@ export interface InitPythonOptions extends ScaffoldTaskOptions {
|
||||
* The props of the various "path" options are rewritten as `dest` for the scaffold tasks functions.
|
||||
*/
|
||||
export type InitOptions = Simplify<
|
||||
Omit<InitPythonOptions & InitTsConfigOptions & InitTypeDocOptions & InitMkDocsOptions, 'dest'> & {
|
||||
Omit<InitPythonOptions & InitTsConfigOptions & InitMkDocsOptions, 'dest'> & {
|
||||
/**
|
||||
* If `true` will initialize a `tsconfig.json` file
|
||||
*/
|
||||
typescript?: boolean;
|
||||
/**
|
||||
* If `true` will initialize a `typedoc.json` file
|
||||
*/
|
||||
typedoc?: boolean;
|
||||
/**
|
||||
* If `true` will install Python deps
|
||||
*/
|
||||
@@ -355,10 +266,6 @@ export type InitOptions = Simplify<
|
||||
* If `true` will initialize a `mkdocs.yml` file
|
||||
*/
|
||||
mkdocs?: boolean;
|
||||
/**
|
||||
* Path to new or existing `typedoc.json` file
|
||||
*/
|
||||
typedocJson?: string;
|
||||
/**
|
||||
* Path to new or existing `tsconfig.json` file
|
||||
*/
|
||||
@@ -376,10 +283,5 @@ export type InitOptions = Simplify<
|
||||
* If `true`, upgrade only
|
||||
*/
|
||||
upgrade?: boolean;
|
||||
|
||||
/**
|
||||
* Path to entry point of extension (source; not "main" field)
|
||||
*/
|
||||
entryPoint?: string;
|
||||
}
|
||||
>;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
*/
|
||||
|
||||
import type {Jsonify, JsonValue, TsConfigJson as TsConfigJsonBase} from 'type-fest';
|
||||
import type {TypeDocOptions} from 'typedoc';
|
||||
|
||||
/**
|
||||
* A `tsconfig.json` file w/ `$schema` prop
|
||||
@@ -14,19 +13,6 @@ export type TsConfigJson = TsConfigJsonBase & {
|
||||
$schema?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* A `typedoc.json` file w/ `$schema` and `extends` props
|
||||
*
|
||||
* TypeDoc doesn't recognize `$schema` and ignores it; its own config parser expands the value of
|
||||
* `extends` before it reaches its `Options` class. This is why we cannot use `TypeDocOptions` directly.
|
||||
*/
|
||||
export type TypeDocJson = Jsonify<
|
||||
Partial<TypeDocOptions> & {
|
||||
$schema?: string;
|
||||
extends?: string;
|
||||
}
|
||||
>;
|
||||
|
||||
/**
|
||||
* The `nav` prop of an `mkdocs.yml` file
|
||||
* @see {@linkcode MkDocsYml}
|
||||
|
||||
@@ -12,7 +12,6 @@ import path from 'node:path';
|
||||
import {satisfies} from 'semver';
|
||||
import {exec} from 'teen_process';
|
||||
import {
|
||||
DEFAULT_REL_TYPEDOC_OUT_PATH,
|
||||
DOCUTILS_PKG,
|
||||
NAME_BIN,
|
||||
NAME_ERR_ENOENT,
|
||||
@@ -24,24 +23,13 @@ import {
|
||||
NAME_PYTHON,
|
||||
NAME_REQUIREMENTS_TXT,
|
||||
NAME_TSCONFIG_JSON,
|
||||
NAME_TYPEDOC,
|
||||
NAME_TYPEDOC_JSON,
|
||||
NAME_TYPESCRIPT,
|
||||
REQUIREMENTS_TXT_PATH,
|
||||
} from './constants';
|
||||
import {DocutilsError} from './error';
|
||||
import {
|
||||
findMkDocsYml,
|
||||
findPkgDir,
|
||||
findTypeDoc,
|
||||
readJson5,
|
||||
readMkDocsYml,
|
||||
readTypedocJson,
|
||||
whichNpm,
|
||||
findPython,
|
||||
} from './fs';
|
||||
import {findMkDocsYml, findPkgDir, readJson5, readMkDocsYml, whichNpm, findPython} from './fs';
|
||||
import {getLogger} from './logger';
|
||||
import {MkDocsYml, PipPackage, TypeDocJson} from './model';
|
||||
import {MkDocsYml, PipPackage} from './model';
|
||||
import {relative} from './util';
|
||||
|
||||
/**
|
||||
@@ -54,11 +42,6 @@ const PYTHON_VER_STR = 'Python 3.';
|
||||
*/
|
||||
const TYPESCRIPT_VERSION_REGEX = /Version\s(\d+\.\d+\..+)/;
|
||||
|
||||
/**
|
||||
* Matches the TypeDoc version string from `typedoc --version`
|
||||
*/
|
||||
const TYPEDOC_VERSION_REGEX = /TypeDoc\s(\d+\.\d+\..+)/;
|
||||
|
||||
/**
|
||||
* Matches the MkDocs version string from `mkdocs --version`
|
||||
*/
|
||||
@@ -72,7 +55,6 @@ const log = getLogger('validate');
|
||||
export type ValidationKind =
|
||||
| typeof NAME_PYTHON
|
||||
| typeof NAME_TYPESCRIPT
|
||||
| typeof NAME_TYPEDOC
|
||||
| typeof NAME_NPM
|
||||
| typeof NAME_MKDOCS;
|
||||
|
||||
@@ -137,13 +119,6 @@ export class DocutilsValidator extends EventEmitter {
|
||||
*/
|
||||
protected tsconfigJsonPath?: string;
|
||||
|
||||
/**
|
||||
* Path to `typedoc.json`. If not provided, will be lazily resolved.
|
||||
*/
|
||||
protected typeDocJsonPath?: string;
|
||||
|
||||
protected typeDocPath?: string;
|
||||
|
||||
/**
|
||||
* Emitted when validation begins with a list of validation kinds to be performed
|
||||
* @event
|
||||
@@ -180,21 +155,15 @@ export class DocutilsValidator extends EventEmitter {
|
||||
this.pythonPath = opts.pythonPath;
|
||||
this.cwd = opts.cwd ?? process.cwd();
|
||||
this.tsconfigJsonPath = opts.tsconfigJson;
|
||||
this.typeDocJsonPath = opts.typedocJson;
|
||||
this.npmPath = opts.npm;
|
||||
this.mkDocsYmlPath = opts.mkdocsYml;
|
||||
this.typeDocPath = opts.typedocPath;
|
||||
|
||||
if (opts.python) {
|
||||
this.validations.add(NAME_PYTHON);
|
||||
}
|
||||
if (opts.typescript) {
|
||||
this.validations.add(NAME_TYPESCRIPT);
|
||||
// npm validation is required for both typescript and typedoc validation
|
||||
this.validations.add(NAME_NPM);
|
||||
}
|
||||
if (opts.typedoc) {
|
||||
this.validations.add(NAME_TYPEDOC);
|
||||
// npm validation is required for typescript
|
||||
this.validations.add(NAME_NPM);
|
||||
}
|
||||
if (opts.mkdocs) {
|
||||
@@ -233,11 +202,6 @@ export class DocutilsValidator extends EventEmitter {
|
||||
await this.validateTypeScriptConfig();
|
||||
}
|
||||
|
||||
if (this.validations.has(NAME_TYPEDOC)) {
|
||||
await this.validateTypeDoc();
|
||||
await this.validateTypeDocConfig();
|
||||
}
|
||||
|
||||
this.emit(DocutilsValidator.END, this.emittedErrors.size);
|
||||
} finally {
|
||||
this.reset();
|
||||
@@ -321,7 +285,7 @@ export class DocutilsValidator extends EventEmitter {
|
||||
|
||||
if (!pythonPath) {
|
||||
return this.fail(
|
||||
`Could not find ${NAME_PYTHON} executable in PATH. If it is installed, check your PATH environment variable.`
|
||||
`Could not find ${NAME_PYTHON} executable in PATH. If it is installed, check your PATH environment variable.`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -338,18 +302,18 @@ export class DocutilsValidator extends EventEmitter {
|
||||
const mkDocsPipPkg = _.find(reqs, {name: NAME_MKDOCS});
|
||||
if (!mkDocsPipPkg) {
|
||||
throw new DocutilsError(
|
||||
`No ${NAME_MKDOCS} package in ${REQUIREMENTS_TXT_PATH}. This is a bug.`
|
||||
`No ${NAME_MKDOCS} package in ${REQUIREMENTS_TXT_PATH}. This is a bug.`,
|
||||
);
|
||||
}
|
||||
const {version: mkDocsReqdVersion} = mkDocsPipPkg;
|
||||
if (version !== mkDocsReqdVersion) {
|
||||
return this.fail(
|
||||
`${NAME_MKDOCS} is v${version}, but ${REQUIREMENTS_TXT_PATH} requires v${mkDocsReqdVersion}`
|
||||
`${NAME_MKDOCS} is v${version}, but ${REQUIREMENTS_TXT_PATH} requires v${mkDocsReqdVersion}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new DocutilsError(
|
||||
`Could not parse version from MkDocs. This is a bug. Output was ${rawMkDocsVersion}`
|
||||
`Could not parse version from MkDocs. This is a bug. Output was ${rawMkDocsVersion}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -365,7 +329,7 @@ export class DocutilsValidator extends EventEmitter {
|
||||
mkDocsYmlPath = mkDocsYmlPath ?? this.mkDocsYmlPath ?? (await findMkDocsYml(this.cwd));
|
||||
if (!mkDocsYmlPath) {
|
||||
return this.fail(
|
||||
`Could not find ${NAME_MKDOCS_YML} from ${this.cwd}. Run "${NAME_BIN} init" to create it`
|
||||
`Could not find ${NAME_MKDOCS_YML} from ${this.cwd}. Run "${NAME_BIN} init" to create it`,
|
||||
);
|
||||
}
|
||||
let mkDocsYml: MkDocsYml;
|
||||
@@ -375,7 +339,7 @@ export class DocutilsValidator extends EventEmitter {
|
||||
const err = e as NodeJS.ErrnoException;
|
||||
if (err.code === NAME_ERR_ENOENT) {
|
||||
return this.fail(
|
||||
`Could not find ${NAME_MKDOCS_YML} at ${mkDocsYmlPath}. Use --mkdocs-yml to specify a different path.`
|
||||
`Could not find ${NAME_MKDOCS_YML} at ${mkDocsYmlPath}. Use --mkdocs-yml to specify a different path.`,
|
||||
);
|
||||
}
|
||||
return this.fail(`Could not parse ${mkDocsYmlPath}: ${err}`);
|
||||
@@ -402,7 +366,7 @@ export class DocutilsValidator extends EventEmitter {
|
||||
const npmPath = this.npmPath ?? (await whichNpm());
|
||||
if (!npmPath) {
|
||||
throw new DocutilsError(
|
||||
`Could not find ${NAME_NPM} in PATH. That seems weird, doesn't it?`
|
||||
`Could not find ${NAME_NPM} in PATH. That seems weird, doesn't it?`,
|
||||
);
|
||||
}
|
||||
const {stdout: npmVersion} = await exec(npmPath, ['-v']);
|
||||
@@ -445,7 +409,7 @@ export class DocutilsValidator extends EventEmitter {
|
||||
installedPkgs = JSON.parse(pipListOutput) as PipPackage[];
|
||||
} catch {
|
||||
throw new DocutilsError(
|
||||
`Could not parse output of "${NAME_PIP} list" as JSON: ${pipListOutput}`
|
||||
`Could not parse output of "${NAME_PIP} list" as JSON: ${pipListOutput}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -470,23 +434,23 @@ export class DocutilsValidator extends EventEmitter {
|
||||
msgParts.push(
|
||||
`The following required ${util.pluralize(
|
||||
'package',
|
||||
missingPackages.length
|
||||
missingPackages.length,
|
||||
)} could not be found:\n${missingPackages
|
||||
.map((p) => chalk`- {yellow ${p.name}} @ {yellow ${p.version}}`)
|
||||
.join('\n')}`
|
||||
.join('\n')}`,
|
||||
);
|
||||
}
|
||||
if (invalidVersionPackages.length) {
|
||||
msgParts.push(
|
||||
`The following required ${util.pluralize(
|
||||
'package',
|
||||
invalidVersionPackages.length
|
||||
invalidVersionPackages.length,
|
||||
)} are installed, but at the wrong version:\n${invalidVersionPackages
|
||||
.map(
|
||||
([expected, actual]) =>
|
||||
chalk`- {yellow ${expected.name}} @ {yellow ${expected.version}} (found {red ${actual.version}})`
|
||||
chalk`- {yellow ${expected.name}} @ {yellow ${expected.version}} (found {red ${actual.version}})`,
|
||||
)
|
||||
.join('\n')}`
|
||||
.join('\n')}`,
|
||||
);
|
||||
}
|
||||
if (msgParts.length) {
|
||||
@@ -509,7 +473,7 @@ export class DocutilsValidator extends EventEmitter {
|
||||
const {stdout} = await exec(pythonPath, ['--version']);
|
||||
if (!stdout.includes(PYTHON_VER_STR)) {
|
||||
return this.fail(
|
||||
`Could not find Python 3.x in PATH; found ${stdout}. Please use --python-path`
|
||||
`Could not find Python 3.x in PATH; found ${stdout}. Please use --python-path`,
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
@@ -518,107 +482,6 @@ export class DocutilsValidator extends EventEmitter {
|
||||
this.ok('Python version OK');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts TypeDoc is installed, runnable, the correct version, and that the config file is readable
|
||||
* and constaints required options
|
||||
*
|
||||
* @todo Another option would be to `npm exec typedoc@<version>` which delegates to `npx`.
|
||||
*/
|
||||
protected async validateTypeDoc() {
|
||||
const pkgDir = await this.findPkgDir();
|
||||
const typeDocPath = this.typeDocPath ?? (await findTypeDoc(pkgDir));
|
||||
|
||||
if (!typeDocPath) {
|
||||
return this.fail(`Could not find ${NAME_TYPEDOC}; is it installed?`);
|
||||
}
|
||||
log.debug('Found %s at %s', NAME_TYPEDOC, typeDocPath);
|
||||
|
||||
let rawTypeDocVersion: string;
|
||||
let typeDocVersion: string;
|
||||
try {
|
||||
({stdout: rawTypeDocVersion} = await exec(process.execPath, [typeDocPath, '--version'], {
|
||||
cwd: pkgDir,
|
||||
}));
|
||||
} catch (err) {
|
||||
return this.fail(
|
||||
`Could not execute ${process.execPath} ${typeDocPath} from ${pkgDir}. Reason: ${
|
||||
(err as Error).message
|
||||
}`
|
||||
);
|
||||
}
|
||||
|
||||
if (rawTypeDocVersion) {
|
||||
const match = rawTypeDocVersion.match(TYPEDOC_VERSION_REGEX);
|
||||
if (match) {
|
||||
typeDocVersion = match[1];
|
||||
} else {
|
||||
throw new DocutilsError(
|
||||
`Could not parse TypeDoc version from "typedoc --version"; output was:\n ${rawTypeDocVersion}`
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const reqdTypeDocVersion = DOCUTILS_PKG.dependencies!.typedoc!; // this is our own package.json
|
||||
if (!satisfies(typeDocVersion, reqdTypeDocVersion)) {
|
||||
return this.fail(
|
||||
`Found TypeDoc version ${typeDocVersion}, but ${reqdTypeDocVersion} is required`
|
||||
);
|
||||
}
|
||||
this.ok('TypeDoc install OK');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the `typedoc.json` file
|
||||
*/
|
||||
protected async validateTypeDocConfig() {
|
||||
const pkgDir = await this.findPkgDir();
|
||||
if (!pkgDir) {
|
||||
return this.fail(new DocutilsError(`Could not find package.json in ${this.cwd}`));
|
||||
}
|
||||
const typeDocJsonPath = (this.typeDocJsonPath =
|
||||
this.typeDocJsonPath ?? path.join(pkgDir, NAME_TYPEDOC_JSON));
|
||||
const relTypeDocJsonPath = relative(this.cwd, typeDocJsonPath);
|
||||
let typeDocJson: TypeDocJson;
|
||||
|
||||
// handle the case where the user passes a JS file as the typedoc config
|
||||
// (which is allowed by TypeDoc)
|
||||
if (typeDocJsonPath.endsWith('.js')) {
|
||||
try {
|
||||
typeDocJson = require(typeDocJsonPath);
|
||||
} catch (err) {
|
||||
throw new DocutilsError(
|
||||
`TypeDoc config at ${relTypeDocJsonPath} threw an exception: ${err}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
typeDocJson = readTypedocJson(typeDocJsonPath);
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
return this.fail(
|
||||
new DocutilsError(`Unparseable ${NAME_TYPEDOC_JSON} at ${relTypeDocJsonPath}: ${e}`)
|
||||
);
|
||||
}
|
||||
return this.fail(
|
||||
new DocutilsError(
|
||||
`Missing ${NAME_TYPEDOC_JSON} at ${relTypeDocJsonPath}; "${NAME_BIN} init" can help`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!typeDocJson.out) {
|
||||
return this.fail(
|
||||
new DocutilsError(
|
||||
`Missing "out" property in ${relTypeDocJsonPath}; path "${DEFAULT_REL_TYPEDOC_OUT_PATH}" is recommended`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
this.ok('TypeDoc config OK');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that TypeScript is installed, runnable, the correct version, and a parseable `tsconfig.json` exists.
|
||||
*/
|
||||
@@ -642,7 +505,7 @@ export class DocutilsValidator extends EventEmitter {
|
||||
typeScriptVersion = match[1];
|
||||
} else {
|
||||
return this.fail(
|
||||
`Could not parse TypeScript version from "tsc --version"; output was:\n ${rawTypeScriptVersion}`
|
||||
`Could not parse TypeScript version from "tsc --version"; output was:\n ${rawTypeScriptVersion}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -650,13 +513,13 @@ export class DocutilsValidator extends EventEmitter {
|
||||
|
||||
if (!reqdTypeScriptVersion) {
|
||||
throw new DocutilsError(
|
||||
`Could not find a dep for ${NAME_TYPESCRIPT} in ${NAME_PACKAGE_JSON}. This is a bug.`
|
||||
`Could not find a dep for ${NAME_TYPESCRIPT} in ${NAME_PACKAGE_JSON}. This is a bug.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!satisfies(typeScriptVersion, reqdTypeScriptVersion)) {
|
||||
return this.fail(
|
||||
`Found TypeScript version ${typeScriptVersion}, but ${reqdTypeScriptVersion} is required`
|
||||
`Found TypeScript version ${typeScriptVersion}, but ${reqdTypeScriptVersion} is required`,
|
||||
);
|
||||
}
|
||||
this.ok('TypeScript install OK');
|
||||
@@ -678,13 +541,13 @@ export class DocutilsValidator extends EventEmitter {
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
return this.fail(
|
||||
new DocutilsError(`Unparseable ${NAME_TSCONFIG_JSON} at ${relTsconfigJsonPath}: ${e}`)
|
||||
new DocutilsError(`Unparseable ${NAME_TSCONFIG_JSON} at ${relTsconfigJsonPath}: ${e}`),
|
||||
);
|
||||
}
|
||||
return this.fail(
|
||||
new DocutilsError(
|
||||
`Missing ${NAME_TSCONFIG_JSON} at ${relTsconfigJsonPath}; "${NAME_BIN} init" can help`
|
||||
)
|
||||
`Missing ${NAME_TSCONFIG_JSON} at ${relTsconfigJsonPath}; "${NAME_BIN} init" can help`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -725,18 +588,6 @@ export interface DocutilsValidatorOpts {
|
||||
* Path to `tsconfig.json`
|
||||
*/
|
||||
tsconfigJson?: string;
|
||||
/**
|
||||
* If `true`, run TypeDoc validation
|
||||
*/
|
||||
typedoc?: boolean;
|
||||
/**
|
||||
* Path to `typedoc` executable
|
||||
*/
|
||||
typedocPath?: string;
|
||||
/**
|
||||
* Path to `typedoc.json`
|
||||
*/
|
||||
typedocJson?: string;
|
||||
/**
|
||||
* If `true`, run TypeScript validation
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@appium/docutils",
|
||||
"version": "0.4.13",
|
||||
"version": "1.0.0",
|
||||
"description": "Documentation generation utilities for Appium and related projects",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -38,7 +38,6 @@
|
||||
"build",
|
||||
"tsconfig.json",
|
||||
"!build/tsconfig.tsbuildinfo",
|
||||
"typedoc-shared.json",
|
||||
"requirements.txt"
|
||||
],
|
||||
"scripts": {
|
||||
@@ -51,14 +50,12 @@
|
||||
"dependencies": {
|
||||
"@appium/support": "^4.1.10",
|
||||
"@appium/tsconfig": "^0.x",
|
||||
"@appium/typedoc-plugin-appium": "^0.x",
|
||||
"@sliphua/lilconfig-ts-loader": "3.2.2",
|
||||
"@types/which": "3.0.1",
|
||||
"chalk": "4.1.2",
|
||||
"consola": "2.15.3",
|
||||
"diff": "5.1.0",
|
||||
"figures": "3.2.0",
|
||||
"find-up": "5.0.0",
|
||||
"json5": "2.2.3",
|
||||
"lilconfig": "2.1.0",
|
||||
"lodash": "4.17.21",
|
||||
@@ -69,9 +66,6 @@
|
||||
"source-map-support": "0.5.21",
|
||||
"teen_process": "2.0.101",
|
||||
"type-fest": "3.13.1",
|
||||
"typedoc": "0.23.28",
|
||||
"typedoc-plugin-markdown": "3.14.0",
|
||||
"typedoc-plugin-resolve-crossmodule-references": "0.3.3",
|
||||
"typescript": "5.0.4",
|
||||
"yaml": "2.3.4",
|
||||
"yargs": "17.7.2",
|
||||
|
||||
@@ -65,8 +65,5 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"typedoc": {
|
||||
"entryPoint": "./lib/index.js"
|
||||
},
|
||||
"gitHead": "8480a85ce2fa466360e0fb1a7f66628331907f02"
|
||||
}
|
||||
|
||||
@@ -59,8 +59,5 @@
|
||||
"pluginName": "execute-driver",
|
||||
"mainClass": "ExecuteDriverPlugin"
|
||||
},
|
||||
"gitHead": "8480a85ce2fa466360e0fb1a7f66628331907f02",
|
||||
"typedoc": {
|
||||
"entryPoint": "./lib/plugin.js"
|
||||
}
|
||||
"gitHead": "8480a85ce2fa466360e0fb1a7f66628331907f02"
|
||||
}
|
||||
|
||||
@@ -79,9 +79,6 @@
|
||||
"fake-stdin": "./build/lib/scripts/fake-stdin.js"
|
||||
}
|
||||
},
|
||||
"typedoc": {
|
||||
"entryPoint": "./lib/index.js"
|
||||
},
|
||||
"types": "./build/lib/index.d.ts",
|
||||
"gitHead": "8480a85ce2fa466360e0fb1a7f66628331907f02"
|
||||
}
|
||||
|
||||
@@ -67,8 +67,5 @@
|
||||
"gitHead": "8480a85ce2fa466360e0fb1a7f66628331907f02",
|
||||
"tags": [
|
||||
"appium"
|
||||
],
|
||||
"typedoc": {
|
||||
"entryPoint": "./lib/plugin.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -60,8 +60,5 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "84b211330dc84849af9cc1f3a5c5ad32e15b2e72",
|
||||
"typedoc": {
|
||||
"entryPoint": "./lib/plugin.js"
|
||||
}
|
||||
"gitHead": "84b211330dc84849af9cc1f3a5c5ad32e15b2e72"
|
||||
}
|
||||
|
||||
@@ -55,8 +55,5 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"typedoc": {
|
||||
"entryPoint": "./lib/index.js"
|
||||
},
|
||||
"gitHead": "84b211330dc84849af9cc1f3a5c5ad32e15b2e72"
|
||||
}
|
||||
|
||||
@@ -58,8 +58,5 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"typedoc": {
|
||||
"entryPoint": "./lib/harness.js"
|
||||
},
|
||||
"gitHead": "8480a85ce2fa466360e0fb1a7f66628331907f02"
|
||||
}
|
||||
|
||||
@@ -60,8 +60,5 @@
|
||||
"gitHead": "84b211330dc84849af9cc1f3a5c5ad32e15b2e72",
|
||||
"tags": [
|
||||
"appium"
|
||||
],
|
||||
"typedoc": {
|
||||
"entryPoint": "./lib/plugin.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -49,8 +49,5 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"typedoc": {
|
||||
"entryPoint": "./lib/index.js"
|
||||
},
|
||||
"gitHead": "84b211330dc84849af9cc1f3a5c5ad32e15b2e72"
|
||||
}
|
||||
|
||||
@@ -105,8 +105,5 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "8480a85ce2fa466360e0fb1a7f66628331907f02",
|
||||
"typedoc": {
|
||||
"entryPoint": "./lib/index.js"
|
||||
}
|
||||
"gitHead": "8480a85ce2fa466360e0fb1a7f66628331907f02"
|
||||
}
|
||||
|
||||
@@ -60,8 +60,5 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"typedoc": {
|
||||
"entryPoint": "./lib/index.js"
|
||||
},
|
||||
"gitHead": "8480a85ce2fa466360e0fb1a7f66628331907f02"
|
||||
}
|
||||
|
||||
@@ -1,247 +0,0 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.6.6](https://github.com/appium/appium/compare/@appium/typedoc-plugin-appium@0.6.5...@appium/typedoc-plugin-appium@0.6.6) (2023-08-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **typedoc-plugin-appium:** update dependency handlebars to v4.7.8 ([#18947](https://github.com/appium/appium/issues/18947)) ([a888e26](https://github.com/appium/appium/commit/a888e263317839cecbc2cb42e183d190e136624d))
|
||||
* **types:** update dependency type-fest to v3.13.1 ([fb34ab9](https://github.com/appium/appium/commit/fb34ab917216121d2b554677a12f07a03393d218))
|
||||
|
||||
|
||||
|
||||
## [0.6.5](https://github.com/appium/appium/compare/@appium/typedoc-plugin-appium@0.6.4...@appium/typedoc-plugin-appium@0.6.5) (2023-06-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **types:** update dependency type-fest to v3.11.0 ([19277f6](https://github.com/appium/appium/commit/19277f6e14a56e52b4669d633e148ad4a3da2c7a))
|
||||
* **types:** update dependency type-fest to v3.11.1 ([56499eb](https://github.com/appium/appium/commit/56499eb997b551739bed628f057de7987674ea7f))
|
||||
|
||||
|
||||
|
||||
## [0.6.4](https://github.com/appium/appium/compare/@appium/typedoc-plugin-appium@0.6.3...@appium/typedoc-plugin-appium@0.6.4) (2023-05-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **typedoc-plugin-appium:** don't prefix execute methods with "Script: " ([#18618](https://github.com/appium/appium/issues/18618)) ([40e5199](https://github.com/appium/appium/commit/40e5199caa0bb6166af5af714f03d26fc5936b97))
|
||||
* **types:** update dependency type-fest to v3.10.0 ([3c4d3ac](https://github.com/appium/appium/commit/3c4d3acc09d2ca1ed74dc77c18c62482e4c70239))
|
||||
* **types:** update dependency type-fest to v3.9.0 ([94a207f](https://github.com/appium/appium/commit/94a207fc9718068f3657c51cc8be0ef682f16b11))
|
||||
|
||||
|
||||
|
||||
## [0.6.3](https://github.com/appium/appium/compare/@appium/typedoc-plugin-appium@0.6.2...@appium/typedoc-plugin-appium@0.6.3) (2023-04-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **typedoc-plugin-appium:** fix parameter display for execute methods ([5a70d7c](https://github.com/appium/appium/commit/5a70d7ce78ce12ef8b9209a2a17c52b02ac0fc80))
|
||||
|
||||
|
||||
|
||||
## [0.6.2](https://github.com/appium/appium/compare/@appium/typedoc-plugin-appium@0.6.1...@appium/typedoc-plugin-appium@0.6.2) (2023-04-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **typedoc-plugin-appium:** restrict peer deps ([63e28a0](https://github.com/appium/appium/commit/63e28a014cf9af8bba9595d83f513bfb7026ae9a))
|
||||
|
||||
|
||||
|
||||
## [0.6.1](https://github.com/appium/appium/compare/@appium/typedoc-plugin-appium@0.6.0...@appium/typedoc-plugin-appium@0.6.1) (2023-04-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **types:** update dependency type-fest to v3.8.0 ([d6c42e9](https://github.com/appium/appium/commit/d6c42e99c08efce0b34796d5982ce379fca044d3))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [0.6.0](https://github.com/appium/appium/compare/@appium/typedoc-plugin-appium@0.5.4...@appium/typedoc-plugin-appium@0.6.0) (2023-04-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **typedoc-plugin-appium:** enable comment derivation via reflection types ([b808dc8](https://github.com/appium/appium/commit/b808dc866d6587269d8adbe8d354471b3a1ba6de))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.5.4](https://github.com/appium/appium/compare/@appium/typedoc-plugin-appium@0.5.3...@appium/typedoc-plugin-appium@0.5.4) (2023-04-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **typedoc-plugin-appium:** allow ts v5 as peer dep ([822206b](https://github.com/appium/appium/commit/822206be204599390c2f97b9b8d6c618ff53cb89))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.5.3](https://github.com/appium/appium/compare/@appium/typedoc-plugin-appium@0.5.2...@appium/typedoc-plugin-appium@0.5.3) (2023-04-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **types:** update dependency type-fest to v3.7.2 ([5580539](https://github.com/appium/appium/commit/55805390b5a0c6aa718bb357b30f66651f3db281))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.5.2](https://github.com/appium/appium/compare/@appium/typedoc-plugin-appium@0.5.1...@appium/typedoc-plugin-appium@0.5.2) (2023-03-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **types:** update dependency type-fest to v3.7.0 ([6912fa1](https://github.com/appium/appium/commit/6912fa14f2a7d338f17e1bed060e959de7aba1d6))
|
||||
* **types:** update dependency type-fest to v3.7.1 ([bc860c7](https://github.com/appium/appium/commit/bc860c733a73760f0c42cbfb384e04d50c376d5e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.5.1](https://github.com/appium/appium/compare/@appium/typedoc-plugin-appium@0.5.0...@appium/typedoc-plugin-appium@0.5.1) (2023-03-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **typedoc-plugin-appium:** replace void responses with 'null' ([cd76622](https://github.com/appium/appium/commit/cd76622aa9931eed058d32f0d3c51327ac43f15c)), closes [#18269](https://github.com/appium/appium/issues/18269)
|
||||
* **types:** update dependency type-fest to v3.6.1 ([471a4b5](https://github.com/appium/appium/commit/471a4b57e622ff077d59f577a78341268700c48d))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **typedoc-plugin-appium:** add test-related types to tsconfig ([3cd22ef](https://github.com/appium/appium/commit/3cd22ef4fb1041f4d43dd11394c3e7f800139647))
|
||||
* **typedoc-plugin-appium:** better top-level header for extension reflection ([db4a1d6](https://github.com/appium/appium/commit/db4a1d6489397b079ab99dcf00b7b1cd521079d4))
|
||||
* **typedoc-plugin-appium:** command headings should be the command name ([7495693](https://github.com/appium/appium/commit/749569323f791ba942116ca90a884e93cc58fa93))
|
||||
* **typedoc-plugin-appium:** fix cloneComment() and add a test ([9b8e9a1](https://github.com/appium/appium/commit/9b8e9a1a36cbe5b7fd496b5c4f912334605cc21a))
|
||||
* **typedoc-plugin-appium:** fix type and test problems ([fee07d3](https://github.com/appium/appium/commit/fee07d38c35087752c84616ff97d0646476c1739))
|
||||
* **typedoc-plugin-appium:** use simple filenames for ExtensionReflection objects ([6c26b97](https://github.com/appium/appium/commit/6c26b971246de09ce07b85a34122273f4fad3125)), closes [#18110](https://github.com/appium/appium/issues/18110)
|
||||
* **types:** update dependency type-fest to v3.5.4 ([cfb5297](https://github.com/appium/appium/commit/cfb529772cff3a2b7e9ff36e12444b603906a769))
|
||||
* **types:** update dependency type-fest to v3.5.5 ([9bf320c](https://github.com/appium/appium/commit/9bf320c87ccf574f933a8247a851b4f848c39fa1))
|
||||
* **types:** update dependency type-fest to v3.5.6 ([775c990](https://github.com/appium/appium/commit/775c990f9d4176e78936a071968a788e19048519))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **typedoc-appium-converter:** add packageTitles option and populate extension titles ([393f9ef](https://github.com/appium/appium/commit/393f9ef32a2f2e5d8ff1f13092c2035704844215))
|
||||
* **typedoc-plugin-appium:** display examples in command documentation ([c829708](https://github.com/appium/appium/commit/c8297088bf17fbc30f7b5fbda5b7575523c2f131))
|
||||
* **typedoc-plugin-appium:** implemented outputBuiltinCommands ([6bb06e2](https://github.com/appium/appium/commit/6bb06e2c19ed6574567b0d66d607ab6eb03e9084))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.3.4](https://github.com/appium/appium/compare/@appium/typedoc-plugin-appium@0.3.3...@appium/typedoc-plugin-appium@0.3.4) (2023-01-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **typedoc-plugin-appium:** fix keywords for typedoc plugin autodetection ([ed1edab](https://github.com/appium/appium/commit/ed1edab2098eed8f36a23419a2cc91c699756ed7))
|
||||
* **typedoc-plugin-appium:** non-appium theme should not cause a fatal error ([ec48c9c](https://github.com/appium/appium/commit/ec48c9cea7949d5e05ab0f748265345d84007283))
|
||||
* **typedoc-plugin-appium:** reduce severity of "missing method" error ([d0e1cb2](https://github.com/appium/appium/commit/d0e1cb2f65d5da11dff251e445861a3d74585bb6))
|
||||
* **types:** update dependency type-fest to v3.5.2 ([64fd8ce](https://github.com/appium/appium/commit/64fd8ce94018b0bb7ccb2baade8d525703f41c45))
|
||||
* **types:** update dependency type-fest to v3.5.3 ([6c4ba8c](https://github.com/appium/appium/commit/6c4ba8caa508840640f05eea1ab41ecb290312aa))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.3.3](https://github.com/appium/appium/compare/@appium/typedoc-plugin-appium@0.3.2...@appium/typedoc-plugin-appium@0.3.3) (2023-01-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/typedoc-plugin-appium
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.3.2](https://github.com/appium/appium/compare/@appium/typedoc-plugin-appium@0.3.1...@appium/typedoc-plugin-appium@0.3.2) (2023-01-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/typedoc-plugin-appium
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.3.1](https://github.com/appium/appium/compare/@appium/typedoc-plugin-appium@0.3.0...@appium/typedoc-plugin-appium@0.3.1) (2023-01-13)
|
||||
|
||||
**Note:** Version bump only for package @appium/typedoc-plugin-appium
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [0.3.0](https://github.com/appium/appium/compare/@appium/typedoc-plugin-appium@0.2.1...@appium/typedoc-plugin-appium@0.3.0) (2023-01-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **images-plugin,universal-xml-plugin,typedoc-plugin-appium:** add missing lodash dependency ([696c94f](https://github.com/appium/appium/commit/696c94f1abf1da15fb9e3a4d60b95cd2d69d9e7c))
|
||||
* **typedoc-plugin-appium:** add peer dep of appium ([0aac077](https://github.com/appium/appium/commit/0aac077e30239dd5e8397a169b61ae6776ce7a64))
|
||||
* **typedoc-plugin-appium:** allow commands as properties referencing async methods ([9d5a737](https://github.com/appium/appium/commit/9d5a737daad15dd8e1d4a8ed23cb9c3e5797a41f))
|
||||
* **typedoc-plugin-appium:** bad directory in repository url ([5ef4ae9](https://github.com/appium/appium/commit/5ef4ae9206c25adf3b3bc825ee81135142ab4166))
|
||||
* **types:** update dependency type-fest to v3.5.0 ([8c8bfe8](https://github.com/appium/appium/commit/8c8bfe824dbe062e24cfe9fc6e1afa2f68cc6e4c))
|
||||
* **types:** update dependency type-fest to v3.5.1 ([4b5ab4d](https://github.com/appium/appium/commit/4b5ab4da7be925d0592c18e8f46a9ce30fbddf8e))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **typedoc-appium-plugin:** implement cross-referencing of methods ([8b33414](https://github.com/appium/appium/commit/8b334149018f7d49448da9e7982356c72bcd468e))
|
||||
* **typedoc-plugin-appium:** add options and 3p support ([0ca54b7](https://github.com/appium/appium/commit/0ca54b79949126495e5cb702d4f5c0b341f1e25d))
|
||||
* **typedoc-plugin-appium:** add plugin support ([b036dde](https://github.com/appium/appium/commit/b036dde5dd5c0ab63a6a4be8cf60017d876edfda))
|
||||
* **typedoc-plugin-appium:** display response type ([2a12b88](https://github.com/appium/appium/commit/2a12b88b2f669766ff47e41eb43fccf6f67195b7))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.2.1](https://github.com/appium/appium/compare/@appium/typedoc-plugin-appium@0.2.0...@appium/typedoc-plugin-appium@0.2.1) (2022-12-21)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **types:** update dependency type-fest to v3.4.0 ([37f71c3](https://github.com/appium/appium/commit/37f71c327a7c1a6d882b5198af6fedc9e8d51496))
|
||||
|
||||
# 0.2.0 (2022-12-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **typedoc-plugin-appium:** fix external driver conversion ([73299a0](https://github.com/appium/appium/commit/73299a00261475ed4062dfeb15758344b74269a3))
|
||||
- **typedoc-plugin-appium:** update dependency type-fest to v3.3.0 ([3bf9358](https://github.com/appium/appium/commit/3bf93582912198b67ce940aeb26b09d35612a2d0))
|
||||
- **typedoc-plugin-appium:** update dependency typedoc-plugin-markdown to v3.14.0 ([91f8814](https://github.com/appium/appium/commit/91f881410c9fb27eccf127ee18ae89425ca8485a))
|
||||
|
||||
### Features
|
||||
|
||||
- **typedoc-plugin-appium:** create appium typedoc plugin ([be7a479](https://github.com/appium/appium/commit/be7a479b4bcba26e9ffc4e214acf2099971224ec))
|
||||
@@ -1,82 +0,0 @@
|
||||
# `@appium/typedoc-plugin-appium`
|
||||
|
||||
> TypeDoc plugin for [Appium](https://appium.io) & its extensions
|
||||
|
||||
## Overview
|
||||
|
||||
This package leverages [TypeDoc](https://typedoc.org) to generate command documentation (HTTP endpoints, payload information, etc.) for Appium v2+ drivers and plugins.
|
||||
|
||||
## Important Note
|
||||
|
||||
_If you are an Appium extension author and just want to build HTML docs_, **don't install this directly**--instead, install [`@appium/docutils`](https://github.com/appium/appium/tree/master/packages/docutils), which is a higher-level package that configures everything for you.
|
||||
|
||||
If you _only_ want to build markdown docs for your extension, then you can use this package directly.
|
||||
|
||||
## Installation
|
||||
|
||||
`npm` v8+ is required to install this package.
|
||||
|
||||
```bash
|
||||
npm install @appium/typedoc-plugin-appium --save-dev
|
||||
```
|
||||
|
||||
The above command installs the plugin as well as all necessary peer dependencies. See [`package.json`](https://github.com/appium/appium/blob/master/packages/docutils/package.json) for the full list of dependencies.
|
||||
|
||||
## Usage
|
||||
|
||||
TypeDoc is configured via a `typedoc.json` or `typedoc.js` file ([read the docs](https://typedoc.org/guides/options/) for more information).
|
||||
|
||||
An Appium extension author wishing to generate markdown documentation for their extension will need to create a `typedoc.json`. At minimum, it should contain:
|
||||
|
||||
```json
|
||||
{
|
||||
"entryPointStrategy": "packages",
|
||||
"entryPoints": ["."],
|
||||
"name": "<name of extension>",
|
||||
"theme": "appium",
|
||||
"out": "<path to output directory>"
|
||||
}
|
||||
```
|
||||
|
||||
Once this file is created, you can run `typedoc` to generate the documentation, and it will be output into the `out` directory as configured above.
|
||||
|
||||
## Options
|
||||
|
||||
This plugin supports all of the options from [typedoc-plugin-markdown](https://npm.im/typedoc-plugin-markdown), as well as the following:
|
||||
|
||||
### `outputModules`
|
||||
|
||||
`boolean` - Output module, class, interface, and other type information (the usual TypeDoc output) in addition to command documentation. This is needed for full documentation of types. _Default value: `true`_
|
||||
|
||||
### `outputBuiltinCommands`
|
||||
|
||||
`boolean` - Outputs _all_ commands and types from Appium builtins--not just your extension. This is intended to be used by Appium itself. _Default value: `false`_
|
||||
|
||||
### `packageTitles`
|
||||
|
||||
`Array<{name: string, title: string}>`: An array of objects containing module name `name` and display name `title`. By default, the module name is used for the title; use this to override that behavior. _Default value: `undefined`_
|
||||
|
||||
### `commandsDir`
|
||||
|
||||
`string` - The name of the "commands" directory relative to the TypeDoc output directory (`out`). _Default value: `commands`_
|
||||
|
||||
### `forceBreadcrumbs`
|
||||
|
||||
`boolean` - Forces breadcrumbs to be output; overrides `hideBreadcrumbs` from `typedoc-plugin-markdown`. _Default value: `false`_
|
||||
|
||||
## Development
|
||||
|
||||
This packages uses snapshot tests to assert the generated markdown is correct. If you have made changes which affect the plugin's output, you will need to update the snapshots.
|
||||
|
||||
To update the snapshots, execute:
|
||||
|
||||
```bash
|
||||
UPDATE_SNAPSHOT=1 npm run test:e2e
|
||||
```
|
||||
|
||||
This will (likely) modify the snapshots in your working copy, so you will then need to commit them.
|
||||
|
||||
## License
|
||||
|
||||
Copyright © 2022 OpenJS Foundation. Licensed Apache-2.0
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
module.exports = require('./build/lib/index.js');
|
||||
@@ -1,9 +0,0 @@
|
||||
import {Context} from 'typedoc';
|
||||
import {AppiumPluginLogger} from '../logger';
|
||||
|
||||
export abstract class BaseConverter<Result> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
constructor(protected ctx: Context, protected log: AppiumPluginLogger, ...args: any[]) {}
|
||||
|
||||
abstract convert(): Result;
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
/**
|
||||
* A thing that creates {@linkcode typedoc#DeclarationReflection} instances from parsed
|
||||
* command & execute method data.
|
||||
* @module
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import pluralize from 'pluralize';
|
||||
import {ContainerReflection, Context, DeclarationReflection, ProjectReflection} from 'typedoc';
|
||||
import {isParentReflection} from '../guards';
|
||||
import {AppiumPluginLogger} from '../logger';
|
||||
import {
|
||||
AppiumPluginReflectionKind,
|
||||
CommandData,
|
||||
CommandReflection,
|
||||
ExecMethodData,
|
||||
ExtensionReflection,
|
||||
ModuleCommands,
|
||||
ProjectCommands,
|
||||
Route,
|
||||
} from '../model';
|
||||
import {PackageTitle} from '../options/declarations';
|
||||
import {NAME_BUILTIN_COMMAND_MODULE} from './builtin-method-map';
|
||||
import {findChildByNameAndGuard} from './utils';
|
||||
|
||||
/**
|
||||
* Creates and adds a child {@linkcode CommandReflection} to this reflection
|
||||
*
|
||||
* During "normal" usage of TypeDoc, one would call
|
||||
* `createDeclarationReflection()`. But since we've subclassed
|
||||
* `DeclarationReflection`, we cannot call it directly. It doesn't seem to do
|
||||
* anything useful besides instantiation then delegating to
|
||||
* `postReflectionCreation()`; so we just need to call it directly.
|
||||
*
|
||||
* Finally, we call `finalizeDeclarationReflection()` which I think just fires
|
||||
* some events for other plugins to potentially use.
|
||||
* @param log Logger
|
||||
* @param data Command reference
|
||||
* @param route Route
|
||||
* @param parent Commands reflection
|
||||
* @internal
|
||||
*/
|
||||
export function createCommandReflection(
|
||||
ctx: Context,
|
||||
data: CommandData | ExecMethodData,
|
||||
parent: ExtensionReflection,
|
||||
route?: Route
|
||||
): void {
|
||||
const commandRefl = new CommandReflection(data, parent, route);
|
||||
// yes, the `undefined`s are needed
|
||||
ctx.postReflectionCreation(commandRefl, undefined, undefined);
|
||||
ctx.finalizeDeclarationReflection(commandRefl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@linkcode ExtensionReflection} and all {@linkcode CommandReflection} children within
|
||||
* it.
|
||||
*
|
||||
* Note that the return value is mainly for informational purposes, since this method mutates
|
||||
* TypeDoc's state.
|
||||
* @param log - Logger
|
||||
* @param ctx - Context
|
||||
* @param name - Name of module containing commands
|
||||
* @param moduleCmds - Command information for `module`
|
||||
* @internal
|
||||
*/
|
||||
export function createExtensionReflection(
|
||||
log: AppiumPluginLogger,
|
||||
ctx: Context,
|
||||
name: string,
|
||||
moduleCmds: ModuleCommands
|
||||
): ExtensionReflection {
|
||||
const packageTitles = ctx.converter.application.options.getValue(
|
||||
'packageTitles'
|
||||
) as PackageTitle[];
|
||||
log.verbose(`Value of packageTitles: %O`, packageTitles);
|
||||
// TODO: parent.name may not be right here
|
||||
const extRefl = new ExtensionReflection(
|
||||
name,
|
||||
ctx.project,
|
||||
moduleCmds,
|
||||
packageTitles.find((p) => p.name === name)?.title
|
||||
);
|
||||
/**
|
||||
* See note in {@link createCommandReflection} above about this call
|
||||
*/
|
||||
ctx.postReflectionCreation(extRefl, undefined, undefined);
|
||||
|
||||
const parentCtx = ctx.withScope(extRefl);
|
||||
const {routeMap: routeMap, execMethodDataSet: execCommandsData} = moduleCmds;
|
||||
|
||||
for (const [route, commandSet] of routeMap) {
|
||||
for (const data of commandSet) {
|
||||
createCommandReflection(parentCtx, data, extRefl, route);
|
||||
}
|
||||
}
|
||||
|
||||
for (const data of execCommandsData) {
|
||||
createCommandReflection(parentCtx, data, extRefl);
|
||||
}
|
||||
|
||||
ctx.finalizeDeclarationReflection(extRefl);
|
||||
return extRefl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates custom {@linkcode typedoc#DeclarationReflection}s from parsed command & execute method data.
|
||||
*
|
||||
* These instances are added to the {@linkcode Context} object itself; this mutates TypeDoc's internal state. Nothing is returned.
|
||||
* @param ctx TypeDoc Context
|
||||
* @param parentLog Plugin logger
|
||||
* @param projectCmds Command info from converter; a map of parent reflections to parsed data
|
||||
* @returns List of {@linkcode ExtensionReflection} instances
|
||||
*/
|
||||
export function createReflections(
|
||||
ctx: Context,
|
||||
parentLog: AppiumPluginLogger,
|
||||
projectCmds: ProjectCommands
|
||||
): ExtensionReflection[] {
|
||||
const log = parentLog.createChildLogger('builder');
|
||||
const {project} = ctx;
|
||||
|
||||
if (!projectCmds.size) {
|
||||
log.error('No reflections to create; nothing to do.');
|
||||
return [];
|
||||
}
|
||||
|
||||
return [...projectCmds.entries()].map(([parentName, parentCmds]) => {
|
||||
const parentRefl =
|
||||
project.name === parentName
|
||||
? project
|
||||
: findChildByNameAndGuard(project, parentName, isParentReflection)!;
|
||||
|
||||
const cmdsRefl = createExtensionReflection(log, ctx, parentRefl.name, parentCmds);
|
||||
|
||||
log.info(
|
||||
'(%s) Created %d new command %s',
|
||||
parentName,
|
||||
cmdsRefl.children?.length ?? 0,
|
||||
pluralize('reflection', cmdsRefl.children?.length ?? 0)
|
||||
);
|
||||
return cmdsRefl;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any reflection _not_ created by this plugin from the TypeDoc refl _except_ those
|
||||
* created by this plugin.
|
||||
* @param project - Current TypeDoc project
|
||||
* @param refl - A {@linkcode ContainerReflection} to remove children from; defaults to `project`
|
||||
* @returns A set of removed {@linkcode DeclarationReflection DeclarationReflections}
|
||||
*/
|
||||
export function omitDefaultReflections(
|
||||
project: ProjectReflection,
|
||||
refl: ContainerReflection = project
|
||||
): Set<DeclarationReflection> {
|
||||
const removed = new Set<DeclarationReflection>();
|
||||
for (const childRefl of refl.getChildrenByKind(~(AppiumPluginReflectionKind.Extension as any))) {
|
||||
project.removeReflection(childRefl);
|
||||
removed.add(childRefl);
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes extension reflection(s) which are part of Appium itself. This is desirable for most
|
||||
* extension authors.
|
||||
* @param project - Current TypeDoc project
|
||||
* @param refl - A {@linkcode ContainerReflection} to remove children from; defaults to `project`
|
||||
* @returns A set of removed {@linkcode DeclarationReflection}s
|
||||
*/
|
||||
export function omitBuiltinReflections(
|
||||
project: ProjectReflection,
|
||||
refl: ContainerReflection = project
|
||||
) {
|
||||
const removed = new Set<DeclarationReflection>();
|
||||
|
||||
const extRefls = refl.getChildrenByKind(
|
||||
AppiumPluginReflectionKind.Extension as any
|
||||
) as DeclarationReflection[];
|
||||
const builtinRefl = _.find(extRefls, {name: NAME_BUILTIN_COMMAND_MODULE});
|
||||
if (!builtinRefl) {
|
||||
throw new Error(`Could not find builtin commands reflection "${NAME_BUILTIN_COMMAND_MODULE}"`);
|
||||
}
|
||||
project.removeReflection(builtinRefl);
|
||||
removed.add(builtinRefl);
|
||||
|
||||
return removed;
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import pluralize from 'pluralize';
|
||||
import {Context} from 'typedoc';
|
||||
import {isAppiumTypesReflection, isExternalDriverDeclarationReflection} from '../guards';
|
||||
import {AppiumPluginLogger} from '../logger';
|
||||
import {BaseConverter} from './base-converter';
|
||||
import {AppiumTypesReflection, KnownMethods} from './types';
|
||||
import {findParentReflectionByName, findCommandMethodsInReflection} from './utils';
|
||||
|
||||
/**
|
||||
* Name of the module containing `ExternalDriver`
|
||||
*/
|
||||
export const NAME_TYPES_MODULE = '@appium/types';
|
||||
|
||||
/**
|
||||
* Name of `ExternalDriver` interface
|
||||
*/
|
||||
export const NAME_EXTERNAL_DRIVER = 'ExternalDriver';
|
||||
|
||||
/**
|
||||
* Converts `@appium/types` into a `KnownMethods`, if it can.
|
||||
*/
|
||||
export class BuiltinExternalDriverConverter extends BaseConverter<KnownMethods> {
|
||||
/**
|
||||
* Creates a child logger for this instance
|
||||
* @param ctx Typedoc Context
|
||||
* @param log Logger
|
||||
*/
|
||||
constructor(protected ctx: Context, log: AppiumPluginLogger) {
|
||||
super(ctx, log.createChildLogger(NAME_TYPES_MODULE));
|
||||
}
|
||||
|
||||
#convertMethodDeclarations(refl: AppiumTypesReflection): KnownMethods {
|
||||
const externalDriverRefl = refl.getChildByName(NAME_EXTERNAL_DRIVER);
|
||||
let methods: KnownMethods = new Map();
|
||||
|
||||
if (!isExternalDriverDeclarationReflection(externalDriverRefl)) {
|
||||
this.log.error('Could not find %s', NAME_EXTERNAL_DRIVER);
|
||||
return methods;
|
||||
}
|
||||
|
||||
methods = findCommandMethodsInReflection(externalDriverRefl);
|
||||
|
||||
if (!methods.size) {
|
||||
this.log.error('(%s) No methods found! This is a bug.', NAME_EXTERNAL_DRIVER);
|
||||
return methods;
|
||||
}
|
||||
|
||||
this.log.verbose(
|
||||
'(%s) Done; found %s',
|
||||
NAME_EXTERNAL_DRIVER,
|
||||
pluralize('builtin command method', methods.size, true)
|
||||
);
|
||||
|
||||
return methods;
|
||||
}
|
||||
|
||||
public override convert(): KnownMethods {
|
||||
const {project} = this.ctx;
|
||||
const typesModule = findParentReflectionByName(project, NAME_TYPES_MODULE);
|
||||
if (!isAppiumTypesReflection(typesModule)) {
|
||||
this.log.error('Could not find %s', NAME_TYPES_MODULE);
|
||||
return new Map();
|
||||
}
|
||||
|
||||
this.log.verbose('Found %s; converting', NAME_EXTERNAL_DRIVER);
|
||||
|
||||
return this.#convertMethodDeclarations(typesModule);
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
import {Context} from 'typedoc';
|
||||
import {
|
||||
isBaseDriverDeclarationReflection,
|
||||
isClassDeclarationReflection,
|
||||
isMethodMapDeclarationReflection,
|
||||
} from '../guards';
|
||||
import {AppiumPluginLogger} from '../logger';
|
||||
import {BuiltinCommands} from '../model/builtin-commands';
|
||||
import {BaseConverter} from './base-converter';
|
||||
import {convertMethodMap} from './method-map';
|
||||
import {KnownMethods} from './types';
|
||||
import {
|
||||
findChildByNameAndGuard,
|
||||
findCommandMethodsInReflection,
|
||||
findParentReflectionByName,
|
||||
} from './utils';
|
||||
|
||||
/**
|
||||
* Name of the builtin method map in `@appium/base-driver`
|
||||
*/
|
||||
export const NAME_METHOD_MAP = 'METHOD_MAP';
|
||||
|
||||
export const NAME_BASE_DRIVER_CLASS = 'BaseDriver';
|
||||
|
||||
/**
|
||||
* Name of the module which contains the builtin method map
|
||||
*/
|
||||
export const NAME_BUILTIN_COMMAND_MODULE = '@appium/base-driver';
|
||||
|
||||
export class BuiltinMethodMapConverter extends BaseConverter<BuiltinCommands> {
|
||||
/**
|
||||
* Creates a child logger for this instance
|
||||
* @param ctx Typedoc Context
|
||||
* @param log Logger
|
||||
*/
|
||||
constructor(
|
||||
ctx: Context,
|
||||
log: AppiumPluginLogger,
|
||||
protected readonly knownBuiltinMethods: KnownMethods
|
||||
) {
|
||||
super(ctx, log.createChildLogger(NAME_BUILTIN_COMMAND_MODULE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts `@appium/base-driver` into a `RouteMap`, if it can.
|
||||
*
|
||||
* @returns Object containing a declaration reflection of `@appium/base-driver` and its associated
|
||||
* route map (if found).
|
||||
*/
|
||||
public override convert(): BuiltinCommands {
|
||||
const {project} = this.ctx;
|
||||
const baseDriverModuleRefl = findParentReflectionByName(project, NAME_BUILTIN_COMMAND_MODULE);
|
||||
|
||||
if (!isBaseDriverDeclarationReflection(baseDriverModuleRefl)) {
|
||||
this.log.error('Could not find %s', NAME_BUILTIN_COMMAND_MODULE);
|
||||
return new BuiltinCommands();
|
||||
}
|
||||
|
||||
this.log.verbose('Converting builtin method map');
|
||||
|
||||
// we need base driver class to find methods implemented in it
|
||||
const baseDriverClassRefl = findChildByNameAndGuard(
|
||||
baseDriverModuleRefl,
|
||||
NAME_BASE_DRIVER_CLASS,
|
||||
isClassDeclarationReflection
|
||||
);
|
||||
if (!baseDriverClassRefl) {
|
||||
this.log.error('Could not find module %s', NAME_BUILTIN_COMMAND_MODULE);
|
||||
return new BuiltinCommands();
|
||||
}
|
||||
|
||||
const methodMap = baseDriverModuleRefl.getChildByName(NAME_METHOD_MAP);
|
||||
|
||||
if (!isMethodMapDeclarationReflection(methodMap)) {
|
||||
this.log.error('Could not find %s in %s', NAME_METHOD_MAP, NAME_BUILTIN_COMMAND_MODULE);
|
||||
return new BuiltinCommands();
|
||||
}
|
||||
|
||||
const knownClassMethods = findCommandMethodsInReflection(baseDriverClassRefl);
|
||||
const baseDriverRoutes = convertMethodMap({
|
||||
ctx: this.ctx,
|
||||
log: this.log,
|
||||
methodMapRefl: methodMap,
|
||||
parentRefl: baseDriverModuleRefl,
|
||||
knownClassMethods,
|
||||
knownBuiltinMethods: this.knownBuiltinMethods,
|
||||
});
|
||||
|
||||
if (!baseDriverRoutes.size) {
|
||||
this.log.error('Could not find any commands in %s', NAME_BUILTIN_COMMAND_MODULE);
|
||||
return new BuiltinCommands();
|
||||
}
|
||||
|
||||
return new BuiltinCommands(baseDriverRoutes, baseDriverModuleRefl);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
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 which was directly provided
|
||||
*/
|
||||
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};
|
||||
@@ -1,191 +0,0 @@
|
||||
/**
|
||||
* 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(tags[0]);
|
||||
} 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};
|
||||
};
|
||||
@@ -1,88 +0,0 @@
|
||||
import {Comment} from 'typedoc';
|
||||
import {isDeclarationReflection, isReflectionType} 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 or from a `ReflectionType`'s declaration)
|
||||
*/
|
||||
getter: ({refl}) => {
|
||||
if (isDeclarationReflection(refl)) {
|
||||
let comment = refl.getAllSignatures().find((sig) => sig.comment?.summary)?.comment;
|
||||
if (comment) {
|
||||
return comment;
|
||||
}
|
||||
if (isReflectionType(refl.type)) {
|
||||
comment = refl.type.declaration
|
||||
.getAllSignatures()
|
||||
.find((sig) => sig.comment?.summary)?.comment;
|
||||
}
|
||||
return 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}),
|
||||
commentSource,
|
||||
}))
|
||||
.find(({comment}) => Boolean(comment))?.comment;
|
||||
if (comment) {
|
||||
knownDeclarationRefComments.set(refl.name, comment);
|
||||
}
|
||||
return comment;
|
||||
},
|
||||
commentSource: CommentSource.OtherMethod,
|
||||
},
|
||||
];
|
||||
|
||||
export default MethodCommentFinders;
|
||||
@@ -1,53 +0,0 @@
|
||||
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[]>;
|
||||
@@ -1,48 +0,0 @@
|
||||
/**
|
||||
* 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[]>;
|
||||
@@ -1,31 +0,0 @@
|
||||
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,131 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import {Context, DeclarationReflection, ReflectionKind} from 'typedoc';
|
||||
import {
|
||||
isCommandPropDeclarationReflection,
|
||||
isExecMethodDefParamsPropDeclarationReflection,
|
||||
} from '../guards';
|
||||
import {AppiumPluginLogger} from '../logger';
|
||||
import {ExecMethodData, ExecMethodDataSet, NAME_EXECUTE_ROUTE} from '../model';
|
||||
import {deriveComment} from './comment';
|
||||
import {ExecMethodDeclarationReflection, KnownMethods} from './types';
|
||||
import {
|
||||
convertOptionalCommandParams,
|
||||
convertRequiredCommandParams,
|
||||
filterChildrenByKind,
|
||||
findChildByGuard,
|
||||
} from './utils';
|
||||
|
||||
/**
|
||||
* Options for {@linkcode convertExecuteMethodMap}
|
||||
*/
|
||||
export interface ConvertExecuteMethodMapOpts {
|
||||
ctx: Context;
|
||||
/**
|
||||
* Logger
|
||||
*/
|
||||
log: AppiumPluginLogger;
|
||||
/**
|
||||
* Probably a class
|
||||
*/
|
||||
parentRefl: DeclarationReflection;
|
||||
/**
|
||||
* The static `executeMethodMap` property of the class
|
||||
*/
|
||||
execMethodMapRefl: ExecMethodDeclarationReflection;
|
||||
/**
|
||||
* Builtin methods from `@appium/types`
|
||||
*/
|
||||
knownMethods: KnownMethods;
|
||||
/**
|
||||
* If `true`, do not add a route if the method it references cannot be found
|
||||
*/
|
||||
strict?: boolean;
|
||||
/**
|
||||
* If `true`, handle commands as `PluginCommand`s, which affects which parameters get used
|
||||
*/
|
||||
isPluginCommand?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gathers info about an `executeMethodMap` prop in a driver
|
||||
* @param opts Options (mostly non-optional)
|
||||
* @returns List of "execute commands", if any
|
||||
*/
|
||||
export function convertExecuteMethodMap({
|
||||
ctx,
|
||||
log,
|
||||
parentRefl,
|
||||
execMethodMapRefl,
|
||||
knownMethods,
|
||||
strict = false,
|
||||
isPluginCommand = false,
|
||||
}: ConvertExecuteMethodMapOpts): ExecMethodDataSet {
|
||||
const commandRefs: ExecMethodDataSet = new Set();
|
||||
if (!execMethodMapRefl) {
|
||||
// no execute commands in this class
|
||||
return commandRefs;
|
||||
}
|
||||
|
||||
const newMethodProps = filterChildrenByKind(execMethodMapRefl, ReflectionKind.Property);
|
||||
for (const newMethodProp of newMethodProps) {
|
||||
const {comment, originalName: script} = newMethodProp;
|
||||
|
||||
const commandProp = findChildByGuard(newMethodProp, isCommandPropDeclarationReflection);
|
||||
|
||||
if (!commandProp) {
|
||||
// this is a bug in the driver implementation
|
||||
log.warn(
|
||||
'Execute method map in %s has no "command" property for %s',
|
||||
parentRefl.name,
|
||||
script
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_.isString(commandProp.type.value) || _.isEmpty(commandProp.type.value)) {
|
||||
// this is a bug in the driver implementation
|
||||
log.warn(
|
||||
'Execute method map in %s has an empty or invalid "command" property for %s',
|
||||
parentRefl.name,
|
||||
script
|
||||
);
|
||||
continue;
|
||||
}
|
||||
const command = String(commandProp.type.value);
|
||||
|
||||
const paramsProp = findChildByGuard(
|
||||
newMethodProp,
|
||||
isExecMethodDefParamsPropDeclarationReflection
|
||||
);
|
||||
const requiredParams = convertRequiredCommandParams(paramsProp);
|
||||
const optionalParams = convertOptionalCommandParams(paramsProp);
|
||||
|
||||
const methodRefl = knownMethods.get(command);
|
||||
|
||||
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});
|
||||
|
||||
const execMethodData = ExecMethodData.create(ctx, log, command, methodRefl, script, {
|
||||
requiredParams,
|
||||
optionalParams,
|
||||
comment: commentData?.comment,
|
||||
commentSource: commentData?.commentSource,
|
||||
isPluginCommand,
|
||||
});
|
||||
|
||||
commandRefs.add(execMethodData);
|
||||
log.verbose(
|
||||
'Added POST route %s for command "%s" from script "%s"',
|
||||
NAME_EXECUTE_ROUTE,
|
||||
command,
|
||||
script
|
||||
);
|
||||
}
|
||||
return commandRefs;
|
||||
}
|
||||
@@ -1,270 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import pluralize from 'pluralize';
|
||||
import {Context, ReflectionKind} from 'typedoc';
|
||||
import {
|
||||
isBasePluginConstructorDeclarationReflection,
|
||||
isClassDeclarationReflection,
|
||||
isConstructorDeclarationReflection,
|
||||
isExecMethodDefReflection,
|
||||
isMethodMapDeclarationReflection,
|
||||
} from '../guards';
|
||||
import {AppiumPluginLogger} from '../logger';
|
||||
import {
|
||||
ExecMethodDataSet,
|
||||
ModuleCommands,
|
||||
ParentReflection,
|
||||
ProjectCommands,
|
||||
RouteMap,
|
||||
} from '../model';
|
||||
import {BaseConverter} from './base-converter';
|
||||
import {convertExecuteMethodMap} from './exec-method-map';
|
||||
import {convertMethodMap} from './method-map';
|
||||
import {convertOverrides} from './overrides';
|
||||
import {ClassDeclarationReflection, KnownMethods} from './types';
|
||||
import {
|
||||
filterChildrenByGuard,
|
||||
findChildByGuard,
|
||||
findChildByNameAndGuard,
|
||||
findCommandMethodsInReflection,
|
||||
} from './utils';
|
||||
|
||||
/**
|
||||
* Name of the static `newMethodMap` property in a Driver or Plugin
|
||||
*/
|
||||
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 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';
|
||||
|
||||
export const NAME_BASE_PLUGIN = 'BasePlugin';
|
||||
|
||||
/**
|
||||
* Converts declarations to information about Appium commands
|
||||
*/
|
||||
export class ExternalConverter extends BaseConverter<ProjectCommands> {
|
||||
/**
|
||||
* Creates a child logger for this instance
|
||||
* @param ctx Typedoc Context
|
||||
* @param log Logger
|
||||
*/
|
||||
constructor(
|
||||
ctx: Context,
|
||||
log: AppiumPluginLogger,
|
||||
protected readonly builtinMethods: KnownMethods,
|
||||
protected readonly builtinCommands?: ModuleCommands
|
||||
) {
|
||||
super(ctx, log.createChildLogger('extension'), builtinCommands);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts declarations into command information
|
||||
*
|
||||
* @returns Command info for entire project
|
||||
*/
|
||||
public override convert(): ProjectCommands {
|
||||
const ctx = this.ctx;
|
||||
const {project} = ctx;
|
||||
const projectCommands: ProjectCommands = new ProjectCommands();
|
||||
|
||||
// convert all modules (or just project if no modules)
|
||||
const modules = project.getChildrenByKind(ReflectionKind.Module);
|
||||
if (modules.length) {
|
||||
for (const mod of modules) {
|
||||
const log = this.log.createChildLogger(mod.name);
|
||||
log.verbose('(%s) Begin conversion', mod.name);
|
||||
const cmdInfo = this.#convertModuleClasses(mod, log);
|
||||
projectCommands.set(mod.name, cmdInfo);
|
||||
log.verbose('(%s) End conversion', mod.name);
|
||||
}
|
||||
} else {
|
||||
const log = this.log.createChildLogger(project.name);
|
||||
log.verbose('(%s) Begin conversion', project.name);
|
||||
const cmdInfo = this.#convertModuleClasses(project, log);
|
||||
projectCommands.set(project.name, cmdInfo);
|
||||
log.verbose('(%s) End conversion', project.name);
|
||||
}
|
||||
|
||||
if (projectCommands.size) {
|
||||
const routeSum = _.sumBy([...projectCommands], ([, info]) => info.routeMap.size);
|
||||
const execMethodSum = _.sumBy(
|
||||
[...projectCommands],
|
||||
([, info]) => info.execMethodDataSet.size
|
||||
);
|
||||
this.log.info(
|
||||
'Found %s and %s in %s',
|
||||
pluralize('command', routeSum, true),
|
||||
pluralize('execute method', execMethodSum, true),
|
||||
pluralize('module', modules.length, true)
|
||||
);
|
||||
} else {
|
||||
this.log.error('No commands nor execute methods found in entire project!');
|
||||
}
|
||||
|
||||
return projectCommands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds commands in all classes within a project or module
|
||||
*
|
||||
* Strategy:
|
||||
*
|
||||
* 1. Given a module or project, find all classes
|
||||
* 2. For each class, find all async methods, which _can_ be commands
|
||||
* 3. Parse the `newMethodMap` of each class, if any
|
||||
* 4. For each method, look for it in either `newMethodMap` or the known methods
|
||||
* 5. Handle execute methods
|
||||
*
|
||||
* @param parentRefl - Project or module
|
||||
* @returns Info about the commands in given `parent`
|
||||
*/
|
||||
#convertModuleClasses(parentRefl: ParentReflection, log: AppiumPluginLogger): ModuleCommands {
|
||||
let routeMap: RouteMap = new Map();
|
||||
let execMethodData: ExecMethodDataSet = new Set();
|
||||
|
||||
const classReflections = filterChildrenByGuard(parentRefl, isClassDeclarationReflection);
|
||||
|
||||
for (const classRefl of classReflections) {
|
||||
const isPlugin = isBasePluginConstructorDeclarationReflection(
|
||||
findChildByGuard(classRefl, isConstructorDeclarationReflection)
|
||||
);
|
||||
|
||||
const classMethods: KnownMethods = findCommandMethodsInReflection(classRefl);
|
||||
|
||||
if (!classMethods.size) {
|
||||
// may or may not be expected
|
||||
log.verbose('(%s) No command methods found', classRefl.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
log.verbose(
|
||||
'(%s) Analyzing %s',
|
||||
classRefl.name,
|
||||
pluralize('method', classMethods.size, true)
|
||||
);
|
||||
|
||||
const newRouteMap = this.#findAndConvertNewMethodMap(classRefl, classMethods, log, isPlugin);
|
||||
routeMap = new Map([...routeMap, ...newRouteMap]);
|
||||
|
||||
const newExecMethodData = this.#findAndConvertExecMethodMap(
|
||||
classRefl,
|
||||
classMethods,
|
||||
log,
|
||||
isPlugin
|
||||
);
|
||||
execMethodData = new Set([...execMethodData, ...newExecMethodData]);
|
||||
|
||||
const overriddenRouteMap: RouteMap = this.builtinCommands
|
||||
? convertOverrides({
|
||||
ctx: this.ctx,
|
||||
log,
|
||||
parentRefl: classRefl,
|
||||
classMethods,
|
||||
builtinMethods: this.builtinMethods,
|
||||
newRouteMap,
|
||||
newExecMethodMap: execMethodData,
|
||||
builtinCommands: this.builtinCommands,
|
||||
})
|
||||
: new Map();
|
||||
|
||||
routeMap = new Map([...routeMap, ...overriddenRouteMap]);
|
||||
|
||||
log.verbose(
|
||||
'(%s) Done; found %s and %s',
|
||||
classRefl.name,
|
||||
pluralize('total command', newRouteMap.size + overriddenRouteMap.size, true),
|
||||
pluralize('total execute method', newExecMethodData.size, true)
|
||||
);
|
||||
}
|
||||
|
||||
return new ModuleCommands(routeMap, execMethodData);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the class has an `executeMethodMap`, convert it
|
||||
* @param parentRefl A class
|
||||
* @param methods Methods in said class
|
||||
* @param isPluginCommand If `parentRefl` represents an Appium Plugin or not
|
||||
* @param log Logger specific to `parentRefl`
|
||||
* @returns A set of exec method data which may be empty
|
||||
*/
|
||||
#findAndConvertExecMethodMap(
|
||||
parentRefl: ClassDeclarationReflection,
|
||||
methods: KnownMethods,
|
||||
log: AppiumPluginLogger,
|
||||
isPluginCommand?: boolean
|
||||
): ExecMethodDataSet {
|
||||
const execMethodMapRefl = findChildByGuard(parentRefl, isExecMethodDefReflection);
|
||||
if (!execMethodMapRefl) {
|
||||
log.verbose('(%s) No execute method map found', parentRefl.name);
|
||||
return new Set();
|
||||
}
|
||||
return convertExecuteMethodMap({
|
||||
ctx: this.ctx,
|
||||
log,
|
||||
parentRefl,
|
||||
execMethodMapRefl,
|
||||
knownMethods: methods,
|
||||
strict: true,
|
||||
isPluginCommand,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* If the class has a `newMethodMap`, convert it
|
||||
* @param parentRefl A class
|
||||
* @param methods Methods in said class
|
||||
* @param log Logger specific to `parentRefl`
|
||||
* @param isPluginCommand If `parentRefl` represents an Appium Plugin or not
|
||||
* @returns A map of routes which may be empty
|
||||
*/
|
||||
#findAndConvertNewMethodMap(
|
||||
parentRefl: ClassDeclarationReflection,
|
||||
methods: KnownMethods,
|
||||
log: AppiumPluginLogger,
|
||||
isPluginCommand?: boolean
|
||||
): RouteMap {
|
||||
const newMethodMapRefl = findChildByNameAndGuard(
|
||||
parentRefl,
|
||||
NAME_NEW_METHOD_MAP,
|
||||
isMethodMapDeclarationReflection
|
||||
);
|
||||
if (!newMethodMapRefl) {
|
||||
log.verbose('(%s) No new method map found', parentRefl.name);
|
||||
return new Map();
|
||||
}
|
||||
return convertMethodMap({
|
||||
ctx: this.ctx,
|
||||
log,
|
||||
methodMapRefl: newMethodMapRefl,
|
||||
parentRefl,
|
||||
knownClassMethods: methods,
|
||||
knownBuiltinMethods: this.builtinMethods,
|
||||
strict: true,
|
||||
isPluginCommand,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/**
|
||||
* Converts code parsed by TypeDoc into a data structure describing the commands and execute methods, which will later be used to create new {@linkcode typedoc#DeclarationReflection} instances in the TypeDoc context.
|
||||
*
|
||||
* The logic in this module is highly dependent on Appium's extension API, and is further dependent on specific usages of TS types. Anything that will be parsed successfully by this module must use a `const` type alias in TS parlance. For example:
|
||||
*
|
||||
* ```ts
|
||||
* const METHOD_MAP = {
|
||||
* '/status': {
|
||||
* GET: {command: 'getStatus'}
|
||||
* },
|
||||
* // ...
|
||||
* } as const; // <-- required
|
||||
* ```
|
||||
* @module
|
||||
*/
|
||||
|
||||
import {Context} from 'typedoc';
|
||||
import {AppiumPluginLogger} from '../logger';
|
||||
import {ProjectCommands} from '../model';
|
||||
import {BuiltinExternalDriverConverter} from './builtin-external-driver';
|
||||
import {BuiltinMethodMapConverter} from './builtin-method-map';
|
||||
import {ExternalConverter} from './external';
|
||||
|
||||
/**
|
||||
* Converts declarations into information about the commands found within
|
||||
* @param ctx - Current TypeDoc context
|
||||
* @param parentLog - Logger
|
||||
* @returns All commands found in the project
|
||||
*/
|
||||
export function convertCommands(
|
||||
ctx: Context,
|
||||
parentLog: AppiumPluginLogger
|
||||
): ProjectCommands | undefined {
|
||||
const log = parentLog.createChildLogger('converter');
|
||||
|
||||
const bedConverter = new BuiltinExternalDriverConverter(ctx, log);
|
||||
const builtinMethods = bedConverter.convert();
|
||||
|
||||
const bmmConverter = new BuiltinMethodMapConverter(ctx, log, builtinMethods);
|
||||
const builtinCommands = bmmConverter.convert();
|
||||
|
||||
const externalConverter = new ExternalConverter(
|
||||
ctx,
|
||||
log,
|
||||
builtinMethods,
|
||||
builtinCommands.moduleCmds
|
||||
);
|
||||
const externalCommands = externalConverter.convert();
|
||||
|
||||
const allCommands = [...builtinCommands.toProjectCommands(), ...externalCommands];
|
||||
|
||||
const projectCmds = new ProjectCommands(allCommands);
|
||||
|
||||
if (projectCmds.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
return projectCmds;
|
||||
}
|
||||
|
||||
export * from '../model/builtin-commands';
|
||||
export * from './base-converter';
|
||||
export * from './builder';
|
||||
export * from './builtin-external-driver';
|
||||
export * from './builtin-method-map';
|
||||
export * from './comment';
|
||||
export * from './exec-method-map';
|
||||
export * from './external';
|
||||
export * from './method-map';
|
||||
export * from './overrides';
|
||||
export * from './types';
|
||||
export * from './utils';
|
||||
@@ -1,170 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import {Context, DeclarationReflection, ReflectionKind} from 'typedoc';
|
||||
import {
|
||||
isCommandPropDeclarationReflection,
|
||||
isHTTPMethodDeclarationReflection,
|
||||
isMethodDefParamsPropDeclarationReflection,
|
||||
isRoutePropDeclarationReflection,
|
||||
} from '../guards';
|
||||
import {AppiumPluginLogger} from '../logger';
|
||||
import {CommandData, CommandSet, RouteMap} from '../model';
|
||||
import {deriveComment} from './comment';
|
||||
import {KnownMethods, MethodMapDeclarationReflection} from './types';
|
||||
import {
|
||||
convertOptionalCommandParams,
|
||||
convertRequiredCommandParams,
|
||||
filterChildrenByGuard,
|
||||
filterChildrenByKind,
|
||||
findChildByGuard,
|
||||
} from './utils';
|
||||
|
||||
/**
|
||||
* Options for {@linkcode convertMethodMap}
|
||||
*/
|
||||
export interface ConvertMethodMapOpts {
|
||||
ctx: Context;
|
||||
/**
|
||||
* All builtin methods from `@appium/types`
|
||||
*/
|
||||
knownBuiltinMethods: KnownMethods;
|
||||
/**
|
||||
* Logger
|
||||
*/
|
||||
log: AppiumPluginLogger;
|
||||
/**
|
||||
* A `MethodMap` object whose parent is `parentRefl`
|
||||
*/
|
||||
methodMapRefl: MethodMapDeclarationReflection;
|
||||
/**
|
||||
* All async methods in `parentRefl`
|
||||
*/
|
||||
knownClassMethods: KnownMethods;
|
||||
/**
|
||||
* The parent of `methodMapRef`; could be a class or module
|
||||
*/
|
||||
parentRefl: DeclarationReflection;
|
||||
/**
|
||||
* If `true`, do not add a route if the method it references cannot be found
|
||||
*/
|
||||
strict?: boolean;
|
||||
|
||||
/**
|
||||
* If `true`, handle commands as `PluginCommand`s, which affects which parameters get used
|
||||
*/
|
||||
isPluginCommand?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts information about `MethodMap` objects
|
||||
* @param opts Options
|
||||
* @returns Lookup of routes to {@linkcode CommandSet} objects
|
||||
*/
|
||||
export function convertMethodMap({
|
||||
ctx,
|
||||
log,
|
||||
methodMapRefl,
|
||||
parentRefl,
|
||||
knownClassMethods,
|
||||
knownBuiltinMethods,
|
||||
strict = false,
|
||||
isPluginCommand = false,
|
||||
}: ConvertMethodMapOpts): RouteMap {
|
||||
const routes: RouteMap = new Map();
|
||||
|
||||
const routeProps = filterChildrenByKind(methodMapRefl, ReflectionKind.Property);
|
||||
|
||||
if (!routeProps.length) {
|
||||
if (methodMapRefl.overwrites?.name === 'BasePlugin') {
|
||||
log.warn('(%s) MethodMap; skipping');
|
||||
}
|
||||
return routes;
|
||||
}
|
||||
|
||||
for (const routeProp of routeProps) {
|
||||
const {originalName: route} = routeProp;
|
||||
|
||||
if (!isRoutePropDeclarationReflection(routeProp)) {
|
||||
log.warn('Empty route: %s', route);
|
||||
continue;
|
||||
}
|
||||
|
||||
const httpMethodProps = filterChildrenByGuard(routeProp, isHTTPMethodDeclarationReflection);
|
||||
|
||||
if (!httpMethodProps.length) {
|
||||
log.warn('No HTTP methods found in route %s', route);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const httpMethodProp of httpMethodProps) {
|
||||
const {comment: mapComment, name: httpMethod} = httpMethodProp;
|
||||
|
||||
const commandProp = findChildByGuard(httpMethodProp, isCommandPropDeclarationReflection);
|
||||
|
||||
// commandProp is optional.
|
||||
if (!commandProp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_.isString(commandProp.type.value) || _.isEmpty(commandProp.type.value)) {
|
||||
log.warn('Empty command name found in %s - %s', route, httpMethod);
|
||||
continue;
|
||||
}
|
||||
|
||||
const command = String(commandProp.type.value);
|
||||
|
||||
const method = knownClassMethods.get(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: knownBuiltinMethods,
|
||||
});
|
||||
const {comment, commentSource} = commentData ?? {};
|
||||
|
||||
const payloadParamsProp = findChildByGuard(
|
||||
httpMethodProp,
|
||||
isMethodDefParamsPropDeclarationReflection
|
||||
);
|
||||
|
||||
const requiredParams = convertRequiredCommandParams(payloadParamsProp);
|
||||
const optionalParams = convertOptionalCommandParams(payloadParamsProp);
|
||||
|
||||
const commandSet: CommandSet = routes.get(route) ?? new Set();
|
||||
|
||||
const commandData = CommandData.create(ctx, log, command, method, httpMethod, route, {
|
||||
requiredParams,
|
||||
optionalParams,
|
||||
comment,
|
||||
commentSource,
|
||||
parentRefl,
|
||||
isPluginCommand,
|
||||
knownBuiltinMethods,
|
||||
});
|
||||
|
||||
commandSet.add(commandData);
|
||||
|
||||
log.verbose(
|
||||
'(%s) Registered route %s %s for command "%s"',
|
||||
parentRefl.name,
|
||||
httpMethod,
|
||||
route,
|
||||
command
|
||||
);
|
||||
|
||||
routes.set(route, commandSet);
|
||||
}
|
||||
}
|
||||
|
||||
return routes;
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import {Context, DeclarationReflection} from 'typedoc';
|
||||
import {AppiumPluginLogger} from '../logger';
|
||||
import {CommandData, CommandSet, ExecMethodDataSet, ModuleCommands, RouteMap} from '../model';
|
||||
import {deriveComment} from './comment';
|
||||
import {KnownMethods} from './types';
|
||||
|
||||
/**
|
||||
* Returns routes pulled from `builtinCommands` if the driver implements them as methods.
|
||||
*
|
||||
* This makes up for the fact that `newMethodMap` only defines _new_ methods, not ones that already
|
||||
* exist in `@appium/base-driver`.
|
||||
*
|
||||
* Sorry about all the arguments!
|
||||
* @param args Required arguments
|
||||
* @returns More routes pulled from `builtinCommands`, if the driver implements them
|
||||
*/
|
||||
export function convertOverrides({
|
||||
ctx,
|
||||
log,
|
||||
parentRefl,
|
||||
classMethods,
|
||||
builtinMethods,
|
||||
newRouteMap,
|
||||
newExecMethodMap,
|
||||
builtinCommands,
|
||||
}: ConvertOverridesOpts): RouteMap {
|
||||
const routes: RouteMap = new Map();
|
||||
|
||||
/**
|
||||
* All command/method names associated with execute methods
|
||||
*/
|
||||
const execMethodNames = [...newExecMethodMap].map((execData) => execData.command);
|
||||
|
||||
/**
|
||||
* All method names in the class
|
||||
*/
|
||||
const methodNames = [...classMethods.keys()];
|
||||
|
||||
/**
|
||||
* All methods in the class which are not associated with execute methods
|
||||
*/
|
||||
const methodsLessExecCommands = _.difference(methodNames, execMethodNames);
|
||||
|
||||
/**
|
||||
* All methods in the class within its `newMethodMap`
|
||||
*/
|
||||
const methodsInMethodMap = [...newRouteMap.values()].flatMap((commandSet) =>
|
||||
[...commandSet].map((commandData) => commandData.command)
|
||||
);
|
||||
|
||||
/**
|
||||
* All methods in the class which are not associated with execute methods nor the class' method map
|
||||
*/
|
||||
const unknownMethods = new Set(_.difference(methodsLessExecCommands, methodsInMethodMap));
|
||||
|
||||
// this discovers all of the ExternalDriver methods implemented in the driver class
|
||||
// and adds them to the routes map.
|
||||
for (const command of unknownMethods) {
|
||||
const builtinRoutes = builtinCommands.routesByCommandName.get(command);
|
||||
if (!builtinMethods.has(command) || !builtinRoutes) {
|
||||
// actually unknown method
|
||||
log.verbose('(%s) Method "%s" is not a registered command', parentRefl.name, command);
|
||||
continue;
|
||||
}
|
||||
|
||||
// the method is in ExternalDriver, so remove it
|
||||
unknownMethods.delete(command);
|
||||
|
||||
for (const route of builtinRoutes) {
|
||||
const newCommandSet: CommandSet = new Set();
|
||||
// this must be defined, because if it wasn't then builtinRoutes would be empty and we'd continue the loop
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const commandSet = builtinCommands.routeMap.get(route)!;
|
||||
for (const commandData of commandSet) {
|
||||
const methodRefl = classMethods.get(command);
|
||||
if (!methodRefl) {
|
||||
log.warn('(%s) No such method "%s"; this is a bug', parentRefl.name, command);
|
||||
continue;
|
||||
}
|
||||
const commentData = deriveComment({
|
||||
refl: methodRefl,
|
||||
knownMethods: builtinMethods,
|
||||
});
|
||||
const newCommandData = CommandData.clone(commandData, ctx, {
|
||||
methodRefl,
|
||||
parentRefl,
|
||||
knownBuiltinMethods: builtinMethods,
|
||||
opts: {
|
||||
comment: commentData?.comment,
|
||||
},
|
||||
});
|
||||
log.verbose(
|
||||
'(%s) Linked command "%s" to route %s %s',
|
||||
parentRefl.name,
|
||||
command,
|
||||
commandData.httpMethod,
|
||||
route
|
||||
);
|
||||
newCommandSet.add(newCommandData);
|
||||
}
|
||||
routes.set(route, newCommandSet);
|
||||
}
|
||||
}
|
||||
|
||||
return routes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@link convertOverrides}
|
||||
*/
|
||||
export interface ConvertOverridesOpts {
|
||||
ctx: Context;
|
||||
/**
|
||||
* Logger
|
||||
*/
|
||||
log: AppiumPluginLogger;
|
||||
/**
|
||||
* Project/module reflection
|
||||
*/
|
||||
parentRefl: DeclarationReflection;
|
||||
/**
|
||||
* Methods in the class
|
||||
*/
|
||||
classMethods: KnownMethods;
|
||||
/**
|
||||
* Methods in `@appium/base-driver`
|
||||
*/
|
||||
builtinMethods: KnownMethods;
|
||||
/**
|
||||
* New routes in the class via `newMethodMap`
|
||||
*/
|
||||
newRouteMap: RouteMap;
|
||||
/**
|
||||
* Execute methods in the class via `executeMethodMap`
|
||||
*/
|
||||
newExecMethodMap: ExecMethodDataSet;
|
||||
/**
|
||||
* Routes in `@appium/base-driver`
|
||||
*/
|
||||
builtinCommands: ModuleCommands;
|
||||
}
|
||||
@@ -1,363 +0,0 @@
|
||||
import {ValueOf} from 'type-fest';
|
||||
import {
|
||||
Comment,
|
||||
DeclarationReflection,
|
||||
LiteralType,
|
||||
ParameterReflection,
|
||||
ReferenceType,
|
||||
ReflectionFlags,
|
||||
ReflectionKind,
|
||||
ReflectionType,
|
||||
SignatureReflection,
|
||||
SomeType,
|
||||
TupleType,
|
||||
TypeOperatorType,
|
||||
} from 'typedoc';
|
||||
import {
|
||||
AllowedHttpMethod,
|
||||
AppiumPluginReflectionKind,
|
||||
CommandReflection,
|
||||
ParentReflection,
|
||||
} from '../model';
|
||||
import {NAME_EXTERNAL_DRIVER, NAME_TYPES_MODULE} from './builtin-external-driver';
|
||||
import {NAME_BUILTIN_COMMAND_MODULE, NAME_METHOD_MAP} from './builtin-method-map';
|
||||
import {NAME_NEW_METHOD_MAP, NAME_EXECUTE_METHOD_MAP, NAME_PARAMS} from './external';
|
||||
|
||||
type WithName<S extends string, R> = R & {
|
||||
name: S;
|
||||
};
|
||||
|
||||
type WithKind<K extends ReflectionKind | AppiumPluginReflectionKind, R> = R & {kind: K};
|
||||
|
||||
/**
|
||||
* Utility to narrow a declaration reflection to a specific `SomeType`
|
||||
*/
|
||||
type WithSomeType<T extends SomeType, R> = R & {type: T};
|
||||
|
||||
/**
|
||||
* Utility to narrow by name and kind
|
||||
*/
|
||||
type WithNameAndKind<S extends string, K extends ReflectionKind, R> = R & {name: S; kind: K};
|
||||
|
||||
/**
|
||||
* Utility; a TupleType with literal elements
|
||||
*/
|
||||
export type TupleTypeWithLiteralElements = TupleType & {elements: LiteralType[]};
|
||||
|
||||
/**
|
||||
* Type corresponding to a reflection of a {@linkcode @appium/types#MethodMap}
|
||||
*/
|
||||
export type MethodMapDeclarationReflection = WithName<
|
||||
typeof NAME_METHOD_MAP | typeof NAME_NEW_METHOD_MAP,
|
||||
DeclarationReflectionWithReflectedType
|
||||
>;
|
||||
|
||||
/**
|
||||
* Type corresponding to a reflection of {@linkcode @appium/base-driver}
|
||||
*/
|
||||
export type BaseDriverDeclarationReflection = WithNameAndKind<
|
||||
typeof NAME_BUILTIN_COMMAND_MODULE,
|
||||
ReflectionKind.Module,
|
||||
DeclarationReflection
|
||||
>;
|
||||
|
||||
/**
|
||||
* Type for the parameters of a command definition or execute method definition.
|
||||
*
|
||||
* Node that merging `TypeOperatorType` won't work because it will no longer satisfy `SomeType`, because `SomeType` is a finite collection.
|
||||
*/
|
||||
export type MethodDefParamNamesDeclarationReflection = WithSomeType<
|
||||
TypeOperatorTypeWithTupleTypeWithLiteralElements,
|
||||
DeclarationReflection
|
||||
>;
|
||||
|
||||
export type TypeOperatorTypeWithTupleTypeWithLiteralElements = TypeOperatorType & {
|
||||
operator: 'readonly';
|
||||
target: TupleTypeWithLiteralElements;
|
||||
};
|
||||
|
||||
/**
|
||||
* Narrows a declaration reflection to one having a reflection type and a property kind. Generic
|
||||
*/
|
||||
export type PropDeclarationReflection = WithKind<
|
||||
ReflectionKind.Property,
|
||||
DeclarationReflectionWithReflectedType
|
||||
>;
|
||||
|
||||
/**
|
||||
* A type corresponding to the HTTP method of a route, which is a property off of the object with the route name in a `MethodMap`
|
||||
*/
|
||||
export type HTTPMethodDeclarationReflection = WithName<
|
||||
AllowedHttpMethod,
|
||||
PropDeclarationReflection
|
||||
>;
|
||||
|
||||
/**
|
||||
* A declaration reflection having a reflection type. Generic
|
||||
*/
|
||||
export type DeclarationReflectionWithReflectedType = WithSomeType<
|
||||
ReflectionType,
|
||||
DeclarationReflection
|
||||
>;
|
||||
|
||||
/**
|
||||
* Type corresponding to the value of the `command` property within a `MethodDef`, which must be a type literal.
|
||||
*/
|
||||
export type CommandPropDeclarationReflection = WithSomeType<LiteralType, DeclarationReflection>;
|
||||
|
||||
/**
|
||||
* A generic type guard
|
||||
*/
|
||||
export type Guard<T> = (value: any) => value is T;
|
||||
|
||||
/**
|
||||
* Type corresponding to an execute method map
|
||||
*/
|
||||
export type ExecMethodDeclarationReflection = WithName<
|
||||
typeof NAME_EXECUTE_METHOD_MAP,
|
||||
DeclarationReflectionWithReflectedType
|
||||
> &
|
||||
WithStaticFlag;
|
||||
|
||||
/**
|
||||
* Whatever has this flag will be a static member
|
||||
*/
|
||||
export type WithStaticFlag = {flags: ReflectionFlags & {isStatic: true}};
|
||||
/**
|
||||
* Whatever has this flag will _not_ be a static member
|
||||
*/
|
||||
export type WithoutStaticFlag = {flags: ReflectionFlags & {isStatic: false}};
|
||||
|
||||
/**
|
||||
* Type corresponding to the `params` prop of a `MethodDef`
|
||||
*/
|
||||
export type MethodDefParamsPropDeclarationReflection = WithNameAndKind<
|
||||
typeof NAME_PARAMS,
|
||||
ReflectionKind.Property,
|
||||
DeclarationReflectionWithReflectedType
|
||||
>;
|
||||
|
||||
/**
|
||||
* Type corresponding to the `payloadParams` prop of an `ExecMethodDef`
|
||||
*/
|
||||
export type ExecMethodDefParamsPropDeclarationReflection = WithName<
|
||||
typeof NAME_PARAMS,
|
||||
DeclarationReflectionWithReflectedType
|
||||
>;
|
||||
|
||||
/**
|
||||
* Type corresponding to `@appium/types` module
|
||||
*/
|
||||
export type AppiumTypesReflection = WithNameAndKind<
|
||||
typeof NAME_TYPES_MODULE,
|
||||
ReflectionKind.Module,
|
||||
ParentReflection
|
||||
>;
|
||||
|
||||
/**
|
||||
* Type corresponding to a TS `interface`
|
||||
*/
|
||||
export type InterfaceDeclarationReflection = WithKind<
|
||||
ReflectionKind.Interface,
|
||||
DeclarationReflection
|
||||
>;
|
||||
|
||||
/**
|
||||
* Type corresponding to the `ExternalDriver` `interface` of `@appium/types`
|
||||
*/
|
||||
export type ExternalDriverDeclarationReflection = WithName<
|
||||
typeof NAME_EXTERNAL_DRIVER,
|
||||
InterfaceDeclarationReflection
|
||||
>;
|
||||
|
||||
/**
|
||||
* A call signature for a function that returns some sort of `Promise`.
|
||||
*/
|
||||
export type AsyncCallSignatureReflection = CallSignatureReflection & {
|
||||
type: ReferenceType;
|
||||
name: 'Promise';
|
||||
};
|
||||
|
||||
/**
|
||||
* An async method or reference to an async method. In a driver, a command's method must be of this type.
|
||||
*/
|
||||
export type CommandMethodDeclarationReflection<
|
||||
T extends ReferenceType | ReflectionType = ReferenceType
|
||||
> = WithSomeType<T, DeclarationReflection> &
|
||||
WithKind<
|
||||
T extends ReferenceType ? ReflectionKind.Method : ReflectionKind.Property,
|
||||
DeclarationReflection
|
||||
> &
|
||||
WithoutStaticFlag &
|
||||
(T extends ReferenceType
|
||||
? {
|
||||
signatures: NonEmptyArray<AsyncCallSignatureReflection>;
|
||||
}
|
||||
: T extends ReflectionType
|
||||
? {
|
||||
type: {
|
||||
declaration: {
|
||||
signatures: NonEmptyArray<AsyncCallSignatureReflection>;
|
||||
};
|
||||
};
|
||||
}
|
||||
: never);
|
||||
|
||||
/**
|
||||
* A lookup of command names to their reflections.
|
||||
*/
|
||||
export type KnownMethods = Map<string, CommandMethodDeclarationReflection>;
|
||||
|
||||
/**
|
||||
* A {@linkcode DeclarationReflection} which is a `class`.
|
||||
*/
|
||||
export type ClassDeclarationReflection = WithKind<ReflectionKind.Class, DeclarationReflection>;
|
||||
|
||||
/**
|
||||
* A constructor
|
||||
*/
|
||||
export type ConstructorDeclarationReflection = WithNameAndKind<
|
||||
'constructor',
|
||||
ReflectionKind.Constructor,
|
||||
DeclarationReflection
|
||||
>;
|
||||
|
||||
/**
|
||||
* A {@linkcode ReferenceType} referencing the constructor of `BasePlugin`
|
||||
*/
|
||||
export type BasePluginConstructorReferenceType = ReferenceType & {name: 'BasePlugin.constructor'};
|
||||
|
||||
/**
|
||||
* A {@linkcode DeclarationReflection} for the constructor of a class extending `BasePlugin`
|
||||
*/
|
||||
export type BasePluginConstructorDeclarationReflection = WithSomeType<
|
||||
ReferenceType,
|
||||
DeclarationReflection
|
||||
> &
|
||||
ConstructorDeclarationReflection &
|
||||
(
|
||||
| {inheritedFrom: BasePluginConstructorReferenceType}
|
||||
| {overwrites: BasePluginConstructorReferenceType}
|
||||
);
|
||||
|
||||
/**
|
||||
* One of {@linkcode ExecMethodDefParamsPropDeclarationReflection} or
|
||||
* {@linkcode MethodDefParamsPropDeclarationReflection}, which are "parameters" properties of method
|
||||
* definition objects (as in a `MethodMap`) or execute method definitions (in an `ExecMethodMap`)
|
||||
*/
|
||||
export type ParamsPropDeclarationReflection =
|
||||
| ExecMethodDefParamsPropDeclarationReflection
|
||||
| MethodDefParamsPropDeclarationReflection;
|
||||
|
||||
/**
|
||||
* A {@linkcode SignatureReflection} which is a call signature; a function signature.
|
||||
*
|
||||
* (Other types of signatures include things like "constructor signatures")
|
||||
*/
|
||||
export type CallSignatureReflection = WithKind<ReflectionKind.CallSignature, SignatureReflection>;
|
||||
|
||||
/**
|
||||
* An array with a nonzero number of items.
|
||||
*/
|
||||
export type NonEmptyArray<T> = [T, ...T[]];
|
||||
|
||||
/**
|
||||
* A {@linkcode CallSignatureReflection} with a nonzero number of parameters.
|
||||
*
|
||||
* This is used to rename parameters on commands to prefer the ones as defined in the method map.
|
||||
*/
|
||||
export type CallSignatureReflectionWithArity = CallSignatureReflection & {
|
||||
parameters: NonEmptyArray<ParameterReflection>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Can be used to narrow a {@linkcode CommandReflection} to one representing an execute method.
|
||||
*/
|
||||
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',
|
||||
}
|
||||
@@ -1,366 +0,0 @@
|
||||
/**
|
||||
* Utilities for the various converters.
|
||||
* @module
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
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 {
|
||||
CallSignatureReflection,
|
||||
ClassDeclarationReflection,
|
||||
CommandMethodDeclarationReflection,
|
||||
Guard,
|
||||
InterfaceDeclarationReflection,
|
||||
KnownMethods,
|
||||
ParamsPropDeclarationReflection,
|
||||
} from './types';
|
||||
|
||||
export function findParentReflectionByName(
|
||||
project: ProjectReflection,
|
||||
name: string
|
||||
): ParentReflection | undefined {
|
||||
return project.name === name ? project : (project.getChildByName(name) as ParentReflection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters children by a type guard
|
||||
* @param refl - Reflection to check
|
||||
* @param guard - Type guard function
|
||||
* @returns Filtered children, if any
|
||||
* @internal
|
||||
*/
|
||||
export function filterChildrenByGuard<T extends ParentReflection, G extends DeclarationReflection>(
|
||||
refl: T,
|
||||
guard: Guard<G>
|
||||
): G[] {
|
||||
return (
|
||||
(isReflectionWithReflectedType(refl)
|
||||
? refl.type.declaration.children?.filter(guard)
|
||||
: refl.children?.filter(guard)) ?? ([] as G[])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a child of a reflection by type guard
|
||||
* @param refl - Reflection to check
|
||||
* @param guard - Guard function to check child
|
||||
* @returns Child if found, `undefined` otherwise
|
||||
* @internal
|
||||
*/
|
||||
export function findChildByGuard<T extends ParentReflection, G extends ParentReflection>(
|
||||
refl: T,
|
||||
guard: Guard<G>
|
||||
): G | undefined {
|
||||
return (
|
||||
isReflectionWithReflectedType(refl)
|
||||
? refl.type.declaration.children?.find(guard)
|
||||
: refl.children?.find(guard)
|
||||
) as G | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a child of a reflection by name and type guard
|
||||
* @param refl - Reflection to check
|
||||
* @param name - Name of child
|
||||
* @param guard - Guard function to check child
|
||||
* @returns Child if found, `undefined` otherwise
|
||||
* @internal
|
||||
*/
|
||||
export function findChildByNameAndGuard<T extends ParentReflection, G extends ParentReflection>(
|
||||
refl: T,
|
||||
name: string,
|
||||
guard: Guard<G>
|
||||
): G | undefined {
|
||||
const predicate = (child: {name: string}) => child.name === name && guard(child);
|
||||
return (
|
||||
isReflectionWithReflectedType(refl)
|
||||
? refl.type.declaration.children?.find(predicate)
|
||||
: refl.children?.find(predicate)
|
||||
) as G | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters children of a reflection by kind and whether they are of type {@linkcode DeclarationReflectionWithReflectedType}
|
||||
* @param refl - Reflection to check
|
||||
* @param kind - Kind of child
|
||||
* @returns Filtered children, if any
|
||||
* @internal
|
||||
*/
|
||||
|
||||
export function filterChildrenByKind<T extends DeclarationReflection>(
|
||||
refl: T,
|
||||
kind: ReflectionKind
|
||||
): DeclarationReflection[] {
|
||||
return (
|
||||
(isReflectionWithReflectedType(refl)
|
||||
? refl.type.declaration.getChildrenByKind(kind)
|
||||
: refl.getChildrenByKind(kind)) ?? ([] as DeclarationReflection[])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds _all_ async command methods in a class or interface
|
||||
* @param refl Class reflection
|
||||
* @returns Map of method names to method reflections
|
||||
*/
|
||||
export function findCommandMethodsInReflection(
|
||||
refl: ClassDeclarationReflection | InterfaceDeclarationReflection
|
||||
): KnownMethods {
|
||||
return new Map(
|
||||
filterChildrenByGuard(refl, isCommandMethodDeclarationReflection).map((method) => [
|
||||
method.name,
|
||||
method,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds "optional" params in a method definition
|
||||
* @param methodDefRefl - Reflection of a method definition
|
||||
* @returns List of optional parameters
|
||||
* @internal
|
||||
*/
|
||||
export function convertOptionalCommandParams(
|
||||
methodDefRefl?: ParamsPropDeclarationReflection
|
||||
): string[] {
|
||||
return convertCommandParams(NAME_OPTIONAL, methodDefRefl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds names of parameters of a command in a method def
|
||||
* @param propName Either required or optional params
|
||||
* @param refl Parent reflection (`params` prop of method def)
|
||||
* @returns List of parameter names
|
||||
* @internal
|
||||
*/
|
||||
export function convertCommandParams(
|
||||
propName: typeof NAME_OPTIONAL | typeof NAME_REQUIRED,
|
||||
refl?: ParamsPropDeclarationReflection
|
||||
): string[] {
|
||||
if (!refl) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const props = findChildByNameAndGuard(refl, propName, isMethodDefParamNamesDeclarationReflection);
|
||||
|
||||
if (!props) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return props.type.target.elements.reduce((names, el: LiteralType) => {
|
||||
const stringValue = String(el.value);
|
||||
if (stringValue) {
|
||||
names.push(stringValue);
|
||||
}
|
||||
return names;
|
||||
}, [] as string[]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds "required" params in a method definition
|
||||
* @param methodDefRefl - Reflection of a method definition
|
||||
* @returns List of required parameters
|
||||
* @internal
|
||||
*/
|
||||
export function convertRequiredCommandParams(
|
||||
methodDefRefl?: ParamsPropDeclarationReflection
|
||||
): 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,
|
||||
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[] {
|
||||
const {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 `CallSignatureReflection` with a new parent and type.
|
||||
*
|
||||
* This does a "deep" clone inasmuch as it clones any associated `ParameterReflection` and
|
||||
* `TypeParameterReflection` instances.
|
||||
*
|
||||
* @privateRemarks I'm not sure this is sufficient.
|
||||
* @param sig A `CallSignatureReflection` to clone
|
||||
* @param parent The desired parent of the new `CallSignatureReflection`
|
||||
* @param type The desired type of the new `CallSignatureReflection`; if not provided, the original type
|
||||
* will be used
|
||||
* @returns A clone of `sig` with the given parent and type
|
||||
*/
|
||||
export function cloneCallSignatureReflection(
|
||||
sig: CallSignatureReflection,
|
||||
parent: CommandMethodDeclarationReflection,
|
||||
type?: SomeType
|
||||
) {
|
||||
const newSig = new SignatureReflection(sig.name, ReflectionKind.CallSignature, parent);
|
||||
|
||||
return _.assign(newSig, _.pick(sig, SIGNATURE_REFLECTION_CLONE_FIELDS), {
|
||||
parameters: _.map(sig.parameters, (p) => cloneParameterReflection(p, p.name, newSig)),
|
||||
typeParameters: _.map(sig.typeParameters, (tP) => cloneTypeParameterReflection(tP, newSig)),
|
||||
type: type ?? sig.type,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@linkcode createNewParamRefls}
|
||||
*/
|
||||
export interface CreateNewParamReflsOpts {
|
||||
/**
|
||||
* Map of known methods
|
||||
*/
|
||||
builtinMethods?: KnownMethods;
|
||||
/**
|
||||
* List of parameter names from method def
|
||||
*/
|
||||
commandParams?: string[];
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -1,368 +0,0 @@
|
||||
/**
|
||||
* A bunch of type guards. Because here is a place to put all of them.
|
||||
* @module
|
||||
*/
|
||||
|
||||
import {
|
||||
DeclarationReflection,
|
||||
LiteralType,
|
||||
ParameterReflection,
|
||||
ProjectReflection,
|
||||
ReferenceType,
|
||||
Reflection,
|
||||
ReflectionKind,
|
||||
ReflectionType,
|
||||
SignatureReflection,
|
||||
TupleType,
|
||||
TypeOperatorType,
|
||||
} from 'typedoc';
|
||||
import {
|
||||
NAME_BASE_PLUGIN,
|
||||
NAME_BUILTIN_COMMAND_MODULE,
|
||||
NAME_COMMAND,
|
||||
NAME_EXECUTE_METHOD_MAP,
|
||||
NAME_EXTERNAL_DRIVER,
|
||||
NAME_METHOD_MAP,
|
||||
NAME_NEW_METHOD_MAP,
|
||||
NAME_PARAMS,
|
||||
NAME_PAYLOAD_PARAMS,
|
||||
NAME_TYPES_MODULE,
|
||||
} from './converter';
|
||||
import {
|
||||
AppiumTypesReflection,
|
||||
BaseDriverDeclarationReflection,
|
||||
BasePluginConstructorDeclarationReflection,
|
||||
CallSignatureReflection,
|
||||
CallSignatureReflectionWithArity,
|
||||
ClassDeclarationReflection,
|
||||
CommandMethodDeclarationReflection,
|
||||
CommandPropDeclarationReflection,
|
||||
ConstructorDeclarationReflection,
|
||||
DeclarationReflectionWithReflectedType,
|
||||
ExecMethodDeclarationReflection,
|
||||
ExecMethodDefParamsPropDeclarationReflection,
|
||||
ExternalDriverDeclarationReflection,
|
||||
HTTPMethodDeclarationReflection,
|
||||
InterfaceDeclarationReflection,
|
||||
MethodDefParamNamesDeclarationReflection,
|
||||
MethodDefParamsPropDeclarationReflection,
|
||||
MethodMapDeclarationReflection,
|
||||
PropDeclarationReflection,
|
||||
} from './converter/types';
|
||||
import {AllowedHttpMethod, ExecMethodData, ParentReflection} from './model';
|
||||
|
||||
/**
|
||||
* Set of HTTP methods allowed by WebDriver; see {@linkcode AllowedHttpMethod}
|
||||
*/
|
||||
const ALLOWED_HTTP_METHODS: Readonly<Set<AllowedHttpMethod>> = new Set([
|
||||
'GET',
|
||||
'POST',
|
||||
'DELETE',
|
||||
] as const);
|
||||
|
||||
/**
|
||||
* Type guard for {@linkcode DeclarationReflection}
|
||||
* @param value any value
|
||||
*/
|
||||
export function isDeclarationReflection(value: any): value is DeclarationReflection {
|
||||
return value instanceof DeclarationReflection;
|
||||
}
|
||||
|
||||
export function isParentReflection(value: any): value is ParentReflection {
|
||||
return (
|
||||
value && (value instanceof DeclarationReflection || (value as ProjectReflection).isProject())
|
||||
);
|
||||
}
|
||||
|
||||
export function isAppiumTypesReflection(value: any): value is AppiumTypesReflection {
|
||||
return isParentReflection(value) && value.name === NAME_TYPES_MODULE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for {@linkcode ReflectionType}
|
||||
* @param value any value
|
||||
*/
|
||||
export function isReflectionType(value: any): value is ReflectionType {
|
||||
return value instanceof ReflectionType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for {@linkcode TypeOperatorType}
|
||||
* @param value any value
|
||||
*/
|
||||
export function isTypeOperatorType(value: any): value is TypeOperatorType {
|
||||
return value instanceof TypeOperatorType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for {@linkcode LiteralType}
|
||||
* @param value any value
|
||||
*/
|
||||
export function isLiteralType(value: any): value is LiteralType {
|
||||
return value instanceof LiteralType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for {@linkcode TupleType}
|
||||
* @param value any value
|
||||
*/
|
||||
export function isTupleType(value: any): value is TupleType {
|
||||
return value instanceof TupleType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for a {@linkcode DeclarationReflectionWithReflectedType} corresponding to
|
||||
* the `executeMethodMap` static property of an extension class.
|
||||
* @param value any
|
||||
*/
|
||||
export function isExecMethodDefReflection(value: any): value is ExecMethodDeclarationReflection {
|
||||
return (
|
||||
isReflectionWithReflectedType(value) &&
|
||||
value.name === NAME_EXECUTE_METHOD_MAP &&
|
||||
value.flags.isStatic
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for a {@linkcode MethodDefParamNamesDeclarationReflection} corresponding to a list of required or optional parameters within a command or execute method definition.
|
||||
* @param value any value
|
||||
*/
|
||||
export function isMethodDefParamNamesDeclarationReflection(
|
||||
value: any
|
||||
): value is MethodDefParamNamesDeclarationReflection {
|
||||
return (
|
||||
isDeclarationReflection(value) &&
|
||||
value.kindOf(ReflectionKind.Property) &&
|
||||
isTypeOperatorType(value.type) &&
|
||||
isTupleType(value.type.target) &&
|
||||
value.type.target.elements.every(isLiteralType)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for a {@linkcode PropDeclarationReflection} corresponding to some property of a constant object.
|
||||
* @param value any value
|
||||
*/
|
||||
export function isRoutePropDeclarationReflection(value: any): value is PropDeclarationReflection {
|
||||
return isReflectionWithReflectedType(value) && isPropertyKind(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for a {@linkcode BaseDriverDeclarationReflection} corresponding to the `@appium/base-driver` module (_not_ the class).
|
||||
* @param value any value
|
||||
*/
|
||||
export function isBaseDriverDeclarationReflection(
|
||||
value: any
|
||||
): value is BaseDriverDeclarationReflection {
|
||||
return (
|
||||
isParentReflection(value) &&
|
||||
value.name === NAME_BUILTIN_COMMAND_MODULE &&
|
||||
value.kindOf(ReflectionKind.Module | ReflectionKind.Project)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for a property of an object (a {@linkcode Reflection} having kind {@linkcode ReflectionKind.Property}).
|
||||
* @param value any value
|
||||
*/
|
||||
export function isPropertyKind(value: any) {
|
||||
return value instanceof Reflection && value.kindOf(ReflectionKind.Property);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for a {@linkcode MethodMapDeclarationReflection} corresponding to the `newMethodMap` static property of an extension class _or_ the `METHOD_MAP` export within `@appium/base-driver`.
|
||||
*
|
||||
* Note that the type does not care about the `isStatic` flag, but this guard does.
|
||||
* @param value any value
|
||||
*/
|
||||
export function isMethodMapDeclarationReflection(
|
||||
value: any
|
||||
): value is MethodMapDeclarationReflection {
|
||||
return (
|
||||
isReflectionWithReflectedType(value) &&
|
||||
((value.name === NAME_NEW_METHOD_MAP && value.flags.isStatic) || value.name === NAME_METHOD_MAP)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for a {@linkcode DeclarationReflectionWithReflectedType} a declaration reflection having a reflection type.
|
||||
*
|
||||
* I don't know what that means, exactly, but there it is.
|
||||
* @param value any value
|
||||
*/
|
||||
export function isReflectionWithReflectedType(
|
||||
value: any
|
||||
): value is DeclarationReflectionWithReflectedType {
|
||||
return isDeclarationReflection(value) && isReflectionType(value.type);
|
||||
}
|
||||
|
||||
export function isHTTPMethodDeclarationReflection(
|
||||
value: any
|
||||
): value is HTTPMethodDeclarationReflection {
|
||||
return (
|
||||
isReflectionWithReflectedType(value) && isPropertyKind(value) && isAllowedHTTPMethod(value.name)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for an {@linkcode AllowedHttpMethod}
|
||||
*
|
||||
* @param value any value
|
||||
*/
|
||||
export function isAllowedHTTPMethod(value: any): value is AllowedHttpMethod {
|
||||
return ALLOWED_HTTP_METHODS.has(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for a {@linkcode CommandPropDeclarationReflection} corresponding to the `command` property of a {@linkcode @appium/types#MethodDef} object contained within a {@linkcode @appium/types#MethodMap}.
|
||||
* @param value any value
|
||||
*/
|
||||
export function isCommandPropDeclarationReflection(
|
||||
value: any
|
||||
): value is CommandPropDeclarationReflection {
|
||||
return isDeclarationReflection(value) && isLiteralType(value.type) && value.name === NAME_COMMAND;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for a {@linkcode ExecMethodData} derived from a {@linkcode @appium/types#ExecuteMethodMap} object.
|
||||
* @param value any value
|
||||
*/
|
||||
export function isExecMethodData(value: any): value is ExecMethodData {
|
||||
return value && typeof value === 'object' && value.script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for a {@linkcode MethodDefParamsPropDeclarationReflection} corresponding to the `params` prop of a `MethodDef`
|
||||
* @param value any value
|
||||
*/
|
||||
export function isMethodDefParamsPropDeclarationReflection(
|
||||
value: any
|
||||
): value is MethodDefParamsPropDeclarationReflection {
|
||||
return isReflectionWithReflectedType(value) && value.name === NAME_PAYLOAD_PARAMS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for a {@linkcode ExecMethodDefParamsPropDeclarationReflection} corresponding to the `payloadParams` prop of an `ExecuteMethodDef`.
|
||||
* @param value any value
|
||||
*/
|
||||
export function isExecMethodDefParamsPropDeclarationReflection(
|
||||
value: any
|
||||
): value is ExecMethodDefParamsPropDeclarationReflection {
|
||||
return isReflectionWithReflectedType(value) && value.name === NAME_PARAMS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for a {@linkcode InterfaceDeclarationReflection} corresponding to a TS interface.
|
||||
* @param value any value
|
||||
*/
|
||||
export function isInterfaceDeclarationReflection(
|
||||
value: any
|
||||
): value is InterfaceDeclarationReflection {
|
||||
return isDeclarationReflection(value) && value.kindOf(ReflectionKind.Interface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for a {@linkcode ExternalDriverDeclarationReflection} which is the `ExternalDriver`
|
||||
* interface defined in `@appium/types`.
|
||||
* @param value any value
|
||||
*/
|
||||
export function isExternalDriverDeclarationReflection(
|
||||
value: any
|
||||
): value is ExternalDriverDeclarationReflection {
|
||||
return isInterfaceDeclarationReflection(value) && value.name === NAME_EXTERNAL_DRIVER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for an {@linkcode CommandMethodDeclarationReflection}, which is _potentially_ a method
|
||||
* for a command. Not all async methods in driver classes are mapped to commands, of course!
|
||||
*
|
||||
* A command method cannot be static, but it can be an actual (async) function or a reference to an
|
||||
* (async) function; just depends how the code is written. Either way, this asserts there's a
|
||||
* call signature returning a `Promise`.
|
||||
* @param value
|
||||
*/
|
||||
export function isCommandMethodDeclarationReflection(
|
||||
value: any
|
||||
): value is CommandMethodDeclarationReflection {
|
||||
if (
|
||||
!isDeclarationReflection(value) ||
|
||||
!value.kindOf(ReflectionKind.Method | ReflectionKind.Property) ||
|
||||
value.flags.isStatic
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const signatures = isReflectionType(value.type)
|
||||
? value.type.declaration.getAllSignatures()
|
||||
: value.getAllSignatures();
|
||||
return Boolean(
|
||||
signatures?.find((sig) => sig.type instanceof ReferenceType && sig.type.name === 'Promise')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for a {@linkcode ClassDeclarationReflection} which is just a {@linkcode DeclarationReflection}
|
||||
* with a `kind` of {@linkcode ReflectionKind.Class}.
|
||||
* @param value any value
|
||||
*/
|
||||
export function isClassDeclarationReflection(value: any): value is ClassDeclarationReflection {
|
||||
return Boolean(isDeclarationReflection(value) && value.kindOf(ReflectionKind.Class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for a {@linkcode CallSignatureReflection} which is just a
|
||||
* {@linkcode SignatureReflection} with kind {@linkcode ReflectionKind.CallSignature}.
|
||||
* @param value any value
|
||||
*/
|
||||
export function isCallSignatureReflection(value: any): value is CallSignatureReflection {
|
||||
return Boolean(
|
||||
value instanceof SignatureReflection && value.kindOf(ReflectionKind.CallSignature)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for a {@linkcode CallSignatureReflectionWithArity}, which is a
|
||||
* {@linkcode CallSignatureReflection} with an arity greater than zero.
|
||||
* @param value any value
|
||||
*/
|
||||
export function isCallSignatureReflectionWithArity(
|
||||
value: any
|
||||
): value is CallSignatureReflectionWithArity {
|
||||
return Boolean(isCallSignatureReflection(value) && value.parameters?.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Guard for {@linkcode ConstructorDeclarationReflection}
|
||||
* @param value any
|
||||
*/
|
||||
export function isConstructorDeclarationReflection(
|
||||
value: any
|
||||
): value is ConstructorDeclarationReflection {
|
||||
return isDeclarationReflection(value) && value.kindOf(ReflectionKind.Constructor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Guard for the constructor of a class extending `BasePlugin`
|
||||
* @param value
|
||||
*/
|
||||
export function isBasePluginConstructorDeclarationReflection(
|
||||
value: any
|
||||
): value is BasePluginConstructorDeclarationReflection {
|
||||
if (!(isDeclarationReflection(value) && value.kindOf(ReflectionKind.Constructor))) {
|
||||
return false;
|
||||
}
|
||||
const ref =
|
||||
value.inheritedFrom instanceof ReferenceType
|
||||
? value.inheritedFrom
|
||||
: value.overwrites instanceof ReferenceType
|
||||
? value.overwrites
|
||||
: 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;
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
/**
|
||||
* Contains the {@link load entry point} for `@appium/typedoc-plugin-appium`
|
||||
* @module
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import pluralize from 'pluralize';
|
||||
import {Application, Context, Converter, DeclarationReflection} from 'typedoc';
|
||||
import {
|
||||
convertCommands,
|
||||
createReflections,
|
||||
omitBuiltinReflections,
|
||||
omitDefaultReflections,
|
||||
} from './converter';
|
||||
import {AppiumPluginLogger, AppiumPluginParentLogger} from './logger';
|
||||
import {ExtensionReflection, NS, ProjectCommands} from './model';
|
||||
import {configureOptions, declarations} from './options';
|
||||
import {configureTheme, THEME_NAME} from './theme';
|
||||
|
||||
let log: AppiumPluginLogger;
|
||||
|
||||
/**
|
||||
* Loads the Appium TypeDoc plugin.
|
||||
*
|
||||
* @param app - TypeDoc Application
|
||||
* @returns Unused by TypeDoc, but can be consumed programmatically.
|
||||
*/
|
||||
export function load(
|
||||
app: Application
|
||||
): Promise<[PromiseSettledResult<ConvertResult>, PromiseSettledResult<PostProcessResult>]> {
|
||||
// register our custom theme. the user still has to choose it
|
||||
setup(app);
|
||||
|
||||
// TypeDoc does not expect a return value here, but it's useful for testing.
|
||||
// note that this runs both methods "in parallel", but the `convert` method will always resolve
|
||||
// first, and `postProcess` won't do any real work until that happens.
|
||||
return Promise.allSettled([convert(app), postProcess(app)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers theme and options, then monkeys with the options
|
||||
*/
|
||||
export const setup: (app: Application) => Application = _.flow(configureTheme, configureOptions);
|
||||
|
||||
/**
|
||||
* Finds commands and creates new reflections for them, adding them to the project.
|
||||
*
|
||||
* 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_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);
|
||||
|
||||
// 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;
|
||||
}
|
||||
// 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});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolved value of {@linkcode convert}
|
||||
*/
|
||||
export interface ConvertResult {
|
||||
/**
|
||||
* Context at time of {@linkcode Context.EVENT_RESOLVE_END}
|
||||
*/
|
||||
ctx: Context;
|
||||
/**
|
||||
* Raw data structure containing everything about commands in the project
|
||||
*/
|
||||
projectCommands?: ProjectCommands;
|
||||
/**
|
||||
* List of custom reflections created by the plugin
|
||||
*/
|
||||
extensionReflections?: ExtensionReflection[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally omits the default TypeDoc reflections from the project based on the `outputModules` option.
|
||||
*
|
||||
* Resolves after {@linkcode Converter.EVENT_RESOLVE_END} emits and when it's finished.
|
||||
* @param app Typedoc application
|
||||
* @returns Typedoc `Context` at the time of the {@linkcode Converter.EVENT_RESOLVE_END} event
|
||||
*/
|
||||
export async function postProcess(app: Application): Promise<PostProcessResult> {
|
||||
log = log ?? new AppiumPluginLogger(app.logger, NS);
|
||||
return new Promise((resolve) => {
|
||||
app.converter.once(Converter.EVENT_RESOLVE_END, (ctx: Context) => {
|
||||
let removed: Set<DeclarationReflection> | undefined;
|
||||
// if the `outputModules` option is false, then we want to remove all the usual TypeDoc reflections.
|
||||
if (!app.options.getValue(declarations.outputModules.name)) {
|
||||
removed = omitDefaultReflections(ctx.project);
|
||||
log.info('%s omitted from output', pluralize('default reflection', removed.size, true));
|
||||
}
|
||||
if (!app.options.getValue(declarations.outputBuiltinCommands.name)) {
|
||||
const removedBuiltinRefls = omitBuiltinReflections(ctx.project);
|
||||
removed = new Set([...(removed ?? []), ...removedBuiltinRefls]);
|
||||
log.info(
|
||||
'%s omitted from output',
|
||||
pluralize('builtin reflection', removedBuiltinRefls.size, true)
|
||||
);
|
||||
}
|
||||
resolve({ctx, removed});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of {@linkcode postProcess}
|
||||
*/
|
||||
export interface PostProcessResult {
|
||||
/**
|
||||
* A list of {@linkcode DeclarationReflection DeclarationReflections} which were removed from the
|
||||
* project, if any.
|
||||
*/
|
||||
removed?: Set<DeclarationReflection>;
|
||||
/**
|
||||
* Context at time of {@linkcode Context.EVENT_RESOLVE_END}
|
||||
*/
|
||||
ctx: Context;
|
||||
}
|
||||
|
||||
export * from './options';
|
||||
export * from './theme';
|
||||
export type {AppiumPluginLogger, AppiumPluginParentLogger};
|
||||
@@ -1,199 +0,0 @@
|
||||
/**
|
||||
* 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 _ 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 {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
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.
|
||||
*/
|
||||
const LogMethods: Readonly<
|
||||
Map<LogLevel, keyof Pick<Logger, 'error' | 'warn' | 'info' | 'verbose'>>
|
||||
> = new Map([
|
||||
[LogLevel.Error, 'error'],
|
||||
[LogLevel.Warn, 'warn'],
|
||||
[LogLevel.Info, 'info'],
|
||||
[LogLevel.Verbose, 'verbose'],
|
||||
]);
|
||||
|
||||
export class AppiumPluginLogger extends Logger {
|
||||
/**
|
||||
* Function provided by `AppiumPluginLogger` parent loggers to log through them.
|
||||
*/
|
||||
readonly #logThroughParent?: AppiumPluginParentLogger;
|
||||
/**
|
||||
* Parent logger
|
||||
*/
|
||||
readonly #parent: Logger;
|
||||
|
||||
/**
|
||||
* Namespace to prepend to log messages
|
||||
*/
|
||||
public readonly ns: string;
|
||||
|
||||
public constructor(logger: Logger, ns: string, logThroughParent?: AppiumPluginParentLogger) {
|
||||
super();
|
||||
this.#parent = logger;
|
||||
this.ns = ns;
|
||||
this.level = this.#parent.level;
|
||||
this.#logThroughParent = logThroughParent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates or retrieves a child logger for the given namespace
|
||||
* @param parent Parent logger
|
||||
* @param ns Namespace
|
||||
* @returns Child logger
|
||||
*/
|
||||
static createChildLogger = _.memoize(
|
||||
(parent: AppiumPluginLogger, ns: string) => {
|
||||
const newLogger = new AppiumPluginLogger(
|
||||
parent.#parent,
|
||||
`${parent.ns}:${ns}`,
|
||||
parent.#logThrough.bind(parent)
|
||||
);
|
||||
newLogger.level = parent.level;
|
||||
return newLogger;
|
||||
},
|
||||
(parent: AppiumPluginLogger, ns: string) => `${parent.ns}:${ns}`
|
||||
);
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
return AppiumPluginLogger.createChildLogger(this, ns);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.get(level)!;
|
||||
this.#parent[parentMethod](this.#formatMessage(ns, message, ...args));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,42 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import {ModuleCommands, ProjectCommands, RouteMap} from '.';
|
||||
import {BaseDriverDeclarationReflection} from '../converter/types';
|
||||
|
||||
/**
|
||||
* This is essentially a {@linkcode ProjectCommands} object with zero or one entries.
|
||||
*
|
||||
* The entry is intended to be reflection for `@appium/base-driver and the associated
|
||||
* {@linkcode ModuleCommands} object.
|
||||
*
|
||||
* If no reflection is provided--even if the `RouteMap` is present--the result of
|
||||
* {@linkcode BuiltinCommands.toProjectCommands} will be empty (because there will be no key).
|
||||
*/
|
||||
export class BuiltinCommands {
|
||||
public readonly moduleCmds: ModuleCommands;
|
||||
|
||||
/**
|
||||
* Note that since `@appium/base-driver` contains no execute methods, the `ModuleCommands` object
|
||||
* won't either. If, in the future, `@appium/base-driver` contains execute methods, this constructor
|
||||
* will want to accept another argument _or_ just accept a `ModuleCommands` object in lieu of a
|
||||
* `RouteMap`/`ExecMethodDataSet`.
|
||||
* @param routeMap Builtin route map
|
||||
* @param refl `@appium/base-driver` `BaseDriverDeclarationReflection`
|
||||
*/
|
||||
constructor(
|
||||
routeMap: RouteMap = new Map(),
|
||||
public readonly refl?: BaseDriverDeclarationReflection
|
||||
) {
|
||||
this.moduleCmds = new ModuleCommands(routeMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this object to a {@linkcode ProjectCommands} object.
|
||||
*
|
||||
* Since the fields in this class are read-only, the contents of the `ProjectCommands` instance
|
||||
* will be invariant; thus we only need to create it once.
|
||||
* @returns A {@linkcode ProjectCommands} object with zero or one entry
|
||||
*/
|
||||
public toProjectCommands = _.once(
|
||||
() => new ProjectCommands(this.refl ? [[this.refl.name, this.moduleCmds]] : [])
|
||||
);
|
||||
}
|
||||
@@ -1,427 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
Comment,
|
||||
Context,
|
||||
DeclarationReflection,
|
||||
IntrinsicType,
|
||||
LiteralType,
|
||||
ParameterReflection,
|
||||
ReferenceType,
|
||||
SignatureReflection,
|
||||
} from 'typedoc';
|
||||
import {
|
||||
CallSignatureReflection,
|
||||
cloneCallSignatureReflection,
|
||||
CommandMethodDeclarationReflection,
|
||||
CommentSource,
|
||||
createNewParamRefls,
|
||||
deriveComment,
|
||||
Example,
|
||||
extractExamples,
|
||||
KnownMethods,
|
||||
} from '../converter';
|
||||
import {AppiumPluginLogger} from '../logger';
|
||||
import {findCallSignature} from '../utils';
|
||||
import {AllowedHttpMethod, Command, Route} from './types';
|
||||
|
||||
/**
|
||||
* Set of type names which should be converted to `null`
|
||||
* @see https://github.com/appium/appium/issues/18269
|
||||
*/
|
||||
const NULL_TYPES: Readonly<Set<string>> = new Set(['undefined', 'void']);
|
||||
|
||||
/**
|
||||
* Abstract representation of metadata for some sort of Appium command
|
||||
*/
|
||||
export abstract class BaseCommandData {
|
||||
/**
|
||||
* Loggher
|
||||
*/
|
||||
protected readonly log: AppiumPluginLogger;
|
||||
|
||||
/**
|
||||
* The method name of the command handler.
|
||||
*/
|
||||
public readonly command: string;
|
||||
/**
|
||||
* The comment to display for the command, if any exists
|
||||
*/
|
||||
public readonly comment?: Comment;
|
||||
/**
|
||||
* Code to describe how and where {@linkcode comment} came from.
|
||||
*
|
||||
* For debugging purposes mainly
|
||||
*/
|
||||
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;
|
||||
/**
|
||||
* Map of known builtin methods
|
||||
*/
|
||||
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[];
|
||||
/**
|
||||
* 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 readonly signature?: SignatureReflection;
|
||||
|
||||
constructor(
|
||||
log: AppiumPluginLogger,
|
||||
command: Command,
|
||||
methodRefl: CommandMethodDeclarationReflection,
|
||||
public readonly opts: BaseCommandDataOpts = {}
|
||||
) {
|
||||
this.command = command;
|
||||
this.methodRefl = methodRefl;
|
||||
this.log = log;
|
||||
|
||||
this.optionalParams = opts.optionalParams;
|
||||
this.requiredParams = opts.requiredParams;
|
||||
|
||||
this.commentSource = opts.commentSource;
|
||||
this.parentRefl = opts.parentRefl;
|
||||
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 = this.rewriteParameters();
|
||||
this.signature = this.rewriteSignature();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the method or execute map defined parameters for this command
|
||||
*/
|
||||
public get hasCommandParams(): boolean {
|
||||
return Boolean(this.optionalParams?.length || this.requiredParams?.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
*/
|
||||
private rewriteParameters(): ParameterReflection[] | undefined {
|
||||
if (!this.hasCommandParams) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sig = findCallSignature(this.methodRefl)!;
|
||||
if (!sig?.parameters?.length) {
|
||||
// no parameters
|
||||
return;
|
||||
}
|
||||
|
||||
const {knownBuiltinMethods: builtinMethods, isPluginCommand} = this;
|
||||
|
||||
const newParamRefls = [
|
||||
...createNewParamRefls(sig, {
|
||||
builtinMethods,
|
||||
commandParams: this.requiredParams,
|
||||
isPluginCommand,
|
||||
}),
|
||||
...createNewParamRefls(sig, {
|
||||
builtinMethods,
|
||||
commandParams: this.optionalParams,
|
||||
isPluginCommand,
|
||||
isOptional: true,
|
||||
}),
|
||||
];
|
||||
|
||||
return newParamRefls;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Rewrites a method's return value for documentation.
|
||||
*
|
||||
* Given a command having a method declaration, creates a clone of its call signature wherein the
|
||||
* return type is unwrapped from `Promise`. In other words, if a method returns `Promise<T>`,
|
||||
* this changes the return type in the signature to `T`.
|
||||
*
|
||||
* Note that the return type of a command's method declaration should always be a `ReferenceType` having
|
||||
* name `Promise`.
|
||||
*/
|
||||
private rewriteSignature(): CallSignatureReflection | undefined {
|
||||
const callSig = findCallSignature(this.methodRefl);
|
||||
if (!callSig) {
|
||||
return;
|
||||
}
|
||||
if (callSig.type instanceof ReferenceType && 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.
|
||||
let typeArg = _.first(callSig.type.typeArguments)!;
|
||||
|
||||
// swaps `void`/`undefined` for `null`
|
||||
if (typeArg instanceof IntrinsicType && NULL_TYPES.has(typeArg.name)) {
|
||||
typeArg = new LiteralType(null);
|
||||
}
|
||||
|
||||
const newCallSig = cloneCallSignatureReflection(callSig, this.methodRefl, typeArg);
|
||||
|
||||
if (!newCallSig.type) {
|
||||
this.log.warn(
|
||||
'(%s) No type arg T found for return type Promise<T> in %s; this is a bug',
|
||||
this.parentRefl!.name,
|
||||
this.methodRefl.name
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
newCallSig.comment = deriveComment({
|
||||
refl: newCallSig,
|
||||
knownMethods: this.knownBuiltinMethods,
|
||||
})?.comment;
|
||||
|
||||
return newCallSig;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@linkcode CommandData} and {@linkcode ExecMethodData} constructors
|
||||
*/
|
||||
export interface BaseCommandDataOpts {
|
||||
/**
|
||||
* The comment to display for the command, if any exists
|
||||
*/
|
||||
comment?: Comment;
|
||||
/**
|
||||
* Name of the reference which the comment is derived from.
|
||||
*
|
||||
* For debugging purposes mainly
|
||||
*/
|
||||
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;
|
||||
/**
|
||||
* List of required parameter names derived from a method map
|
||||
*/
|
||||
requiredParams?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a generic WD or Appium-specific endpoint
|
||||
*/
|
||||
export class CommandData extends BaseCommandData {
|
||||
/**
|
||||
* The HTTP method of the route
|
||||
*/
|
||||
public readonly httpMethod: AllowedHttpMethod;
|
||||
/**
|
||||
* The route of the command
|
||||
*/
|
||||
public readonly route: Route;
|
||||
|
||||
/**
|
||||
* Use {@linkcode CommandData.create} instead
|
||||
*/
|
||||
private constructor(
|
||||
log: AppiumPluginLogger,
|
||||
command: Command,
|
||||
methodRefl: CommandMethodDeclarationReflection,
|
||||
httpMethod: AllowedHttpMethod,
|
||||
route: Route,
|
||||
opts: BaseCommandDataOpts = {}
|
||||
) {
|
||||
super(log, command, methodRefl, opts);
|
||||
this.httpMethod = httpMethod;
|
||||
this.route = route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a **shallow** clone of this instance.
|
||||
*
|
||||
* @param commandData Instance to clone
|
||||
* @param ctx Context
|
||||
* @param overrides Override any properties of the instance here (including {@linkcode CommandData.opts})
|
||||
* @returns Cloned instance
|
||||
*/
|
||||
public static clone(commandData: CommandData, ctx: Context, overrides?: Partial<CommandData>) {
|
||||
const {log, command, methodRefl, httpMethod, route, opts} = _.defaults(overrides, {
|
||||
log: commandData.log,
|
||||
command: commandData.command,
|
||||
methodRefl: commandData.methodRefl,
|
||||
httpMethod: commandData.httpMethod,
|
||||
route: commandData.route,
|
||||
opts: _.defaults(overrides?.opts, commandData.opts),
|
||||
});
|
||||
return CommandData.create(ctx, log, command, methodRefl, httpMethod, route, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@linkcode CommandData} and registers any newly-created reflections
|
||||
* with TypeDoc.
|
||||
* @param ctx Context
|
||||
* @param log Logger
|
||||
* @param command Command name
|
||||
* @param methodRefl Command method reflection
|
||||
* @param httpMethod HTTP method of route
|
||||
* @param route Route path
|
||||
* @param opts Options
|
||||
* @returns
|
||||
*/
|
||||
public static create(
|
||||
ctx: Context,
|
||||
log: AppiumPluginLogger,
|
||||
command: Command,
|
||||
methodRefl: CommandMethodDeclarationReflection,
|
||||
httpMethod: AllowedHttpMethod,
|
||||
route: Route,
|
||||
opts: BaseCommandDataOpts = {}
|
||||
): CommandData {
|
||||
const commandData = new CommandData(log, command, methodRefl, httpMethod, route, opts);
|
||||
|
||||
if (commandData.signature) {
|
||||
ctx.registerReflection(commandData.signature, undefined);
|
||||
}
|
||||
|
||||
if (commandData.parameters) {
|
||||
for (const param of commandData.parameters) {
|
||||
ctx.registerReflection(param, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
return commandData;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an "execute command" ("execute method")
|
||||
*
|
||||
* Each will have a unique `script` property which is provided as the script to run via the
|
||||
* `execute` WD endpoint.
|
||||
*
|
||||
* All of these share the same `execute` route, so it is omitted from this interface.
|
||||
*/
|
||||
export class ExecMethodData extends BaseCommandData {
|
||||
/**
|
||||
* Use {@linkcode ExecMethodData.create} instead
|
||||
* @param log Logger
|
||||
* @param command Command name
|
||||
* @param methodRefl method reflection
|
||||
* @param script Script name (not the same as command name); this is what is passed to the `execute` endpoint
|
||||
*/
|
||||
private constructor(
|
||||
log: AppiumPluginLogger,
|
||||
command: Command,
|
||||
methodRefl: CommandMethodDeclarationReflection,
|
||||
public readonly script: string,
|
||||
public readonly opts: BaseCommandDataOpts = {}
|
||||
) {
|
||||
super(log, command, methodRefl, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a **shallow** clone of this instance.
|
||||
*
|
||||
* @param execMethodData Instance to clone
|
||||
* @param ctx Context
|
||||
* @param overrides Override any properties of the instance here (including {@linkcode ExecMethodData.opts})
|
||||
* @returns Cloned instance
|
||||
*/
|
||||
public static clone(
|
||||
execMethodData: ExecMethodData,
|
||||
ctx: Context,
|
||||
overrides?: Partial<ExecMethodData>
|
||||
) {
|
||||
const {log, command, methodRefl, script, opts} = _.defaults(overrides, {
|
||||
log: execMethodData.log,
|
||||
command: execMethodData.command,
|
||||
methodRefl: execMethodData.methodRefl,
|
||||
script: execMethodData.script,
|
||||
opts: _.defaults(overrides?.opts, execMethodData.opts),
|
||||
});
|
||||
return ExecMethodData.create(ctx, log, command, methodRefl, script, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@linkcode CommandData} and registers any newly-created reflections
|
||||
* with TypeDoc.
|
||||
* @param ctx Context
|
||||
* @param log Logger
|
||||
* @param command Command name
|
||||
* @param script Script name
|
||||
* @param route Route path
|
||||
* @param opts Options
|
||||
*/
|
||||
public static create(
|
||||
ctx: Context,
|
||||
log: AppiumPluginLogger,
|
||||
command: Command,
|
||||
methodRefl: CommandMethodDeclarationReflection,
|
||||
script: string,
|
||||
opts: BaseCommandDataOpts = {}
|
||||
): ExecMethodData {
|
||||
const execMethodData = new ExecMethodData(log, command, methodRefl, script, opts);
|
||||
|
||||
if (execMethodData.signature) {
|
||||
ctx.registerReflection(execMethodData.signature, undefined);
|
||||
}
|
||||
|
||||
if (execMethodData.parameters) {
|
||||
for (const param of execMethodData.parameters) {
|
||||
ctx.registerReflection(param, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
return execMethodData;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
export * from './types';
|
||||
export * from './reflection';
|
||||
export * from './module-commands';
|
||||
export * from './command-data';
|
||||
export * from './project-commands';
|
||||
|
||||
/**
|
||||
* Namespace for whatever we need namespaces for.
|
||||
*/
|
||||
export const NS = 'appium';
|
||||
@@ -1,30 +0,0 @@
|
||||
import {Command, ExecMethodDataSet, Route, RouteMap} from './types';
|
||||
|
||||
/**
|
||||
* Data structure describing routes and commands for a particular module (which may be the entire project),
|
||||
* including execute methods (if any)
|
||||
*/
|
||||
export class ModuleCommands {
|
||||
public readonly routesByCommandName: Map<Command, Set<Route>>;
|
||||
constructor(
|
||||
public readonly routeMap: RouteMap = new Map(),
|
||||
public readonly execMethodDataSet: ExecMethodDataSet = new Set()
|
||||
) {
|
||||
this.routesByCommandName = new Map();
|
||||
|
||||
for (const [route, commandSet] of routeMap) {
|
||||
for (const {command} of commandSet) {
|
||||
const routes = this.routesByCommandName.get(command) ?? new Set();
|
||||
routes.add(route);
|
||||
this.routesByCommandName.set(command, routes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if this instance has some actual data
|
||||
*/
|
||||
public get hasData() {
|
||||
return Boolean(this.execMethodDataSet.size + this.routeMap.size);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import {ModuleCommands} from './module-commands';
|
||||
|
||||
/**
|
||||
* Represents all command data for a TypeDoc project, keyed by module/project name.
|
||||
*
|
||||
* This is a map which refuses to add values that have no command data.
|
||||
*
|
||||
* The key is later used to lookup the `Reflection` in the TypeDoc project context.
|
||||
*/
|
||||
export class ProjectCommands extends Map<string, ModuleCommands> {
|
||||
override set(moduleName: string, moduleCmds: ModuleCommands) {
|
||||
if (moduleCmds.hasData) {
|
||||
return super.set(moduleName, moduleCmds);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
get isEmpty() {
|
||||
return this.size === 0;
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
import {Comment, DeclarationReflection, ParameterReflection, SignatureReflection} from 'typedoc';
|
||||
import {CommandMethodDeclarationReflection, CommentSource, Example} from '../../converter';
|
||||
import {isExecMethodData} from '../../guards';
|
||||
import {CommandData, ExecMethodData} from '../command-data';
|
||||
import {AllowedHttpMethod, Route} from '../types';
|
||||
import {ExtensionReflection} from './extension';
|
||||
import {AppiumPluginReflectionKind} from './kind';
|
||||
/**
|
||||
* Execute Methods all have the same route.
|
||||
*/
|
||||
export const NAME_EXECUTE_ROUTE = '/session/:sessionId/execute';
|
||||
|
||||
/**
|
||||
* Execute methods all have the same HTTP method.
|
||||
*/
|
||||
export const HTTP_METHOD_EXECUTE = 'POST';
|
||||
|
||||
/**
|
||||
* A reflection containing data about a single command or execute method.
|
||||
*
|
||||
* 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[];
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Sets props depending on type of `data`
|
||||
* @param data Command or execute method data
|
||||
* @param parent Always a {@linkcode ExtensionReflection}
|
||||
* @param route Route, if not an execute method
|
||||
*/
|
||||
constructor(
|
||||
readonly data: CommandData | ExecMethodData,
|
||||
parent: ExtensionReflection,
|
||||
route?: Route
|
||||
) {
|
||||
let name: string;
|
||||
let kind: AppiumPluginReflectionKind;
|
||||
let script: string | undefined;
|
||||
let httpMethod: AllowedHttpMethod;
|
||||
|
||||
// common data
|
||||
const {
|
||||
requiredParams,
|
||||
optionalParams,
|
||||
comment,
|
||||
methodRefl: refl,
|
||||
commentSource,
|
||||
parameters,
|
||||
signature,
|
||||
examples,
|
||||
command,
|
||||
} = data;
|
||||
|
||||
// kind-specific data
|
||||
if (isExecMethodData(data)) {
|
||||
script = name = data.script;
|
||||
kind = AppiumPluginReflectionKind.ExecuteMethod;
|
||||
route = NAME_EXECUTE_ROUTE;
|
||||
httpMethod = HTTP_METHOD_EXECUTE;
|
||||
} else {
|
||||
if (!route) {
|
||||
throw new TypeError('"route" arg is required for a non-execute-method command');
|
||||
}
|
||||
name = command;
|
||||
kind = AppiumPluginReflectionKind.Command;
|
||||
httpMethod = data.httpMethod;
|
||||
}
|
||||
|
||||
super(name, kind as any, parent);
|
||||
|
||||
this.route = route;
|
||||
this.httpMethod = httpMethod;
|
||||
this.requiredParams = requiredParams ?? [];
|
||||
this.optionalParams = optionalParams ?? [];
|
||||
this.script = script;
|
||||
this.refl = refl;
|
||||
this.commentSource = commentSource;
|
||||
this.parameters = parameters;
|
||||
this.signature = signature;
|
||||
this.examples = examples;
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* If `true`, this command contains one or more examples
|
||||
*
|
||||
* Used by templates
|
||||
*/
|
||||
public get hasExample(): boolean {
|
||||
return Boolean(this.examples?.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* If `true`, this command has optional parameters
|
||||
*
|
||||
* Used by templates
|
||||
*/
|
||||
public get hasOptionalParams(): boolean {
|
||||
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
|
||||
*
|
||||
* Used by templates
|
||||
*/
|
||||
public get isExecuteMethod(): boolean {
|
||||
return this.kindOf(AppiumPluginReflectionKind.ExecuteMethod as any);
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import {DeclarationReflection, ProjectReflection} from 'typedoc';
|
||||
import {NAME_BUILTIN_COMMAND_MODULE} from '../../converter';
|
||||
import {ModuleCommands} from '../module-commands';
|
||||
import {ExecMethodDataSet, ParentReflection, RouteMap} from '../types';
|
||||
import {AppiumPluginReflectionKind} from './kind';
|
||||
|
||||
const SCOPED_PACKAGE_NAME_REGEX = /^(@[^/]+)\/([^/]+)$/;
|
||||
|
||||
/**
|
||||
* A reflection containing data about commands and/or execute methods.
|
||||
*
|
||||
* Methods may be invoked directly by Handlebars templates.
|
||||
*/
|
||||
export class ExtensionReflection extends DeclarationReflection {
|
||||
/**
|
||||
* Info about execute methods
|
||||
*/
|
||||
public readonly execMethodDataSet: ExecMethodDataSet;
|
||||
/**
|
||||
* Info about routes/commands
|
||||
*/
|
||||
public readonly routeMap: RouteMap;
|
||||
|
||||
public override hasOwnDocument = true;
|
||||
|
||||
/**
|
||||
* Cached value of {@linkcode ExtensionReflection.getAlias}
|
||||
*/
|
||||
#alias: string | undefined;
|
||||
|
||||
readonly #title: string | undefined;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param name Name of module containing commands
|
||||
* @param parent Module containing commands
|
||||
* @param moduleCommands Data containing commands and execute methods
|
||||
* @param title Title if provided in options
|
||||
* @todo Determine the kind by inspecting `package.json` or smth
|
||||
*/
|
||||
constructor(
|
||||
name: string,
|
||||
parent: ParentReflection,
|
||||
moduleCommands: ModuleCommands,
|
||||
title?: string
|
||||
) {
|
||||
const {routeMap, execMethodDataSet} = moduleCommands;
|
||||
const kind = (
|
||||
name.includes('driver')
|
||||
? AppiumPluginReflectionKind.Driver
|
||||
: name.includes('plugin')
|
||||
? AppiumPluginReflectionKind.Plugin
|
||||
: AppiumPluginReflectionKind.Extension
|
||||
) as any;
|
||||
super(name, kind, parent);
|
||||
this.parent = parent;
|
||||
this.routeMap = routeMap;
|
||||
this.execMethodDataSet = execMethodDataSet;
|
||||
this.#title = title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the project contains more than one `ExtensionReflection` that isn't built-in commands.
|
||||
*
|
||||
* Should **not** be called before the TypeDoc converter has emitted `EVENT_RESOLVE_END`
|
||||
*/
|
||||
static isCompositeProject = _.memoize(
|
||||
(project: ProjectReflection) =>
|
||||
project
|
||||
.getChildrenByKind(AppiumPluginReflectionKind.Extension as any)
|
||||
?.filter(({name}) => name !== NAME_BUILTIN_COMMAND_MODULE).length > 1
|
||||
);
|
||||
|
||||
/**
|
||||
* This is called by `AppiumTheme`'s `getUrl` method, which causes a particular filename to be used.
|
||||
*
|
||||
* - The name of an `ExtensionReflection` is the name of the module containing commands
|
||||
* - If that name is a _scoped package name_, we strip the scope and delimiter
|
||||
* - Replaces any non-alphanumeric characters with `-`
|
||||
* @returns The "alias", whatever that means
|
||||
*/
|
||||
public override getAlias(): string {
|
||||
if (this.#alias) {
|
||||
return this.#alias;
|
||||
}
|
||||
|
||||
let alias: string;
|
||||
const matches = this.name.match(SCOPED_PACKAGE_NAME_REGEX);
|
||||
alias = matches ? matches[2] : this.name;
|
||||
alias = alias.replace(/\W/, '-');
|
||||
this.#alias = alias;
|
||||
return alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the title for display. Used as the first-level header for rendered page.
|
||||
*/
|
||||
public get extensionTitle(): string {
|
||||
if (this.#title) {
|
||||
return this.#title;
|
||||
}
|
||||
if (ExtensionReflection.isCompositeProject(this.project)) {
|
||||
return `${this.name} Commands`;
|
||||
}
|
||||
return 'Commands';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of execute methods in this data
|
||||
*/
|
||||
public get execMethodCount(): number {
|
||||
return this.execMethodDataSet.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if there are any "execute commands" in this set.
|
||||
*
|
||||
* Used by templates
|
||||
*/
|
||||
public get hasExecuteMethod(): boolean {
|
||||
return Boolean(this.execMethodCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if there are any "regular" commands in this set.
|
||||
*
|
||||
* Used by templates
|
||||
*/
|
||||
public get hasRoute(): boolean {
|
||||
return Boolean(this.routeCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of routes ("commands") in this in this data
|
||||
*/
|
||||
public get routeCount(): number {
|
||||
return this.routeMap.size;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from './command';
|
||||
export * from './extension';
|
||||
export * from './kind';
|
||||
@@ -1,53 +0,0 @@
|
||||
/**
|
||||
* Declares new "kinds" within TypeDoc.
|
||||
*
|
||||
* A "kind" is a way for TypeDoc to understand how to document something. Mostly, these have a 1:1 relationship with some sort of TypeScript concept. This is unsuitable for our purposes, since there's no notion of a "command" or "execute method" in TypeScript. To that end, we must create new ones.
|
||||
*
|
||||
* Note that _creating new `ReflectionKind`s is a hack_ and is not supported by TypeDoc. This is the reason you will see `as any` wherever a {@linkcode AppiumPluginReflectionKind} is used.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
export enum AppiumPluginReflectionKind {
|
||||
Driver = addReflectionKind('Driver'),
|
||||
Plugin = addReflectionKind('Plugin'),
|
||||
Command = addReflectionKind('Command'),
|
||||
ExecuteMethod = addReflectionKind('ExecuteMethod'),
|
||||
Extension = addReflectionKind('Extension', Driver | Plugin),
|
||||
Any = addReflectionKind('Any', Command | ExecuteMethod | Driver | Plugin),
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import {DeclarationReflection, ProjectReflection} from 'typedoc';
|
||||
import {CommandData, ExecMethodData} from './command-data';
|
||||
|
||||
/**
|
||||
* WD spec allows these HTTP methods.
|
||||
*/
|
||||
export type AllowedHttpMethod = 'GET' | 'POST' | 'DELETE';
|
||||
/**
|
||||
* An Express-style "route", otherwise known as a "URI Template"
|
||||
*
|
||||
* Aliased for intent
|
||||
*/
|
||||
export type Route = string;
|
||||
|
||||
/**
|
||||
* A method name in `BaseDriver` or subclass thereof
|
||||
*
|
||||
* Currently must be a string, but could theoretically be a {@link PropertyKey}
|
||||
*
|
||||
* Aliased for intent
|
||||
*/
|
||||
export type Command = string;
|
||||
|
||||
/**
|
||||
* All commands for a route.
|
||||
*/
|
||||
export type CommandSet = Set<CommandData>;
|
||||
|
||||
/**
|
||||
* A reflection which can be the parent of a {@linkcode ExtensionReflection}
|
||||
*/
|
||||
export type ParentReflection = DeclarationReflection | ProjectReflection;
|
||||
|
||||
/**
|
||||
* A map of routes to {@linkcode CommandSet} maps
|
||||
*/
|
||||
export type RouteMap = Map<Route, CommandSet>;
|
||||
|
||||
/**
|
||||
* A set of {@linkcode ExecMethodData} objects
|
||||
*/
|
||||
export type ExecMethodDataSet = Set<ExecMethodData>;
|
||||
@@ -1,61 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import {DeclarationOption, ParameterType} from 'typedoc';
|
||||
import {NS} from '../model';
|
||||
|
||||
export type EntryPointTitleRecord = Record<string, string>;
|
||||
|
||||
/**
|
||||
* List of options for the plugin
|
||||
* @internal
|
||||
*/
|
||||
export const declarations = {
|
||||
commandsDir: {
|
||||
defaultValue: 'commands',
|
||||
help: `(${NS}) Name of "commands" directory under the TypeDoc output directory. Not a full path`,
|
||||
name: 'commandsDir',
|
||||
type: ParameterType.String,
|
||||
},
|
||||
forceBreadcrumbs: {
|
||||
defaultValue: false,
|
||||
help: `(${NS}) Force breadcrumbs to be shown; overrides "hideBreadcrumbs"`,
|
||||
name: 'forceBreadcrumbs',
|
||||
type: ParameterType.Boolean,
|
||||
},
|
||||
outputBuiltinCommands: {
|
||||
defaultValue: false,
|
||||
help: `(${NS}) Output builtin commands`,
|
||||
name: 'outputBuiltinCommands',
|
||||
type: ParameterType.Boolean,
|
||||
},
|
||||
outputModules: {
|
||||
defaultValue: true,
|
||||
help: `(${NS}) Output modules APIs in addition to commands. This is needed for complete typing information`,
|
||||
name: 'outputModules',
|
||||
type: ParameterType.Boolean,
|
||||
},
|
||||
packageTitles: {
|
||||
defaultValue: [],
|
||||
help: `(${NS}) An array of objects with props "name" (module name) and "title" (display name)`,
|
||||
name: 'packageTitles',
|
||||
type: ParameterType.Mixed,
|
||||
validate(val: unknown) {
|
||||
if (!isPackageTitles(val)) {
|
||||
throw new Error(
|
||||
`Invalid value for "packageTitles" option; must be an array of objects with props "name" (module name) and "title" (display name): ${val}`
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export function isPackageTitle(value: any): value is PackageTitle {
|
||||
return _.isPlainObject(value) && _.isString(value.name) && _.isString(value.title);
|
||||
}
|
||||
|
||||
export function isPackageTitles(value: any): value is PackageTitle[] {
|
||||
return _.isArray(value) && value.every(isPackageTitle);
|
||||
}
|
||||
export type PackageTitle = {name: string; title: string};
|
||||
|
||||
// type sanity check
|
||||
declarations as Record<string, DeclarationOption>;
|
||||
@@ -1,34 +0,0 @@
|
||||
import {Application, ParameterType} from 'typedoc';
|
||||
import {AppiumPluginOptionsReader} from './reader';
|
||||
import {declarations} from './declarations';
|
||||
import {AppiumPluginLogger} from '../logger';
|
||||
|
||||
export * from './reader';
|
||||
export {declarations};
|
||||
|
||||
/**
|
||||
* Configures how this plugin handles TypeDoc options.
|
||||
* @param app TypeDoc Application
|
||||
*/
|
||||
export function configureOptions(app: Application): Application {
|
||||
const log = new AppiumPluginLogger(app.logger, 'options');
|
||||
|
||||
// for evil
|
||||
app.options.addReader(new AppiumPluginOptionsReader(log));
|
||||
|
||||
// add our custom options
|
||||
for (const declaration of Object.values(declarations)) {
|
||||
app.options.addDeclaration(declaration);
|
||||
}
|
||||
return app;
|
||||
}
|
||||
|
||||
type OptionDeclarations = typeof declarations;
|
||||
|
||||
export type AppiumPluginOptions = {
|
||||
[O in keyof OptionDeclarations]: OptionDeclarations[O]['type'] extends ParameterType.Boolean
|
||||
? boolean
|
||||
: OptionDeclarations[O]['type'] extends ParameterType.String
|
||||
? string
|
||||
: unknown;
|
||||
};
|
||||
@@ -1,173 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import path from 'node:path';
|
||||
import {EntryPointStrategy, Options, OptionsReader} from 'typedoc';
|
||||
import {AppiumPluginLogger} from '../logger';
|
||||
import {THEME_NAME} from '../theme';
|
||||
import {PackageTitle} from './declarations';
|
||||
|
||||
/**
|
||||
* List of theme names to override.
|
||||
*
|
||||
* `default` is what happens if the user does not specify a theme. The markdown plugin,
|
||||
* if loaded, will overwrite `default` with `markdown`, so we'll have to overwrite it again.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const OVERRIDE_THEME_NAMES: Readonly<Set<string>> = new Set(['default', 'markdown']);
|
||||
|
||||
/**
|
||||
* These packages must be resolvable for the plugin to work at all.
|
||||
* @internal
|
||||
*/
|
||||
const REQUIRED_PACKAGES: Readonly<Set<string>> = new Set(['@appium/base-driver', '@appium/types']);
|
||||
|
||||
/**
|
||||
* This befouls the options.
|
||||
*
|
||||
* It can do what has been undone and undo what has been done. It can make real your dreams... or nightmares.
|
||||
*/
|
||||
export class AppiumPluginOptionsReader implements OptionsReader {
|
||||
readonly #log: AppiumPluginLogger;
|
||||
|
||||
/**
|
||||
* I don't know the point of `name`, but the interface requires it, so here.
|
||||
*/
|
||||
public readonly name = 'naughty-appium-options-reader';
|
||||
/**
|
||||
* This needs to be higher than the value in `MarkdownOptionsReader`.
|
||||
*/
|
||||
public readonly priority = 2000;
|
||||
|
||||
constructor(logger: AppiumPluginLogger) {
|
||||
this.#log = logger.createChildLogger('options-reader');
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to derive a title (for use in theme output) from a package's `package.json` if that package is an Appium extension
|
||||
* @param pkgJsonPath Path to a `package.json`
|
||||
*/
|
||||
public static getTitleFromPackageJson(pkgJsonPath: string): string | undefined {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const pkg = require(pkgJsonPath);
|
||||
return pkg?.appium?.driverName ?? pkg?.appium?.pluginName;
|
||||
} catch {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls various private methods to override option values or provide defaults.
|
||||
* @param container - Options container
|
||||
*/
|
||||
public read(container: Options) {
|
||||
this.#configureTheme(container);
|
||||
this.#configureEntryPointStrategy(container);
|
||||
this.#configureEntryPoints(container);
|
||||
this.#configurePackages(container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the `entryPointStrategy` option to be {@linkcode EntryPointStrategy.Packages}
|
||||
* @param container Options
|
||||
*/
|
||||
#configureEntryPointStrategy(container: Options) {
|
||||
const entryPointStrategy = container.getValue('entryPointStrategy');
|
||||
if (entryPointStrategy !== EntryPointStrategy.Packages) {
|
||||
container.setValue('entryPointStrategy', EntryPointStrategy.Packages);
|
||||
this.#log.verbose('Set option "entryPointStrategy" to "%s"', EntryPointStrategy.Packages);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds required packages to the `entryPoints` option.
|
||||
*
|
||||
* If the `entryPoints` option already contains something that _looks like_ a
|
||||
* {@linkcode REQUIRED_PACKAGES required package}, then it is validated via
|
||||
* `require.resolve`. If this fails, it is replaced with the proper package path.
|
||||
*
|
||||
* If a required package cannot be resolved, an error occurs
|
||||
* @param container Options
|
||||
*/
|
||||
#configureEntryPoints(container: Options) {
|
||||
let entryPoints = container.getValue('entryPoints');
|
||||
const newEntryPoints = new Set(entryPoints);
|
||||
|
||||
const addEntryPoint = (entryPoint: string) => {
|
||||
try {
|
||||
const entryPointPath = path.dirname(require.resolve(`${entryPoint}/package.json`));
|
||||
newEntryPoints.add(entryPointPath);
|
||||
this.#log.verbose('Added %s to "entryPoint" option', entryPointPath);
|
||||
} catch (err) {
|
||||
this.#log.error('Could not find required package "%s"', entryPoint);
|
||||
}
|
||||
};
|
||||
|
||||
for (const reqdEntryPoint of REQUIRED_PACKAGES) {
|
||||
const foundReqdEP = entryPoints.find((entryPoint) => entryPoint.includes(reqdEntryPoint));
|
||||
if (foundReqdEP) {
|
||||
try {
|
||||
require.resolve(foundReqdEP);
|
||||
this.#log.verbose('entryPoint %s already exists (%s)', reqdEntryPoint, foundReqdEP);
|
||||
} catch {
|
||||
newEntryPoints.delete(foundReqdEP);
|
||||
addEntryPoint(reqdEntryPoint);
|
||||
this.#log.warn(
|
||||
'"entryPoint" option item matching required package "%s" is invalid or missing (%s); it was replaced',
|
||||
reqdEntryPoint,
|
||||
foundReqdEP
|
||||
);
|
||||
}
|
||||
} else {
|
||||
addEntryPoint(reqdEntryPoint);
|
||||
}
|
||||
}
|
||||
|
||||
entryPoints = [...newEntryPoints];
|
||||
container.setValue('entryPoints', entryPoints);
|
||||
this.#log.verbose('Final value of "entryPoints" option: %O', entryPoints);
|
||||
}
|
||||
|
||||
#configurePackages(container: Options) {
|
||||
let pkgTitles = container.getValue('packageTitles') as PackageTitle[];
|
||||
const entryPoints = container.getValue('entryPoints');
|
||||
|
||||
const newPkgTitles: PackageTitle[] = [];
|
||||
|
||||
for (const entryPoint of entryPoints) {
|
||||
const pkgJsonPath = require.resolve(`${entryPoint}/package.json`);
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const pkg = require(pkgJsonPath);
|
||||
const {name} = pkg;
|
||||
let title: string | undefined;
|
||||
if (pkg.appium?.driverName) {
|
||||
title = `Driver: ${pkg.appium.driverName}`;
|
||||
} else if (pkg.appium?.pluginName) {
|
||||
title = `Plugin: ${pkg.appium.pluginName}`;
|
||||
}
|
||||
|
||||
if (title && !_.find(pkgTitles, {name})) {
|
||||
newPkgTitles.push({name, title});
|
||||
}
|
||||
} catch {
|
||||
this.#log.warn('Could not resolve package.json for %s', entryPoint);
|
||||
}
|
||||
}
|
||||
|
||||
pkgTitles = [...pkgTitles, ...newPkgTitles];
|
||||
container.setValue('packageTitles', pkgTitles);
|
||||
this.#log.verbose('Final value of "packageTitles" option: %O', pkgTitles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the theme to be {@linkcode THEME_NAME}
|
||||
* @param container Options
|
||||
*/
|
||||
#configureTheme(container: Options) {
|
||||
if (OVERRIDE_THEME_NAMES.has(container.getValue('theme'))) {
|
||||
container.setValue('theme', THEME_NAME);
|
||||
this.#log.verbose('Set option "theme" to "appium"');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/**
|
||||
* Custom Handlebars helpers
|
||||
* @module
|
||||
*/
|
||||
|
||||
import Handlebars from 'handlebars';
|
||||
import {PageEvent, ContainerReflection, ReflectionKind} from 'typedoc';
|
||||
import {AppiumPluginReflectionKind} from '../model';
|
||||
import plural from 'pluralize';
|
||||
|
||||
/**
|
||||
* Overwrites {@linkcode typedoc-plugin-markdown#MarkdownTheme}'s `reflectionPath` helper to handle {@linkcode AppiumPluginReflectionKind} reflection kinds
|
||||
* @param this Page event
|
||||
* @returns Reflection path, if any
|
||||
*/
|
||||
function reflectionPath(this: PageEvent<ContainerReflection>) {
|
||||
if (this.model) {
|
||||
if (this.model.kind && this.model.kind !== ReflectionKind.Module) {
|
||||
if (this.model.kind === (AppiumPluginReflectionKind.Driver as any)) {
|
||||
return `${this.model.name} Driver`;
|
||||
} else if (this.model.kind === (AppiumPluginReflectionKind.Plugin as any)) {
|
||||
return `${this.model.name} Plugin`;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to "pluralize" a string.
|
||||
* @param value String to pluralize
|
||||
* @param count Number of items to consider
|
||||
* @param inclusive Whether to show the count in the output
|
||||
* @returns The pluralized string (if necessary)
|
||||
*/
|
||||
function pluralize(value: string, count: number, inclusive = false) {
|
||||
const safeValue = Handlebars.escapeExpression(value);
|
||||
// XXX: Handlebars seems to be passing in a truthy value here, even if the arg is unused in the template! Make double-sure it's a boolean.
|
||||
inclusive = inclusive === true;
|
||||
const pluralValue = plural(safeValue, count, inclusive);
|
||||
return new Handlebars.SafeString(pluralValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all custom helpers with Handlebars
|
||||
*/
|
||||
export function registerHelpers() {
|
||||
Handlebars.registerHelper('reflectionPath', reflectionPath);
|
||||
Handlebars.registerHelper('pluralize', pluralize);
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
Application,
|
||||
ContainerReflection,
|
||||
PageEvent,
|
||||
Reflection,
|
||||
ReflectionKind,
|
||||
Renderer,
|
||||
} from 'typedoc';
|
||||
import {MarkdownTheme} from 'typedoc-plugin-markdown';
|
||||
import {AppiumPluginLogger} from '../logger';
|
||||
import {AppiumPluginReflectionKind, NS} from '../model';
|
||||
import {AppiumPluginOptions, declarations} from '../options';
|
||||
import {registerHelpers} from './helpers';
|
||||
import {AppiumThemeTemplate, compileTemplate} from './template';
|
||||
|
||||
/**
|
||||
* Name of the theme; used at definition time
|
||||
*/
|
||||
export const THEME_NAME = 'appium';
|
||||
|
||||
/**
|
||||
* This theme uses everything from `MarkdownTheme` and adds a new section for commands.
|
||||
*/
|
||||
export class AppiumTheme extends MarkdownTheme {
|
||||
/**
|
||||
* A template renderer for `CommandReflection`s
|
||||
*/
|
||||
#extensionTemplateRenderer: TemplateRenderer;
|
||||
/**
|
||||
* Custom logger. This is not the same as the one created by the plugin loader.
|
||||
*/
|
||||
#log: AppiumPluginLogger;
|
||||
|
||||
/**
|
||||
* Options specific to this plugin
|
||||
*/
|
||||
#opts: AppiumPluginOptions;
|
||||
|
||||
/**
|
||||
* Creates template renderers and registers all {@linkcode Handlebars} helpers.
|
||||
* @param renderer - TypeDoc renderer
|
||||
*
|
||||
* @todo Use declaration merging to add an instance of `AppiumPluginLogger` to `Application`,
|
||||
* which we can then reference here.
|
||||
*/
|
||||
constructor(renderer: Renderer) {
|
||||
super(renderer);
|
||||
|
||||
this.#log = new AppiumPluginLogger(renderer.owner.logger, `${NS}:theme`);
|
||||
|
||||
this.#extensionTemplateRenderer = this.#createTemplateRenderer(AppiumThemeTemplate.Extension);
|
||||
|
||||
this.#opts = _.fromPairs(
|
||||
Object.keys(declarations).map((name) => [[name, this.application.options.getValue(name)]])
|
||||
) as AppiumPluginOptions;
|
||||
|
||||
/**
|
||||
* We do not want to show breadcrumbs by default, but `MarkdownTheme` does. We cannot override
|
||||
* default value of the `hideBreadcrumbs` option, but we can add a new one, which is what we've done;
|
||||
* if the `forceBreadcrumbs` option is not truthy, then we will hide breadcrumbs.
|
||||
*/
|
||||
this.hideBreadcrumbs = !this.#opts.forceBreadcrumbs;
|
||||
|
||||
// this ensures we can overwrite MarkdownTheme's Handlebars helpers
|
||||
registerHelpers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Special-cases `ExtensionReflection` instances to make the filename shorter and thus better
|
||||
* suitable for `mkdocs`.
|
||||
* @param reflection Reflection to get URL for
|
||||
* @returns String URL
|
||||
*/
|
||||
public override getUrl(reflection: Reflection) {
|
||||
if (reflection.kindOf(AppiumPluginReflectionKind.Extension as any)) {
|
||||
// I don't know what this replace is for, but the superclass does it, so maybe it's important.
|
||||
return reflection.getAlias().replace('^_', '');
|
||||
}
|
||||
return super.getUrl(reflection);
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* A lookup of {@linkcode ReflectionKind}s to templates. It also controls in which directory the output files live.
|
||||
*
|
||||
* This is part of {@linkcode MarkdownTheme} and adds a new template.
|
||||
*
|
||||
* If `isLeaf` is `false`, the model gets its own document.
|
||||
*/
|
||||
public override get mappings(): TemplateMapping[] {
|
||||
return [
|
||||
{
|
||||
kind: [AppiumPluginReflectionKind.Extension as any],
|
||||
isLeaf: false,
|
||||
directory: this.application.options.getValue(declarations.commandsDir.name) as string,
|
||||
template: this.#extensionTemplateRenderer,
|
||||
},
|
||||
...super.mappings,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a {@linkcode AppiumThemeTemplate} return a function which will render the template
|
||||
* given some data.
|
||||
* @param template Template to render
|
||||
* @returns Rendering function
|
||||
*/
|
||||
#createTemplateRenderer(template: AppiumThemeTemplate): 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},
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A function which accepts {@linkcode PageEvent} as its model and returns the final markdown.
|
||||
*/
|
||||
export type TemplateRenderer = (pageEvent: PageEvent<ContainerReflection>) => string;
|
||||
|
||||
/**
|
||||
* A mapping of `ReflectionKind` to a template and other metadata.
|
||||
*
|
||||
* Defined by `MarkdownTheme`.
|
||||
*/
|
||||
export type TemplateMapping = {
|
||||
kind: ReflectionKind[];
|
||||
isLeaf: boolean;
|
||||
directory: string;
|
||||
template: (pageEvent: PageEvent<ContainerReflection>) => string;
|
||||
};
|
||||
|
||||
export type {AppiumThemeTemplate};
|
||||
|
||||
/**
|
||||
* Registers {@linkcode AppiumTheme} with TypeDoc.
|
||||
* @param app TypeDoc application
|
||||
*/
|
||||
export function configureTheme(app: Application): Application {
|
||||
app.renderer.defineTheme(THEME_NAME, AppiumTheme);
|
||||
return app;
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
{{#unless hasOwnDocument}}
|
||||
|
||||
### {{#ifNamedAnchors}} <a id="{{anchor}}" name="{{this.anchor}}"></a> {{/ifNamedAnchors}}`{{name}}`
|
||||
|
||||
`{{httpMethod}}` **`{{route}}`**
|
||||
|
||||
{{#if hasComment}}
|
||||
{{> comment}}
|
||||
<!-- comment source: {{commentSource}} -->
|
||||
{{/if}}
|
||||
|
||||
{{#if parameters}}
|
||||
|
||||
#### Parameters
|
||||
|
||||
{{#with parameters}}
|
||||
|
||||
{{{parameterTable}}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{else}}
|
||||
{{! the required/optional parameters bits are fallbacks }}
|
||||
{{#if hasRequiredParams}}
|
||||
|
||||
##### Required Parameters
|
||||
|
||||
{{#each requiredParams}}
|
||||
- `{{this}}`
|
||||
{{/each}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#if hasOptionalParams}}
|
||||
|
||||
##### Optional Parameters
|
||||
|
||||
{{#each optionalParams}}
|
||||
- `{{this}}`
|
||||
{{/each}}
|
||||
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if hasExample}}
|
||||
#### {{pluralize 'Example' examples.length}}
|
||||
|
||||
<!-- BEGIN:EXAMPLES -->
|
||||
{{#each examples}}
|
||||
##### {{lang}}
|
||||
<!-- BEGIN:EXAMPLE lang={{lang}} -->
|
||||
|
||||
{{{text}}}
|
||||
|
||||
<!-- END:EXAMPLE -->
|
||||
{{/each}}
|
||||
<!-- END:EXAMPLES -->
|
||||
{{/if}}
|
||||
{{#if signature}}
|
||||
|
||||
#### Response
|
||||
|
||||
{{#with signature}}
|
||||
|
||||
{{#with type}}
|
||||
|
||||
{{{type 'all'}}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{#with comment}}
|
||||
|
||||
{{{returns this}}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{#with type}}
|
||||
|
||||
{{#if declaration.signatures}}
|
||||
|
||||
{{#each declaration.signatures}}
|
||||
|
||||
{{> member.signature showSources=false }}
|
||||
|
||||
{{/each}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{/with}}
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
@@ -1,95 +0,0 @@
|
||||
{{#unless hasOwnDocument}}
|
||||
|
||||
### {{#ifNamedAnchors}} <a id="{{anchor}}" name="{{this.anchor}}"></a> {{/ifNamedAnchors}}`{{name}}`
|
||||
|
||||
{{#if hasComment}}
|
||||
{{> comment}}
|
||||
<!-- comment source: {{commentSource}} -->
|
||||
{{/if}}
|
||||
|
||||
#### Route
|
||||
|
||||
`{{httpMethod}} {{route}}`
|
||||
|
||||
{{#if parameters}}
|
||||
|
||||
#### Parameters
|
||||
|
||||
{{#with parameters}}
|
||||
|
||||
{{{parameterTable}}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{else}}
|
||||
{{! the required/optional parameters bits are fallbacks }}
|
||||
{{#if hasRequiredParams}}
|
||||
|
||||
##### Required Parameters
|
||||
|
||||
{{#each requiredParams}}
|
||||
- `{{this}}`
|
||||
{{/each}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#if hasOptionalParams}}
|
||||
|
||||
##### Optional Parameters
|
||||
|
||||
{{#each optionalParams}}
|
||||
- `{{this}}`
|
||||
{{/each}}
|
||||
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if hasExample}}
|
||||
#### {{pluralize 'Example' examples.length}}
|
||||
|
||||
<!-- BEGIN:EXAMPLES -->
|
||||
{{#each examples}}
|
||||
##### {{lang}}
|
||||
<!-- BEGIN:EXAMPLE lang={{lang}} -->
|
||||
|
||||
{{{text}}}
|
||||
|
||||
<!-- END:EXAMPLE -->
|
||||
{{/each}}
|
||||
<!-- END:EXAMPLES -->
|
||||
{{/if}}
|
||||
{{#if signature}}
|
||||
|
||||
#### Response
|
||||
|
||||
{{#with signature}}
|
||||
|
||||
{{#with type}}
|
||||
|
||||
{{{type 'all'}}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{#with comment}}
|
||||
|
||||
{{{returns this}}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{#with type}}
|
||||
|
||||
{{#if declaration.signatures}}
|
||||
|
||||
{{#each declaration.signatures}}
|
||||
|
||||
{{> member.signature showSources=false }}
|
||||
|
||||
{{/each}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{/with}}
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
@@ -1,35 +0,0 @@
|
||||
{{> header}}
|
||||
|
||||
{{#with model}}
|
||||
|
||||
# {{extensionTitle}}
|
||||
|
||||
{{#if hasComment}}
|
||||
|
||||
{{> comment}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{#with model}}
|
||||
|
||||
{{#if hasRoute}}
|
||||
## {{pluralize 'Command' routeCount}}
|
||||
{{#each children}}
|
||||
{{#unless isExecuteMethod}}
|
||||
{{> command}}
|
||||
{{/unless}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
|
||||
{{#if hasExecuteMethod}}
|
||||
## Execute {{pluralize 'Method' execMethodCount}}
|
||||
{{#each children}}
|
||||
{{#if isExecuteMethod}}
|
||||
{{> executeMethod}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
|
||||
{{/with}}
|
||||
@@ -1,63 +0,0 @@
|
||||
/**
|
||||
* Handlebars template & partial helpers
|
||||
* @module
|
||||
*/
|
||||
|
||||
import Handlebars from 'handlebars';
|
||||
import _ from 'lodash';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
/**
|
||||
* Path to resources directory, containing all templates and partials.
|
||||
*/
|
||||
const RESOURCES_PATH = path.join(__dirname, 'resources');
|
||||
|
||||
/**
|
||||
* Path to templates directory within {@linkcode RESOURCES_PATH}
|
||||
*/
|
||||
const TEMPLATE_PATH = path.join(RESOURCES_PATH, 'templates');
|
||||
|
||||
/**
|
||||
* Path to partials directory within {@linkcode RESOURCES_PATH}
|
||||
*/
|
||||
const PARTIALS_PATH = path.join(RESOURCES_PATH, 'partials');
|
||||
|
||||
/**
|
||||
* Enum of all available partials
|
||||
*/
|
||||
enum AppiumThemePartial {
|
||||
command = 'command.hbs',
|
||||
executeMethod = 'execute-method.hbs',
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum of all available templates
|
||||
*/
|
||||
export enum AppiumThemeTemplate {
|
||||
/**
|
||||
* Template to render a list of commands
|
||||
*/
|
||||
Extension = 'extension.hbs',
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all partials found in {@linkcode PARTIALS_PATH} with {@linkcode Handlebars}.
|
||||
*
|
||||
* This is executed immediately upon loading this module.
|
||||
*/
|
||||
function registerPartials() {
|
||||
for (const [name, filename] of Object.entries(AppiumThemePartial)) {
|
||||
Handlebars.registerPartial(name, fs.readFileSync(path.join(PARTIALS_PATH, filename), 'utf8'));
|
||||
}
|
||||
}
|
||||
|
||||
registerPartials();
|
||||
|
||||
/**
|
||||
* Compiles a {@linkcode AppiumThemeTemplate}.
|
||||
*/
|
||||
export const compileTemplate = _.memoize((template: AppiumThemeTemplate) => {
|
||||
const templatePath = path.join(TEMPLATE_PATH, template);
|
||||
return Handlebars.compile(fs.readFileSync(templatePath, 'utf8'));
|
||||
});
|
||||
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* Utils used across entire package
|
||||
* @module
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import {DeclarationReflection} from 'typedoc';
|
||||
import {isCallSignatureReflection, isReflectionWithReflectedType} 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"
|
||||
* This also works on DeclarationReflections that have a reflected type; in other words, some value that is the type of a function, but is not a function itself (such as a property assigned to a function).
|
||||
*/
|
||||
export const findCallSignature = _.memoize(
|
||||
(refl?: DeclarationReflection) =>
|
||||
refl?.getAllSignatures()?.find(isCallSignatureReflection) ??
|
||||
(isReflectionWithReflectedType(refl)
|
||||
? refl.type.declaration.getAllSignatures()?.find(isCallSignatureReflection)
|
||||
: undefined)
|
||||
);
|
||||
@@ -1,69 +0,0 @@
|
||||
{
|
||||
"name": "@appium/typedoc-plugin-appium",
|
||||
"version": "0.6.6",
|
||||
"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/typedoc-plugin-appium"
|
||||
},
|
||||
"keywords": [
|
||||
"typedoc-plugin",
|
||||
"typedocplugin",
|
||||
"typedoc-theme",
|
||||
"appium",
|
||||
"markdown"
|
||||
],
|
||||
"author": "https://github.com/appium",
|
||||
"types": "./build/lib/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "cpy lib/theme/resources build",
|
||||
"clean": "npx rimraf build",
|
||||
"test": "run-s test:unit",
|
||||
"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\" && mocha --timeout 20s \"test/e2e/theme/output.e2e.spec.ts\"",
|
||||
"test:smoke": "node ./index.js",
|
||||
"test:unit": "mocha --timeout 2s \"test/unit/**/*.spec.ts\""
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/appium/appium/issues"
|
||||
},
|
||||
"directories": {
|
||||
"lib": "lib",
|
||||
"test": "test"
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"lib",
|
||||
"build/lib",
|
||||
"build/resources",
|
||||
"resources",
|
||||
"tsconfig.json"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
|
||||
"npm": ">=8"
|
||||
},
|
||||
"typedoc": {
|
||||
"entryPoint": "./lib/index.ts"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"appium": "^2.0.0-beta.48",
|
||||
"typedoc": "~0.23.14",
|
||||
"typedoc-plugin-markdown": "3.14.0",
|
||||
"typedoc-plugin-resolve-crossmodule-references": "~0.3.3",
|
||||
"typescript": "^4.7.0 || ^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"handlebars": "4.7.8",
|
||||
"lodash": "4.17.21",
|
||||
"pluralize": "8.0.0",
|
||||
"type-fest": "3.13.1"
|
||||
},
|
||||
"gitHead": "8480a85ce2fa466360e0fb1a7f66628331907f02"
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
import {expect} from 'chai';
|
||||
import {createSandbox, SinonSandbox} from 'sinon';
|
||||
import {Context} from 'typedoc';
|
||||
import {
|
||||
BuiltinExternalDriverConverter,
|
||||
KnownMethods,
|
||||
NAME_EXTERNAL_DRIVER,
|
||||
NAME_TYPES_MODULE,
|
||||
} from '../../../lib/converter';
|
||||
import {AppiumPluginLogger} from '../../../lib/logger';
|
||||
import {initConverter, NAME_FAKE_DRIVER_MODULE} from '../helpers';
|
||||
describe('@appium/typedoc-plugin-appium', function () {
|
||||
describe('BuiltinExternalDriverConverter', function () {
|
||||
let sandbox: SinonSandbox;
|
||||
|
||||
beforeEach(function () {
|
||||
sandbox = createSandbox();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('constructor', function () {
|
||||
it('should instantiate a BuiltinExternalDriverConverter', function () {
|
||||
const ctx = sandbox.createStubInstance(Context);
|
||||
const log = sandbox.createStubInstance(AppiumPluginLogger);
|
||||
expect(new BuiltinExternalDriverConverter(ctx, log)).to.be.an.instanceof(
|
||||
BuiltinExternalDriverConverter
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('instance method', function () {
|
||||
describe('convert()', function () {
|
||||
let converter: BuiltinExternalDriverConverter;
|
||||
let knownMethods: KnownMethods;
|
||||
|
||||
before(async function () {
|
||||
converter = await initConverter(BuiltinExternalDriverConverter, NAME_TYPES_MODULE);
|
||||
knownMethods = converter.convert();
|
||||
});
|
||||
|
||||
it(`should find ${NAME_EXTERNAL_DRIVER}'s method declarations in ${NAME_TYPES_MODULE}`, async function () {
|
||||
expect(knownMethods.size).to.be.above(0);
|
||||
});
|
||||
|
||||
it(`should only work with ${NAME_TYPES_MODULE}`, async function () {
|
||||
const badConverter = await initConverter(
|
||||
BuiltinExternalDriverConverter,
|
||||
NAME_FAKE_DRIVER_MODULE
|
||||
);
|
||||
expect(badConverter.convert()).to.be.empty;
|
||||
});
|
||||
|
||||
it(`should contain methods in ${NAME_EXTERNAL_DRIVER}`, async function () {
|
||||
converter = await initConverter(BuiltinExternalDriverConverter, NAME_TYPES_MODULE);
|
||||
knownMethods = converter.convert();
|
||||
expect(knownMethods.size).to.be.above(0);
|
||||
let method = knownMethods.get('createSession')!;
|
||||
expect(method).to.exist;
|
||||
method = knownMethods.get('activateApp')!;
|
||||
expect(method).to.exist;
|
||||
method = knownMethods.get('executeCdp')!;
|
||||
expect(method).to.exist;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,121 +0,0 @@
|
||||
import {expect} from 'chai';
|
||||
import _ from 'lodash';
|
||||
import {createSandbox, SinonSandbox} from 'sinon';
|
||||
import {Comment, Context} from 'typedoc';
|
||||
import {
|
||||
BuiltinExternalDriverConverter,
|
||||
BuiltinMethodMapConverter,
|
||||
KnownMethods,
|
||||
NAME_BUILTIN_COMMAND_MODULE,
|
||||
NAME_TYPES_MODULE,
|
||||
} from '../../../lib/converter';
|
||||
import {AppiumPluginLogger} from '../../../lib/logger';
|
||||
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 () {
|
||||
let sandbox: SinonSandbox;
|
||||
|
||||
beforeEach(function () {
|
||||
sandbox = createSandbox();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('constructor', function () {
|
||||
it('should instantiate a BuiltinMethodMapConverter', function () {
|
||||
const knownMethods: KnownMethods = new Map();
|
||||
const ctx = sandbox.createStubInstance(Context);
|
||||
const log = sandbox.createStubInstance(AppiumPluginLogger);
|
||||
expect(new BuiltinMethodMapConverter(ctx, log, knownMethods)).to.be.an.instanceof(
|
||||
BuiltinMethodMapConverter
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('instance method', function () {
|
||||
describe('convert()', function () {
|
||||
describe('when provided the correct module', function () {
|
||||
let builtinSource: BuiltinCommands;
|
||||
let converter: BuiltinMethodMapConverter;
|
||||
|
||||
before(async function () {
|
||||
const knownMethods = (
|
||||
await initConverter(BuiltinExternalDriverConverter, NAME_TYPES_MODULE)
|
||||
).convert();
|
||||
converter = await initConverter(
|
||||
BuiltinMethodMapConverter,
|
||||
NAME_BUILTIN_COMMAND_MODULE,
|
||||
{extraArgs: [knownMethods]}
|
||||
);
|
||||
builtinSource = converter.convert()!;
|
||||
});
|
||||
|
||||
it(`should find commands in ${NAME_BUILTIN_COMMAND_MODULE}`, async function () {
|
||||
expect(builtinSource).to.exist;
|
||||
});
|
||||
|
||||
it('should map a method name to a route', function () {
|
||||
expect(builtinSource.moduleCmds!.routesByCommandName.get('createSession')).to.eql(
|
||||
new Set(['/session'])
|
||||
);
|
||||
});
|
||||
|
||||
describe('command data', function () {
|
||||
let cmdData: CommandData;
|
||||
before(function () {
|
||||
cmdData = [...builtinSource.moduleCmds!.routeMap.get('/session')!.values()][0];
|
||||
});
|
||||
|
||||
it('should contain the expected properties in the getSession command data', function () {
|
||||
expect(
|
||||
_.omit(
|
||||
cmdData,
|
||||
'opts',
|
||||
'methodRefl',
|
||||
'parentRefl',
|
||||
'knownBuiltinMethods',
|
||||
'comment',
|
||||
'log',
|
||||
'parameters',
|
||||
'signature'
|
||||
)
|
||||
).to.eql({
|
||||
command: 'createSession',
|
||||
httpMethod: 'POST',
|
||||
commentSource: 'multiple',
|
||||
requiredParams: [],
|
||||
route: '/session',
|
||||
optionalParams: ['desiredCapabilities', 'requiredCapabilities', 'capabilities'],
|
||||
isPluginCommand: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should associate the command data for getSession with the createSession method', function () {
|
||||
expect(cmdData.methodRefl!.name).to.equal('createSession');
|
||||
});
|
||||
|
||||
it('should derive a comment for the getSession command', function () {
|
||||
expect(cmdData.comment).to.be.an.instanceof(Comment);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it(`should only work with ${NAME_BUILTIN_COMMAND_MODULE}`, async function () {
|
||||
const converter = await initConverter(
|
||||
BuiltinMethodMapConverter,
|
||||
NAME_FAKE_DRIVER_MODULE,
|
||||
{
|
||||
extraArgs: [new Map()],
|
||||
}
|
||||
);
|
||||
expect(converter.convert().toProjectCommands()).to.be.empty;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,45 +0,0 @@
|
||||
import {createSandbox, SinonSandbox} from 'sinon';
|
||||
import {Context, Converter} from 'typedoc';
|
||||
import {
|
||||
convertCommands,
|
||||
NAME_BUILTIN_COMMAND_MODULE,
|
||||
NAME_TYPES_MODULE,
|
||||
} from '../../../lib/converter';
|
||||
import {AppiumPluginLogger} from '../../../lib/logger';
|
||||
import {ProjectCommands} from '../../../lib/model';
|
||||
import {initAppForPkgs, NAME_FAKE_DRIVER_MODULE} from '../helpers';
|
||||
|
||||
const {expect} = chai;
|
||||
|
||||
describe('@appium/typedoc-plugin-appium', function () {
|
||||
describe('convertCommands()', function () {
|
||||
let sandbox: SinonSandbox;
|
||||
beforeEach(function () {
|
||||
sandbox = createSandbox();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
let ctx: Context;
|
||||
let log: AppiumPluginLogger;
|
||||
before(async function () {
|
||||
const app = initAppForPkgs({
|
||||
entryPoints: [NAME_TYPES_MODULE, NAME_FAKE_DRIVER_MODULE, NAME_BUILTIN_COMMAND_MODULE],
|
||||
});
|
||||
ctx = await new Promise((resolve) => {
|
||||
app.converter.once(Converter.EVENT_RESOLVE_END, (ctx: Context) => {
|
||||
resolve(ctx);
|
||||
});
|
||||
app.convert();
|
||||
});
|
||||
log = new AppiumPluginLogger(app.logger, 'appium-test');
|
||||
});
|
||||
|
||||
describe('convertCommands()', function () {
|
||||
it('should return a non-empty ProjectCommands Map', function () {
|
||||
expect(convertCommands(ctx, log)).to.be.an.instanceof(ProjectCommands).and.not.to.be.empty;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,152 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import {expect} from 'chai';
|
||||
import {createSandbox, SinonSandbox} from 'sinon';
|
||||
import {Comment, Context} from 'typedoc';
|
||||
import {
|
||||
BuiltinExternalDriverConverter,
|
||||
BuiltinMethodMapConverter,
|
||||
ExternalConverter,
|
||||
KnownMethods,
|
||||
NAME_BUILTIN_COMMAND_MODULE,
|
||||
NAME_TYPES_MODULE,
|
||||
} from '../../../lib/converter';
|
||||
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 () {
|
||||
let sandbox: SinonSandbox;
|
||||
|
||||
beforeEach(function () {
|
||||
sandbox = createSandbox();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('constructor', function () {
|
||||
it('should instantiate a ExternalConverter', function () {
|
||||
const knownMethods: KnownMethods = new Map();
|
||||
const ctx = sandbox.createStubInstance(Context);
|
||||
const log = sandbox.createStubInstance(AppiumPluginLogger);
|
||||
expect(
|
||||
new ExternalConverter(ctx, log, knownMethods, {} as ModuleCommands)
|
||||
).to.be.an.instanceof(ExternalConverter);
|
||||
});
|
||||
});
|
||||
|
||||
describe('instance method', function () {
|
||||
describe('convert()', function () {
|
||||
let externalDriverMethods: KnownMethods;
|
||||
let builtinCmdSrc: BuiltinCommands;
|
||||
|
||||
before(async function () {
|
||||
const bedConverter = await initConverter(
|
||||
BuiltinExternalDriverConverter,
|
||||
NAME_TYPES_MODULE
|
||||
);
|
||||
externalDriverMethods = bedConverter.convert();
|
||||
const bmmConverter = await initConverter(
|
||||
BuiltinMethodMapConverter,
|
||||
NAME_BUILTIN_COMMAND_MODULE,
|
||||
{extraArgs: [externalDriverMethods]}
|
||||
);
|
||||
builtinCmdSrc = bmmConverter.convert();
|
||||
});
|
||||
|
||||
describe('when run against an Appium extension', function () {
|
||||
let driverCmds: ProjectCommands;
|
||||
let fakeDriverCmds: ModuleCommands;
|
||||
let sessionCmdSet: CommandSet;
|
||||
|
||||
before(async function () {
|
||||
const converter = await initConverter(ExternalConverter, NAME_FAKE_DRIVER_MODULE, {
|
||||
extraArgs: [externalDriverMethods, builtinCmdSrc.moduleCmds],
|
||||
});
|
||||
driverCmds = converter.convert();
|
||||
|
||||
fakeDriverCmds = driverCmds.get(NAME_FAKE_DRIVER_MODULE)!;
|
||||
sessionCmdSet = fakeDriverCmds.routeMap.get('/session')!;
|
||||
});
|
||||
|
||||
it('should find commands', function () {
|
||||
expect(driverCmds).not.to.be.empty;
|
||||
});
|
||||
|
||||
it('should find commands in FakeDriver', function () {
|
||||
expect(fakeDriverCmds).to.exist;
|
||||
});
|
||||
|
||||
it('should find the createSession command', function () {
|
||||
expect(sessionCmdSet).to.exist;
|
||||
});
|
||||
|
||||
it('should find commands from the new method map', function () {
|
||||
expect(fakeDriverCmds.routeMap.get('/session/:sessionId/fakedriver')).to.have.lengthOf(
|
||||
2
|
||||
);
|
||||
});
|
||||
|
||||
it('should find commands in the execute method map', function () {
|
||||
const execCmds = [...fakeDriverCmds.execMethodDataSet];
|
||||
expect(execCmds.find((cmd) => cmd.script === 'fake: getThing')).to.exist;
|
||||
});
|
||||
|
||||
it('should use the summary from the driver instead of from builtins', function () {
|
||||
const postRoute = _.find([...sessionCmdSet], {httpMethod: 'POST'})!;
|
||||
|
||||
expect(Comment.combineDisplayParts(postRoute.comment!.summary)).to.equal(
|
||||
'Comment for `createSession` in `FakeDriver`'
|
||||
);
|
||||
});
|
||||
|
||||
it('should prefer method map parameters over method parameters', function () {
|
||||
const postRoute = _.find([...sessionCmdSet], {httpMethod: 'POST'})!;
|
||||
|
||||
// the method has 4 parameters, but the method map has 3
|
||||
expect(postRoute.parameters).to.have.lengthOf(3);
|
||||
|
||||
expect(postRoute.parameters![0])
|
||||
.to.deep.include({
|
||||
name: 'desiredCapabilities',
|
||||
})
|
||||
.and.to.have.nested.property('flags.isOptional', true);
|
||||
|
||||
expect(postRoute.parameters![1])
|
||||
.to.deep.include({
|
||||
name: 'requiredCapabilities',
|
||||
})
|
||||
.and.to.have.nested.property('flags.isOptional', true);
|
||||
expect(postRoute.parameters![2])
|
||||
.to.deep.include({
|
||||
name: 'capabilities',
|
||||
})
|
||||
.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;
|
||||
});
|
||||
});
|
||||
|
||||
describe('when run against a non-Appium-extension', function () {
|
||||
it(`should find no commands`, async function () {
|
||||
const converter = await initConverter(ExternalConverter, NAME_TYPES_MODULE, {
|
||||
extraArgs: [externalDriverMethods],
|
||||
});
|
||||
expect(converter.convert()).to.be.empty;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,187 +0,0 @@
|
||||
import path from 'node:path';
|
||||
import readPkg from 'read-pkg';
|
||||
import {Constructor, SetRequired} from 'type-fest';
|
||||
import {
|
||||
Application,
|
||||
Context,
|
||||
Converter,
|
||||
EntryPointStrategy,
|
||||
LogLevel,
|
||||
TSConfigReader,
|
||||
TypeDocOptions,
|
||||
} from 'typedoc';
|
||||
import {THEME_NAME, setup} from '../../lib';
|
||||
import {BaseConverter, NAME_BUILTIN_COMMAND_MODULE, NAME_TYPES_MODULE} from '../../lib/converter';
|
||||
import {AppiumPluginLogger} from '../../lib/logger';
|
||||
|
||||
const {expect} = chai;
|
||||
|
||||
const NAME_PACKAGE_JSON = 'package.json';
|
||||
|
||||
/**
|
||||
* Name of the fake driver package which is good for testing
|
||||
*/
|
||||
export const NAME_FAKE_DRIVER_MODULE = '@appium/fake-driver';
|
||||
|
||||
/**
|
||||
* Path to monorepo root tsconfig
|
||||
*/
|
||||
export const ROOT_TSCONFIG = path.join(__dirname, '..', '..', '..', '..', 'tsconfig.json');
|
||||
|
||||
/**
|
||||
* Finds entry point for a single package. To be used when the `entryPointStrategy` is `resolve`
|
||||
* @param pkgName Name of package
|
||||
* @returns Path to its entry point
|
||||
*/
|
||||
async function getEntryPoint(pkgName: string): Promise<string> {
|
||||
const pkgDir = path.dirname(require.resolve(`${pkgName}/${NAME_PACKAGE_JSON}`));
|
||||
if (!pkgDir) {
|
||||
throw new TypeError(`Could not find package ${pkgName}!`);
|
||||
}
|
||||
const pkg = await readPkg({cwd: pkgDir});
|
||||
expect(pkg.typedoc.entryPoint).to.exist;
|
||||
return path.resolve(pkgDir, pkg.typedoc.entryPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new TypeDoc application with some defaults.
|
||||
*
|
||||
* If `_FORCE_LOGS` is in the env, use verbose logging; otherwise logs are suppressed for your pleasure
|
||||
*
|
||||
* Note: if multiple entry points are provided, we assume that the `entryPointStrategy` should be `packages`
|
||||
*
|
||||
* @param opts - Opts
|
||||
* @returns New TypeDoc app
|
||||
* @todo Figure out how to get plugin-specific options into TypeDoc other than via `Options.setValue`
|
||||
*/
|
||||
function getTypedocApp(opts: Partial<TypeDocOptions> = {}): Application {
|
||||
const app = new Application();
|
||||
app.options.addReader(new TSConfigReader());
|
||||
|
||||
const forceLogs = Boolean(process.env._FORCE_LOGS);
|
||||
|
||||
const finalOpts = {
|
||||
excludeExternals: true,
|
||||
plugin: ['none'], // prevent any plugins from being auto-loaded
|
||||
logLevel: forceLogs ? LogLevel.Verbose : LogLevel.Info,
|
||||
logger: forceLogs ? undefined : 'none',
|
||||
entryPointStrategy:
|
||||
opts.entryPoints?.length ?? 0 > 1 ? EntryPointStrategy.Packages : EntryPointStrategy.Resolve,
|
||||
theme: THEME_NAME,
|
||||
skipErrorChecking: true,
|
||||
...opts,
|
||||
};
|
||||
|
||||
app.bootstrap(finalOpts);
|
||||
return app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs Typedoc against a single package
|
||||
* @param pkgName Name of package to get Application for
|
||||
* @returns TypeDoc application
|
||||
*/
|
||||
export async function initAppForPkg(
|
||||
pkgName: string,
|
||||
opts: Partial<TypeDocOptions> = {}
|
||||
): Promise<Application> {
|
||||
const entryPoint = await getEntryPoint(pkgName);
|
||||
const tsconfig = require.resolve(`${pkgName}/tsconfig.json`);
|
||||
const entryPointStrategy = EntryPointStrategy.Resolve;
|
||||
return getTypedocApp({...opts, tsconfig, entryPoints: [entryPoint], entryPointStrategy});
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs Typedoc against multiple packages (using `entryPointStrategy` of `packages`)
|
||||
* @param opts - Options; `entryPoints` is required
|
||||
* @returns Typedoc Application
|
||||
*/
|
||||
export function initAppForPkgs({
|
||||
tsconfig = ROOT_TSCONFIG,
|
||||
...opts
|
||||
}: SetRequired<Partial<TypeDocOptions>, 'entryPoints'>): Application {
|
||||
let {entryPoints} = opts;
|
||||
entryPoints = entryPoints.map((pkgName) =>
|
||||
path.dirname(require.resolve(`${pkgName}/${NAME_PACKAGE_JSON}`))
|
||||
);
|
||||
// because entryPoints is a list of directories, this must be 'packages'
|
||||
const entryPointStrategy = EntryPointStrategy.Packages;
|
||||
return getTypedocApp({...opts, tsconfig, entryPoints, entryPointStrategy});
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the conversion process and returns the instance of the converter class once it is ready.
|
||||
* @param app TypeDoc application
|
||||
* @param cls Converter class
|
||||
* @param extraArgs Extra args to `cls`' constructor
|
||||
* @returns Converter class instance
|
||||
*/
|
||||
async function convert<T, C extends BaseConverter<T>, Args extends readonly any[] = any[]>(
|
||||
app: Application,
|
||||
cls: ConverterConstructor<T, C, Args>,
|
||||
extraArgs?: Args
|
||||
): Promise<C> {
|
||||
return await new Promise((resolve, reject) => {
|
||||
const listener = (ctx: Context) => {
|
||||
const log = new AppiumPluginLogger(app.logger, `test-${cls.name}`);
|
||||
if (extraArgs?.length) {
|
||||
resolve(new cls(ctx, log, ...extraArgs));
|
||||
} else {
|
||||
resolve(new cls(ctx, log));
|
||||
}
|
||||
};
|
||||
app.converter.once(Converter.EVENT_RESOLVE_END, listener);
|
||||
try {
|
||||
app.convert();
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
} finally {
|
||||
app.converter.off(Converter.EVENT_RESOLVE_END, listener);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a new TypeDoc app for a single package, begins the conversion process but resolves
|
||||
* with the converter class instance once it is ready (with whatever TypeDoc already converted).
|
||||
* @param cls Converter class
|
||||
* @param pkgName Package to convert
|
||||
* @param opts Extra args to `cls`' constructor and typedoc options
|
||||
* @returns Converter class instance
|
||||
*/
|
||||
export async function initConverter<
|
||||
T,
|
||||
C extends BaseConverter<T>,
|
||||
Args extends readonly any[] = any[]
|
||||
>(
|
||||
cls: ConverterConstructor<T, C, Args>,
|
||||
pkgName: string,
|
||||
opts: InitConverterOptions<Args> = {}
|
||||
): Promise<C> {
|
||||
const {extraArgs, ...typeDocOpts} = opts;
|
||||
const app = await initAppForPkg(pkgName, typeDocOpts);
|
||||
|
||||
return await convert(app, cls, extraArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@linkcode initConverter}
|
||||
*/
|
||||
export interface InitConverterOptions<Args extends readonly any[] = any[]>
|
||||
extends Partial<TypeDocOptions> {
|
||||
extraArgs?: Args;
|
||||
}
|
||||
|
||||
type ConverterConstructor<T, C extends BaseConverter<T>, Args extends readonly any[] = any[]> =
|
||||
| Constructor<C, [Context, AppiumPluginLogger, ...Args]>
|
||||
| Constructor<C, [Context, AppiumPluginLogger]>;
|
||||
|
||||
/**
|
||||
* Creates a new TypeDoc application and/or resets it
|
||||
*/
|
||||
export function reset({
|
||||
entryPoints = [NAME_TYPES_MODULE, NAME_FAKE_DRIVER_MODULE, NAME_BUILTIN_COMMAND_MODULE],
|
||||
...opts
|
||||
}: Partial<TypeDocOptions> = {}): Application {
|
||||
return setup(initAppForPkgs({entryPoints, ...opts}));
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import {Context, DeclarationReflection, ProjectReflection, ReflectionKind} from 'typedoc';
|
||||
import {convert, ConvertResult, postProcess, PostProcessResult} from '../../lib';
|
||||
import {NAME_BUILTIN_COMMAND_MODULE, NAME_TYPES_MODULE} from '../../lib/converter';
|
||||
import {AppiumPluginReflectionKind, CommandReflection, ExtensionReflection} from '../../lib/model';
|
||||
import {reset} from './helpers';
|
||||
|
||||
const {expect} = chai;
|
||||
|
||||
describe('@appium/typedoc-plugin-appium', function () {
|
||||
/**
|
||||
* Result of {@linkcode convert}
|
||||
*/
|
||||
let resolveBeginCtxPromise: Promise<ConvertResult>;
|
||||
|
||||
/**
|
||||
* Result of {@linkcode postProcess}
|
||||
*/
|
||||
let resolveEndCtxPromise: Promise<PostProcessResult>;
|
||||
/**
|
||||
* Whatever {@linkcode Context} we're using to test
|
||||
*/
|
||||
let ctx: Context;
|
||||
|
||||
/**
|
||||
* Array of {@linkcode ExtensionReflection} instances as in a {@linkcode ConvertResult}
|
||||
*/
|
||||
let extensionReflections!: ExtensionReflection[];
|
||||
|
||||
describe('convert()', function () {
|
||||
describe('when "theme" is not "markdown" or "appium"', function () {
|
||||
it('should do nothing', async function () {
|
||||
const app = reset({theme: 'foo'});
|
||||
resolveBeginCtxPromise = convert(app);
|
||||
app.convert();
|
||||
await expect(resolveBeginCtxPromise).to.eventually.not.have.all.keys(
|
||||
'projectCommands',
|
||||
'extensionReflections'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when commands are found in the project', function () {
|
||||
before(async function () {
|
||||
const app = reset();
|
||||
resolveBeginCtxPromise = convert(app);
|
||||
app.convert();
|
||||
const result = await resolveBeginCtxPromise;
|
||||
extensionReflections = result.extensionReflections!;
|
||||
});
|
||||
|
||||
it('should create CommandReflections for each extension', function () {
|
||||
for (const extRefl of extensionReflections) {
|
||||
const cmdRefls = extRefl.getChildrenByKind(AppiumPluginReflectionKind.Command as any);
|
||||
expect(cmdRefls).to.not.be.empty;
|
||||
}
|
||||
});
|
||||
|
||||
it('should find examples', function () {
|
||||
const baseDriverRefl = extensionReflections.find(
|
||||
(refl) => refl.name === '@appium/base-driver'
|
||||
)!;
|
||||
expect(baseDriverRefl).to.exist;
|
||||
const cmdRefl = baseDriverRefl.getChildByName('getStatus')! as CommandReflection;
|
||||
expect(cmdRefl).to.have.property('comment').and.to.not.be.undefined;
|
||||
cmdRefl;
|
||||
expect(cmdRefl).to.have.property('hasExample', true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when no commands found in the project', function () {
|
||||
it('should return an object without projectCommands and extensionReflections', async function () {
|
||||
const app = reset({entryPoints: [NAME_TYPES_MODULE]});
|
||||
resolveBeginCtxPromise = convert(app);
|
||||
app.convert();
|
||||
await expect(resolveBeginCtxPromise).to.eventually.not.have.all.keys(
|
||||
'projectCommands',
|
||||
'extensionReflections'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('postProcess()', function () {
|
||||
let project: ProjectReflection;
|
||||
let removed: Set<DeclarationReflection> | undefined;
|
||||
|
||||
describe('when the `outputModules` option is false', function () {
|
||||
before(async function () {
|
||||
const app = reset();
|
||||
app.options.setValue('outputModules', false);
|
||||
resolveBeginCtxPromise = convert(app);
|
||||
resolveEndCtxPromise = postProcess(app);
|
||||
app.convert();
|
||||
[, {ctx, removed}] = await Promise.all([resolveBeginCtxPromise, resolveEndCtxPromise]);
|
||||
({project} = ctx);
|
||||
});
|
||||
|
||||
it('should mutate the project', function () {
|
||||
const childRefls = project.getChildrenByKind(AppiumPluginReflectionKind.Extension as any);
|
||||
expect(childRefls).to.have.lengthOf(project.children!.length).and.to.not.be.empty;
|
||||
expect(project.getChildrenByKind(ReflectionKind.Module)).to.be.empty;
|
||||
});
|
||||
|
||||
it('should remove DeclarationReflections', function () {
|
||||
expect(removed).not.to.be.empty;
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the `outputModules` option is true', function () {
|
||||
before(async function () {
|
||||
const app = reset();
|
||||
app.options.setValue('outputModules', true);
|
||||
app.options.setValue('outputBuiltinCommands', true); // do not pollute result
|
||||
resolveBeginCtxPromise = convert(app);
|
||||
resolveEndCtxPromise = postProcess(app);
|
||||
app.convert();
|
||||
[, {ctx, removed}] = await Promise.all([resolveBeginCtxPromise, resolveEndCtxPromise]);
|
||||
({project} = ctx);
|
||||
});
|
||||
|
||||
it('should not remove anything from the project', function () {
|
||||
const defaultRefls = project.getChildrenByKind(
|
||||
~(AppiumPluginReflectionKind.Extension as any)
|
||||
);
|
||||
expect(defaultRefls).to.not.be.empty;
|
||||
const pluginRefls = project.getChildrenByKind(AppiumPluginReflectionKind.Extension as any);
|
||||
expect(pluginRefls).to.not.be.empty;
|
||||
});
|
||||
|
||||
it('should not remove DeclarationReflections', function () {
|
||||
expect(removed).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the `outputBuiltinCommands` option is false', function () {
|
||||
before(async function () {
|
||||
const app = reset();
|
||||
app.options.setValue('outputModules', true); // as to not pollute the 'removed' set
|
||||
app.options.setValue('outputBuiltinCommands', false);
|
||||
resolveBeginCtxPromise = convert(app);
|
||||
resolveEndCtxPromise = postProcess(app);
|
||||
app.convert();
|
||||
[, {ctx, removed}] = await Promise.all([resolveBeginCtxPromise, resolveEndCtxPromise]);
|
||||
({project} = ctx);
|
||||
});
|
||||
|
||||
it('should remove the builtin commands', function () {
|
||||
expect(removed).to.have.lengthOf(1);
|
||||
});
|
||||
|
||||
it('should not output builtin commands', async function () {
|
||||
const extRefls = project.getChildrenByKind(AppiumPluginReflectionKind.Extension as any);
|
||||
expect(extRefls).to.not.be.empty;
|
||||
expect(_.find(extRefls, {name: NAME_BUILTIN_COMMAND_MODULE})).to.be.undefined;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,49 +0,0 @@
|
||||
import path from 'node:path';
|
||||
import {tempDir, fs} from 'appium/support';
|
||||
import {jestSnapshotPlugin} from 'mocha-chai-jest-snapshot';
|
||||
import {convert} from '../../../lib';
|
||||
import {reset} from '../helpers';
|
||||
|
||||
chai.use(jestSnapshotPlugin());
|
||||
|
||||
const {expect} = chai;
|
||||
|
||||
describe('@appium/typedoc-plugin-appium', function () {
|
||||
let tmpDir: string;
|
||||
before(async function () {
|
||||
const app = reset({
|
||||
plugin: ['typedoc-plugin-markdown', 'typedoc-plugin-crossmodule-references'],
|
||||
});
|
||||
const promise = convert(app);
|
||||
const project = app.convert();
|
||||
await promise;
|
||||
expect(project).to.exist;
|
||||
tmpDir = await tempDir.openDir();
|
||||
await app.generateDocs(project!, tmpDir);
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
await fs.rimraf(tmpDir);
|
||||
});
|
||||
|
||||
describe('theme', function () {
|
||||
describe('command output', function () {
|
||||
it('should generate expected markdown', async function () {
|
||||
const baseDriverMd = await fs.readFile(
|
||||
path.join(tmpDir, 'commands', 'base-driver.md'),
|
||||
'utf8'
|
||||
);
|
||||
expect(baseDriverMd).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
describe('execute method output', function () {
|
||||
it('should generate expected markdown', async function () {
|
||||
const fakeDriverMd = await fs.readFile(
|
||||
path.join(tmpDir, 'commands', 'fake-driver.md'),
|
||||
'utf8'
|
||||
);
|
||||
expect(fakeDriverMd).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,365 +0,0 @@
|
||||
import {expect} from 'chai';
|
||||
import {
|
||||
Comment,
|
||||
CommentTag,
|
||||
DeclarationReflection,
|
||||
ParameterReflection,
|
||||
ProjectReflection,
|
||||
ReferenceType,
|
||||
ReflectionKind,
|
||||
ReflectionType,
|
||||
SignatureReflection,
|
||||
} from 'typedoc';
|
||||
import {
|
||||
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 project: ProjectReflection;
|
||||
let knownMethods: KnownMethods;
|
||||
let methodRefl: CommandMethodDeclarationReflection;
|
||||
|
||||
describe('when not provided parameters', function () {
|
||||
it('should return undefined', function () {
|
||||
const commentData = deriveComment();
|
||||
expect(commentData).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided a CommandMethodDeclarationReflection', function () {
|
||||
beforeEach(function () {
|
||||
project = new ProjectReflection('my project');
|
||||
methodRefl = new DeclarationReflection(
|
||||
'test',
|
||||
ReflectionKind.Method,
|
||||
new ExtensionReflection('somedriver', project, new ModuleCommands())
|
||||
) as CommandMethodDeclarationReflection;
|
||||
});
|
||||
|
||||
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 DeclarationReflection which is of kind Property', function () {
|
||||
describe('when provided a reflection without a visible comment which has a reflection type of a declaration having a visible comment', function () {
|
||||
// Note: This is how appium-xcuitest-driver works (though is not generally a recommended strategy). Each method is a property on the driver which is assigned to a method contained in some mixin. The method has the docstring (or more precisely, its signature does).
|
||||
let otherComment: Comment;
|
||||
let otherRefl: DeclarationReflection;
|
||||
let propRefl: DeclarationReflection;
|
||||
beforeEach(function () {
|
||||
propRefl = new DeclarationReflection('from', ReflectionKind.Property);
|
||||
otherRefl = new DeclarationReflection('to', ReflectionKind.Method);
|
||||
propRefl.type = new ReflectionType(otherRefl);
|
||||
|
||||
const sigRefl = new SignatureReflection('to', ReflectionKind.CallSignature, otherRefl);
|
||||
otherRefl.signatures = [sigRefl];
|
||||
otherComment = new Comment([{kind: 'text', text: 'a description of the method'}]);
|
||||
sigRefl.comment = otherComment;
|
||||
});
|
||||
|
||||
it('should use the comment from the reference type declaration', function () {
|
||||
const {comment, commentSource} = deriveComment({refl: propRefl})!;
|
||||
expect(comment).to.eql(otherComment);
|
||||
expect(commentSource).to.equal(CommentSource.MethodSignature);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('cloneComment()', function () {
|
||||
/**
|
||||
* Asserts that `b` has all properties of `a` (recursively) and that each object in `a` is an
|
||||
* in `b`, and each primitive is strictly equal in both places.
|
||||
* @param a some thing
|
||||
* @param b another thing
|
||||
*/
|
||||
function assertDeepEqual(a: any, b: any): void {
|
||||
for (const [key, value] of Object.entries(a)) {
|
||||
if (typeof value === 'object') {
|
||||
expect(typeof b[key]).to.equal('object');
|
||||
return assertDeepEqual(value, b[key]);
|
||||
} else {
|
||||
expect(value).to.equal(b[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
describe('when not provided any blockTags', function () {
|
||||
it('should clone a Comment', function () {
|
||||
const comment = new Comment(
|
||||
[{kind: 'text', text: 'a funny comment'}],
|
||||
[new CommentTag('@example', [{kind: 'code', text: 'doStuff();'}])]
|
||||
);
|
||||
const cloned = cloneComment(comment);
|
||||
expect(assertDeepEqual(comment, cloned)).not.to.throw;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,126 +0,0 @@
|
||||
import {createSandbox, SinonSandbox} from 'sinon';
|
||||
import {
|
||||
DeclarationReflection,
|
||||
LiteralType,
|
||||
ReflectionKind,
|
||||
TupleType,
|
||||
TypeOperatorType,
|
||||
} from 'typedoc';
|
||||
import {
|
||||
convertCommandParams,
|
||||
MethodDefParamNamesDeclarationReflection,
|
||||
MethodDefParamsPropDeclarationReflection,
|
||||
NAME_OPTIONAL,
|
||||
NAME_PARAMS,
|
||||
NAME_REQUIRED,
|
||||
TupleTypeWithLiteralElements,
|
||||
TypeOperatorTypeWithTupleTypeWithLiteralElements,
|
||||
} from '../../../lib/converter';
|
||||
|
||||
const {expect} = chai;
|
||||
describe('@appium/typedoc-plugin-appium', function () {
|
||||
describe('utils', function () {
|
||||
let sandbox: SinonSandbox;
|
||||
|
||||
beforeEach(function () {
|
||||
sandbox = createSandbox();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('convertCommandParams()', function () {
|
||||
describe('when not provided a MethodDefParamsPropDeclarationReflection', function () {
|
||||
it('should return an empty array', function () {
|
||||
expect(convertCommandParams(NAME_REQUIRED)).to.be.empty.and.an('array');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided a MethodDefParamsPropDeclarationReflection', function () {
|
||||
let ref: MethodDefParamsPropDeclarationReflection;
|
||||
|
||||
beforeEach(function () {
|
||||
// this is fudged since there's no `type` prop of type `ReflectionType`
|
||||
ref = new DeclarationReflection(
|
||||
NAME_PARAMS,
|
||||
ReflectionKind.Property
|
||||
) as MethodDefParamsPropDeclarationReflection;
|
||||
});
|
||||
|
||||
describe('when the reflection has no MethodDefParamNamesDeclarationReflection children', function () {
|
||||
it('should return an empty array', function () {
|
||||
expect(convertCommandParams(NAME_REQUIRED, ref)).to.be.empty.and.an('array');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when reflection has "required" MethodDefParamNamesDeclarationReflection children', function () {
|
||||
beforeEach(function () {
|
||||
const propNameRef = new DeclarationReflection(
|
||||
NAME_REQUIRED,
|
||||
ReflectionKind.Property,
|
||||
ref
|
||||
) as MethodDefParamNamesDeclarationReflection;
|
||||
|
||||
// it appears `type` must be assigned manually
|
||||
propNameRef.type = new TypeOperatorType(
|
||||
new TupleType([
|
||||
new LiteralType('foo'),
|
||||
new LiteralType('bar'),
|
||||
new LiteralType('baz'),
|
||||
]) as TupleTypeWithLiteralElements,
|
||||
'readonly'
|
||||
) as TypeOperatorTypeWithTupleTypeWithLiteralElements;
|
||||
|
||||
ref.children = [propNameRef];
|
||||
});
|
||||
|
||||
describe('when converting "required" props', function () {
|
||||
it('should return a list of prop names', function () {
|
||||
expect(convertCommandParams(NAME_REQUIRED, ref)).to.eql(['foo', 'bar', 'baz']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when converting "optional" props', function () {
|
||||
it('should return an empty array', function () {
|
||||
expect(convertCommandParams(NAME_OPTIONAL, ref)).to.be.empty.and.an('array');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when reflection has "optional" MethodDefParamNamesDeclarationReflection children', function () {
|
||||
beforeEach(function () {
|
||||
const propNameRef = new DeclarationReflection(
|
||||
NAME_OPTIONAL,
|
||||
ReflectionKind.Property,
|
||||
ref
|
||||
) as MethodDefParamNamesDeclarationReflection;
|
||||
|
||||
propNameRef.type = new TypeOperatorType(
|
||||
new TupleType([
|
||||
new LiteralType('foo'),
|
||||
new LiteralType('bar'),
|
||||
new LiteralType('baz'),
|
||||
]) as TupleTypeWithLiteralElements,
|
||||
'readonly'
|
||||
) as TypeOperatorTypeWithTupleTypeWithLiteralElements;
|
||||
|
||||
ref.children = [propNameRef];
|
||||
});
|
||||
|
||||
describe('when converting "optional" props', function () {
|
||||
it('should return a list of prop names', function () {
|
||||
expect(convertCommandParams(NAME_OPTIONAL, ref)).to.eql(['foo', 'bar', 'baz']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when converting "required" props', function () {
|
||||
it('should return an empty array', function () {
|
||||
expect(convertCommandParams(NAME_REQUIRED, ref)).to.be.empty.and.an('array');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"extends": "@appium/tsconfig/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": ".",
|
||||
"outDir": "build",
|
||||
"types": ["node", "mocha", "chai", "sinon-chai", "chai-as-promised"],
|
||||
"strict": true
|
||||
},
|
||||
"ts-node": {
|
||||
"transpileOnly": true,
|
||||
"files": true
|
||||
},
|
||||
"include": ["lib", "test"]
|
||||
}
|
||||
@@ -52,8 +52,5 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "8480a85ce2fa466360e0fb1a7f66628331907f02",
|
||||
"typedoc": {
|
||||
"entryPoint": "./lib/index.ts"
|
||||
}
|
||||
"gitHead": "8480a85ce2fa466360e0fb1a7f66628331907f02"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user