appium 2.0.0-beta.5 → 2.0.0-beta.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +156 -63
- package/build/lib/appium.d.ts +226 -0
- package/build/lib/appium.d.ts.map +1 -0
- package/build/lib/appium.js +694 -439
- package/build/lib/appium.js.map +1 -0
- package/build/lib/cli/args.d.ts +17 -0
- package/build/lib/cli/args.d.ts.map +1 -0
- package/build/lib/cli/args.js +253 -319
- package/build/lib/cli/args.js.map +1 -0
- package/build/lib/cli/driver-command.d.ts +102 -0
- package/build/lib/cli/driver-command.d.ts.map +1 -0
- package/build/lib/cli/driver-command.js +126 -81
- package/build/lib/cli/driver-command.js.map +1 -0
- package/build/lib/cli/extension-command.d.ts +385 -0
- package/build/lib/cli/extension-command.d.ts.map +1 -0
- package/build/lib/cli/extension-command.js +731 -383
- package/build/lib/cli/extension-command.js.map +1 -0
- package/build/lib/cli/extension.d.ts +23 -0
- package/build/lib/cli/extension.d.ts.map +1 -0
- package/build/lib/cli/extension.js +71 -54
- package/build/lib/cli/extension.js.map +1 -0
- package/build/lib/cli/parser.d.ts +84 -0
- package/build/lib/cli/parser.d.ts.map +1 -0
- package/build/lib/cli/parser.js +240 -128
- package/build/lib/cli/parser.js.map +1 -0
- package/build/lib/cli/plugin-command.d.ts +99 -0
- package/build/lib/cli/plugin-command.d.ts.map +1 -0
- package/build/lib/cli/plugin-command.js +120 -81
- package/build/lib/cli/plugin-command.js.map +1 -0
- package/build/lib/cli/utils.d.ts +29 -0
- package/build/lib/cli/utils.d.ts.map +1 -0
- package/build/lib/cli/utils.js +72 -51
- package/build/lib/cli/utils.js.map +1 -0
- package/build/lib/config-file.d.ts +100 -0
- package/build/lib/config-file.d.ts.map +1 -0
- package/build/lib/config-file.js +207 -0
- package/build/lib/config-file.js.map +1 -0
- package/build/lib/config.d.ts +49 -0
- package/build/lib/config.d.ts.map +1 -0
- package/build/lib/config.js +265 -201
- package/build/lib/config.js.map +1 -0
- package/build/lib/constants.d.ts +49 -0
- package/build/lib/constants.d.ts.map +1 -0
- package/build/lib/constants.js +66 -0
- package/build/lib/constants.js.map +1 -0
- package/build/lib/extension/driver-config.d.ts +82 -0
- package/build/lib/extension/driver-config.d.ts.map +1 -0
- package/build/lib/extension/driver-config.js +212 -0
- package/build/lib/extension/driver-config.js.map +1 -0
- package/build/lib/extension/extension-config.d.ts +249 -0
- package/build/lib/extension/extension-config.d.ts.map +1 -0
- package/build/lib/extension/extension-config.js +581 -0
- package/build/lib/extension/extension-config.js.map +1 -0
- package/build/lib/extension/index.d.ts +48 -0
- package/build/lib/extension/index.d.ts.map +1 -0
- package/build/lib/extension/index.js +105 -0
- package/build/lib/extension/index.js.map +1 -0
- package/build/lib/extension/manifest-migrations.d.ts +27 -0
- package/build/lib/extension/manifest-migrations.d.ts.map +1 -0
- package/build/lib/extension/manifest-migrations.js +118 -0
- package/build/lib/extension/manifest-migrations.js.map +1 -0
- package/build/lib/extension/manifest.d.ts +145 -0
- package/build/lib/extension/manifest.d.ts.map +1 -0
- package/build/lib/extension/manifest.js +521 -0
- package/build/lib/extension/manifest.js.map +1 -0
- package/build/lib/extension/package-changed.d.ts +11 -0
- package/build/lib/extension/package-changed.d.ts.map +1 -0
- package/build/lib/extension/package-changed.js +62 -0
- package/build/lib/extension/package-changed.js.map +1 -0
- package/build/lib/extension/plugin-config.d.ts +56 -0
- package/build/lib/extension/plugin-config.d.ts.map +1 -0
- package/build/lib/extension/plugin-config.js +102 -0
- package/build/lib/extension/plugin-config.js.map +1 -0
- package/build/lib/grid-register.d.ts +10 -0
- package/build/lib/grid-register.d.ts.map +1 -0
- package/build/lib/grid-register.js +122 -144
- package/build/lib/grid-register.js.map +1 -0
- package/build/lib/logger.d.ts +3 -0
- package/build/lib/logger.d.ts.map +1 -0
- package/build/lib/logger.js +5 -17
- package/build/lib/logger.js.map +1 -0
- package/build/lib/logsink.d.ts +4 -0
- package/build/lib/logsink.d.ts.map +1 -0
- package/build/lib/logsink.js +190 -187
- package/build/lib/logsink.js.map +1 -0
- package/build/lib/main.d.ts +62 -0
- package/build/lib/main.d.ts.map +1 -0
- package/build/lib/main.js +348 -228
- package/build/lib/main.js.map +1 -0
- package/build/lib/schema/arg-spec.d.ts +143 -0
- package/build/lib/schema/arg-spec.d.ts.map +1 -0
- package/build/lib/schema/arg-spec.js +164 -0
- package/build/lib/schema/arg-spec.js.map +1 -0
- package/build/lib/schema/cli-args.d.ts +19 -0
- package/build/lib/schema/cli-args.d.ts.map +1 -0
- package/build/lib/schema/cli-args.js +217 -0
- package/build/lib/schema/cli-args.js.map +1 -0
- package/build/lib/schema/cli-transformers.d.ts +5 -0
- package/build/lib/schema/cli-transformers.d.ts.map +1 -0
- package/build/lib/schema/cli-transformers.js +124 -0
- package/build/lib/schema/cli-transformers.js.map +1 -0
- package/build/lib/schema/index.d.ts +3 -0
- package/build/lib/schema/index.d.ts.map +1 -0
- package/build/lib/schema/index.js +19 -0
- package/build/lib/schema/index.js.map +1 -0
- package/build/lib/schema/keywords.d.ts +24 -0
- package/build/lib/schema/keywords.d.ts.map +1 -0
- package/build/lib/schema/keywords.js +128 -0
- package/build/lib/schema/keywords.js.map +1 -0
- package/build/lib/schema/schema.d.ts +259 -0
- package/build/lib/schema/schema.d.ts.map +1 -0
- package/build/lib/schema/schema.js +615 -0
- package/build/lib/schema/schema.js.map +1 -0
- package/build/lib/utils.d.ts +121 -0
- package/build/lib/utils.d.ts.map +1 -0
- package/build/lib/utils.js +351 -273
- package/build/lib/utils.js.map +1 -0
- package/build/tsconfig.tsbuildinfo +1 -0
- package/build/types/cli.d.ts +134 -0
- package/build/types/cli.d.ts.map +1 -0
- package/build/types/cli.js +3 -0
- package/build/types/cli.js.map +1 -0
- package/build/types/index.d.ts +15 -0
- package/build/types/index.d.ts.map +1 -0
- package/build/types/index.js +19 -0
- package/build/types/index.js.map +1 -0
- package/build/types/manifest/base.d.ts +135 -0
- package/build/types/manifest/base.d.ts.map +1 -0
- package/build/types/manifest/base.js +3 -0
- package/build/types/manifest/base.js.map +1 -0
- package/build/types/manifest/index.d.ts +19 -0
- package/build/types/manifest/index.d.ts.map +1 -0
- package/build/types/manifest/index.js +40 -0
- package/build/types/manifest/index.js.map +1 -0
- package/build/types/manifest/v3.d.ts +139 -0
- package/build/types/manifest/v3.d.ts.map +1 -0
- package/build/types/manifest/v3.js +3 -0
- package/build/types/manifest/v3.js.map +1 -0
- package/driver.d.ts +1 -0
- package/driver.js +14 -0
- package/index.js +11 -0
- package/lib/appium.js +535 -186
- package/lib/cli/args.js +267 -422
- package/lib/cli/driver-command.js +126 -23
- package/lib/cli/extension-command.js +679 -271
- package/lib/cli/extension.js +48 -17
- package/lib/cli/parser.js +263 -83
- package/lib/cli/plugin-command.js +115 -20
- package/lib/cli/utils.js +24 -10
- package/lib/config-file.js +220 -0
- package/lib/config.js +244 -110
- package/lib/constants.js +71 -0
- package/lib/extension/driver-config.js +250 -0
- package/lib/extension/extension-config.js +682 -0
- package/lib/extension/index.js +116 -0
- package/lib/extension/manifest-migrations.js +120 -0
- package/lib/extension/manifest.js +573 -0
- package/lib/extension/package-changed.js +64 -0
- package/lib/extension/plugin-config.js +112 -0
- package/lib/grid-register.js +49 -35
- package/lib/logger.js +1 -2
- package/lib/logsink.js +64 -38
- package/lib/main.js +321 -101
- package/lib/schema/arg-spec.js +229 -0
- package/lib/schema/cli-args.js +238 -0
- package/lib/schema/cli-transformers.js +119 -0
- package/lib/schema/index.js +2 -0
- package/lib/schema/keywords.js +136 -0
- package/lib/schema/schema.js +722 -0
- package/lib/utils.js +291 -167
- package/package.json +83 -84
- package/plugin.d.ts +1 -0
- package/plugin.js +13 -0
- package/scripts/autoinstall-extensions.js +237 -0
- package/support.d.ts +1 -0
- package/support.js +13 -0
- package/tsconfig.json +25 -0
- package/types/cli.ts +189 -0
- package/types/index.ts +20 -0
- package/types/manifest/README.md +30 -0
- package/types/manifest/base.ts +158 -0
- package/types/manifest/index.ts +27 -0
- package/types/manifest/v3.ts +161 -0
- package/CHANGELOG.md +0 -3515
- package/bin/ios-webkit-debug-proxy-launcher.js +0 -71
- package/build/lib/cli/npm.js +0 -208
- package/build/lib/cli/parser-helpers.js +0 -82
- package/build/lib/driver-config.js +0 -77
- package/build/lib/drivers.js +0 -96
- package/build/lib/extension-config.js +0 -253
- package/build/lib/plugin-config.js +0 -59
- package/build/lib/plugins.js +0 -14
- package/lib/cli/npm.js +0 -184
- package/lib/cli/parser-helpers.js +0 -79
- package/lib/driver-config.js +0 -46
- package/lib/drivers.js +0 -81
- package/lib/extension-config.js +0 -209
- package/lib/plugin-config.js +0 -34
- package/lib/plugins.js +0 -10
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module containing {@link Manifest} which handles reading & writing of extension config files.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import B from 'bluebird';
|
|
6
|
+
import glob from 'glob';
|
|
7
|
+
import {env, fs} from '@appium/support';
|
|
8
|
+
import _ from 'lodash';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import YAML from 'yaml';
|
|
11
|
+
import {CURRENT_SCHEMA_REV, DRIVER_TYPE, PLUGIN_TYPE} from '../constants';
|
|
12
|
+
import log from '../logger';
|
|
13
|
+
import {INSTALL_TYPE_NPM} from './extension-config';
|
|
14
|
+
import {packageDidChange} from './package-changed';
|
|
15
|
+
import {migrate} from './manifest-migrations';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The name of the prop (`drivers`) used in `extensions.yaml` for drivers.
|
|
19
|
+
* @type {`${typeof DRIVER_TYPE}s`}
|
|
20
|
+
*/
|
|
21
|
+
const CONFIG_DATA_DRIVER_KEY = `${DRIVER_TYPE}s`;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The name of the prop (`plugins`) used in `extensions.yaml` for plugins.
|
|
25
|
+
* @type {`${typeof PLUGIN_TYPE}s`}
|
|
26
|
+
*/
|
|
27
|
+
const CONFIG_DATA_PLUGIN_KEY = `${PLUGIN_TYPE}s`;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @type {Readonly<ManifestData>}
|
|
31
|
+
*/
|
|
32
|
+
const INITIAL_MANIFEST_DATA = Object.freeze({
|
|
33
|
+
[CONFIG_DATA_DRIVER_KEY]: Object.freeze({}),
|
|
34
|
+
[CONFIG_DATA_PLUGIN_KEY]: Object.freeze({}),
|
|
35
|
+
schemaRev: CURRENT_SCHEMA_REV,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Given a `package.json` return `true` if it represents an Appium Extension (either a driver or plugin).
|
|
40
|
+
*
|
|
41
|
+
* _This is a type guard; not a validator._
|
|
42
|
+
*
|
|
43
|
+
* The `package.json` must have an `appium` property which is an object.
|
|
44
|
+
* @param {any} value
|
|
45
|
+
* @returns {value is ExtPackageJson<ExtensionType>}
|
|
46
|
+
*/
|
|
47
|
+
function isExtension(value) {
|
|
48
|
+
return (
|
|
49
|
+
_.isPlainObject(value) &&
|
|
50
|
+
_.isPlainObject(value.appium) &&
|
|
51
|
+
_.isString(value.name) &&
|
|
52
|
+
_.isString(value.version)
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Given a `package.json`, return `true` if it represents an Appium Driver.
|
|
58
|
+
*
|
|
59
|
+
* _This is a type guard; not a validator._
|
|
60
|
+
*
|
|
61
|
+
* To be considered a driver, a `package.json` must have an `appium.driverName` field.
|
|
62
|
+
*
|
|
63
|
+
* Further validation of the `appium` property happens elsewhere.
|
|
64
|
+
* @param {any} value - Value to test
|
|
65
|
+
* @returns {value is ExtPackageJson<DriverType>}
|
|
66
|
+
*/
|
|
67
|
+
function isDriver(value) {
|
|
68
|
+
return isExtension(value) && 'driverName' in value.appium && _.isString(value.appium.driverName);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Given a `package.json`, return `true` if it represents an Appium Plugin.
|
|
73
|
+
*
|
|
74
|
+
* _This is a type guard; not a validator._
|
|
75
|
+
*
|
|
76
|
+
* To be considered a plugin, a `package.json` must have an `appium.pluginName` field.
|
|
77
|
+
*
|
|
78
|
+
* Further validation of the `appium` property happens elsewhere.
|
|
79
|
+
* @param {any} value - Value to test
|
|
80
|
+
* @returns {value is ExtPackageJson<PluginType>}
|
|
81
|
+
*/
|
|
82
|
+
function isPlugin(value) {
|
|
83
|
+
return isExtension(value) && 'pluginName' in value.appium && _.isString(value.appium.pluginName);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Handles reading & writing of extension config files.
|
|
88
|
+
*
|
|
89
|
+
* Only one instance of this class exists per value of `APPIUM_HOME`.
|
|
90
|
+
*/
|
|
91
|
+
export class Manifest {
|
|
92
|
+
/**
|
|
93
|
+
* The entire contents of a parsed YAML extension config file.
|
|
94
|
+
*
|
|
95
|
+
* Contains proxies for automatic persistence on disk
|
|
96
|
+
* @type {ManifestData}
|
|
97
|
+
*/
|
|
98
|
+
#data;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Path to `APPIUM_HOME`.
|
|
102
|
+
* @type {Readonly<string>}
|
|
103
|
+
*/
|
|
104
|
+
#appiumHome;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Path to `extensions.yaml`
|
|
108
|
+
* @type {string}
|
|
109
|
+
* Not set until {@link Manifest.read} is called.
|
|
110
|
+
*/
|
|
111
|
+
#manifestPath;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Helps avoid writing multiple times.
|
|
115
|
+
*
|
|
116
|
+
* If this is `undefined`, calling {@link Manifest.write} will cause it to be
|
|
117
|
+
* set to a `Promise`. When the call to `write()` is complete, the `Promise`
|
|
118
|
+
* will resolve and then this value will be set to `undefined`. Concurrent calls
|
|
119
|
+
* made while this value is a `Promise` will return the `Promise` itself.
|
|
120
|
+
* @type {Promise<boolean>|undefined}
|
|
121
|
+
*/
|
|
122
|
+
#writing;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Helps avoid reading multiple times.
|
|
126
|
+
*
|
|
127
|
+
* If this is `undefined`, calling {@link Manifest.read} will cause it to be
|
|
128
|
+
* set to a `Promise`. When the call to `read()` is complete, the `Promise`
|
|
129
|
+
* will resolve and then this value will be set to `undefined`. Concurrent calls
|
|
130
|
+
* made while this value is a `Promise` will return the `Promise` itself.
|
|
131
|
+
* @type {Promise<void>|undefined}
|
|
132
|
+
*/
|
|
133
|
+
#reading;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Sets internal data to a fresh clone of {@link INITIAL_MANIFEST_DATA}
|
|
137
|
+
*
|
|
138
|
+
* Use {@link Manifest.getInstance} instead.
|
|
139
|
+
* @param {string} appiumHome
|
|
140
|
+
* @private
|
|
141
|
+
*/
|
|
142
|
+
constructor(appiumHome) {
|
|
143
|
+
this.#appiumHome = appiumHome;
|
|
144
|
+
this.#data = _.cloneDeep(INITIAL_MANIFEST_DATA);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Returns a new or existing {@link Manifest} instance, based on the value of `appiumHome`.
|
|
149
|
+
*
|
|
150
|
+
* Maintains one instance per value of `appiumHome`.
|
|
151
|
+
* @param {string} appiumHome - Path to `APPIUM_HOME`
|
|
152
|
+
* @returns {Manifest}
|
|
153
|
+
*/
|
|
154
|
+
static getInstance = _.memoize(function _getInstance(appiumHome) {
|
|
155
|
+
return new Manifest(appiumHome);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Searches `APPIUM_HOME` for installed extensions and adds them to the manifest.
|
|
160
|
+
* @returns {Promise<boolean>} `true` if any extensions were added, `false` otherwise.
|
|
161
|
+
*/
|
|
162
|
+
async syncWithInstalledExtensions() {
|
|
163
|
+
// this could be parallelized, but we can't use fs.walk as an async iterator
|
|
164
|
+
let didChange = false;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Listener for the `match` event of a `glob` instance
|
|
168
|
+
* @param {string} filepath - Path to a `package.json`
|
|
169
|
+
* @returns {Promise<void>}
|
|
170
|
+
*/
|
|
171
|
+
const onMatch = async (filepath) => {
|
|
172
|
+
try {
|
|
173
|
+
const pkg = JSON.parse(await fs.readFile(filepath, 'utf8'));
|
|
174
|
+
if (isExtension(pkg)) {
|
|
175
|
+
const extType = isDriver(pkg) ? DRIVER_TYPE : PLUGIN_TYPE;
|
|
176
|
+
/**
|
|
177
|
+
* this should only be 'unknown' if the extension's `package.json` is invalid
|
|
178
|
+
* @type {string}
|
|
179
|
+
*/
|
|
180
|
+
const name = isDriver(pkg)
|
|
181
|
+
? pkg.appium.driverName
|
|
182
|
+
: isPlugin(pkg)
|
|
183
|
+
? pkg.appium.pluginName
|
|
184
|
+
: '(unknown)';
|
|
185
|
+
if (
|
|
186
|
+
(isDriver(pkg) && !this.hasDriver(name)) ||
|
|
187
|
+
(isPlugin(pkg) && !this.hasPlugin(name))
|
|
188
|
+
) {
|
|
189
|
+
log.info(`Discovered installed ${extType} "${name}"`);
|
|
190
|
+
}
|
|
191
|
+
const changed = this.addExtensionFromPackage(pkg, filepath);
|
|
192
|
+
didChange = didChange || changed;
|
|
193
|
+
}
|
|
194
|
+
} catch {}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* A list of `Promise`s which read `package.json` files looking for Appium extensions.
|
|
199
|
+
* @type {Promise<void>[]}
|
|
200
|
+
*/
|
|
201
|
+
const queue = [
|
|
202
|
+
// look at `package.json` in `APPIUM_HOME` only
|
|
203
|
+
onMatch(path.join(this.#appiumHome, 'package.json')),
|
|
204
|
+
];
|
|
205
|
+
|
|
206
|
+
// add dependencies to the queue
|
|
207
|
+
await new B((resolve, reject) => {
|
|
208
|
+
glob(
|
|
209
|
+
'node_modules/{*,@*/*}/package.json',
|
|
210
|
+
{cwd: this.#appiumHome, silent: true, absolute: true},
|
|
211
|
+
// eslint-disable-next-line promise/prefer-await-to-callbacks
|
|
212
|
+
(err) => {
|
|
213
|
+
if (err) {
|
|
214
|
+
reject(err);
|
|
215
|
+
}
|
|
216
|
+
resolve();
|
|
217
|
+
}
|
|
218
|
+
)
|
|
219
|
+
.on('error', reject)
|
|
220
|
+
.on('match', (filepath) => {
|
|
221
|
+
queue.push(onMatch(filepath));
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// wait for everything to finish
|
|
226
|
+
await B.all(queue);
|
|
227
|
+
|
|
228
|
+
return didChange;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Returns `true` if driver with name `name` is registered.
|
|
233
|
+
* @param {string} name - Driver name
|
|
234
|
+
* @returns {boolean}
|
|
235
|
+
*/
|
|
236
|
+
hasDriver(name) {
|
|
237
|
+
return Boolean(this.#data.drivers[name]);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Returns `true` if plugin with name `name` is registered.
|
|
242
|
+
* @param {string} name - Plugin name
|
|
243
|
+
* @returns {boolean}
|
|
244
|
+
*/
|
|
245
|
+
hasPlugin(name) {
|
|
246
|
+
return Boolean(this.#data.plugins[name]);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Given a path to a `package.json`, add it as either a driver or plugin to the manifest.
|
|
251
|
+
*
|
|
252
|
+
* @template {ExtensionType} ExtType
|
|
253
|
+
* @param {ExtPackageJson<ExtType>} pkgJson
|
|
254
|
+
* @param {string} pkgPath
|
|
255
|
+
* @returns {boolean} - `true` if this method did anything.
|
|
256
|
+
*/
|
|
257
|
+
addExtensionFromPackage(pkgJson, pkgPath) {
|
|
258
|
+
const extensionPath = path.dirname(pkgPath);
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* @type {InternalMetadata}
|
|
262
|
+
*/
|
|
263
|
+
const internal = {
|
|
264
|
+
pkgName: pkgJson.name,
|
|
265
|
+
version: pkgJson.version,
|
|
266
|
+
appiumVersion: pkgJson.peerDependencies?.appium,
|
|
267
|
+
installType: INSTALL_TYPE_NPM,
|
|
268
|
+
installSpec: `${pkgJson.name}@${pkgJson.version}`,
|
|
269
|
+
installPath: extensionPath,
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
if (isDriver(pkgJson)) {
|
|
273
|
+
const value = {
|
|
274
|
+
..._.omit(pkgJson.appium, 'driverName'),
|
|
275
|
+
...internal,
|
|
276
|
+
};
|
|
277
|
+
if (!_.isEqual(value, this.#data.drivers[pkgJson.appium.driverName])) {
|
|
278
|
+
this.setExtension(DRIVER_TYPE, pkgJson.appium.driverName, value);
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
return false;
|
|
282
|
+
} else if (isPlugin(pkgJson)) {
|
|
283
|
+
const value = {
|
|
284
|
+
..._.omit(pkgJson.appium, 'pluginName'),
|
|
285
|
+
...internal,
|
|
286
|
+
};
|
|
287
|
+
if (!_.isEqual(value, this.#data.plugins[pkgJson.appium.pluginName])) {
|
|
288
|
+
this.setExtension(PLUGIN_TYPE, pkgJson.appium.pluginName, value);
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
return false;
|
|
292
|
+
} else {
|
|
293
|
+
throw new TypeError(
|
|
294
|
+
`The extension in ${extensionPath} is neither a valid ${DRIVER_TYPE} nor a valid ${PLUGIN_TYPE}.`
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Adds an extension to the manifest as was installed by the `appium` CLI. The
|
|
301
|
+
* `extData`, `extType`, and `extName` have already been determined.
|
|
302
|
+
*
|
|
303
|
+
* See {@link Manifest.addExtensionFromPackage} for adding an extension from an on-disk package.
|
|
304
|
+
* @template {ExtensionType} ExtType
|
|
305
|
+
* @param {ExtType} extType - `driver` or `plugin`
|
|
306
|
+
* @param {string} extName - Name of extension
|
|
307
|
+
* @param {ExtManifest<ExtType>} extData - Extension metadata
|
|
308
|
+
* @returns {ExtManifest<ExtType>} A clone of `extData`, potentially with a mutated `appiumVersion` field
|
|
309
|
+
*/
|
|
310
|
+
setExtension(extType, extName, extData) {
|
|
311
|
+
const data = _.cloneDeep(extData);
|
|
312
|
+
this.#data[`${extType}s`][extName] = data;
|
|
313
|
+
return data;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Sets the schema revision
|
|
318
|
+
* @param {keyof import('./manifest-migrations').ManifestDataVersions} rev
|
|
319
|
+
*/
|
|
320
|
+
setSchemaRev(rev) {
|
|
321
|
+
this.#data.schemaRev = rev;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Remove an extension from the manifest.
|
|
326
|
+
* @param {ExtensionType} extType
|
|
327
|
+
* @param {string} extName
|
|
328
|
+
*/
|
|
329
|
+
deleteExtension(extType, extName) {
|
|
330
|
+
delete this.#data[`${extType}s`][extName];
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Returns the `APPIUM_HOME` path
|
|
335
|
+
*/
|
|
336
|
+
get appiumHome() {
|
|
337
|
+
return this.#appiumHome;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Returns the path to the manifest file (`extensions.yaml`)
|
|
342
|
+
*/
|
|
343
|
+
get manifestPath() {
|
|
344
|
+
return this.#manifestPath;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Returns the schema rev of this manifest
|
|
349
|
+
*/
|
|
350
|
+
get schemaRev() {
|
|
351
|
+
return this.#data.schemaRev;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Returns extension data for a particular type.
|
|
356
|
+
*
|
|
357
|
+
* @template {ExtensionType} ExtType
|
|
358
|
+
* @param {ExtType} extType
|
|
359
|
+
* @returns {Readonly<ExtRecord<ExtType>>}
|
|
360
|
+
*/
|
|
361
|
+
getExtensionData(extType) {
|
|
362
|
+
return this.#data[/** @type {string} */ (`${extType}s`)];
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Reads manifest from disk and _overwrites_ the internal data.
|
|
367
|
+
*
|
|
368
|
+
* If the manifest does not exist on disk, an
|
|
369
|
+
* {@link INITIAL_MANIFEST_DATA "empty"} manifest file will be created, as
|
|
370
|
+
* well as its directory if needed.
|
|
371
|
+
*
|
|
372
|
+
* This will also, if necessary:
|
|
373
|
+
* 1. perform a migration of the manifest data
|
|
374
|
+
* 2. sync the manifest with extensions on-disk (kind of like "auto
|
|
375
|
+
* discovery")
|
|
376
|
+
* 3. write the manifest to disk.
|
|
377
|
+
*
|
|
378
|
+
* Only one read operation can happen at a time.
|
|
379
|
+
*
|
|
380
|
+
* @returns {Promise<ManifestData>} The data
|
|
381
|
+
*/
|
|
382
|
+
async read() {
|
|
383
|
+
if (this.#reading) {
|
|
384
|
+
await this.#reading;
|
|
385
|
+
return this.#data;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
this.#reading = (async () => {
|
|
389
|
+
/** @type {ManifestData} */
|
|
390
|
+
let data;
|
|
391
|
+
/**
|
|
392
|
+
* This will be `true` if, after reading, we need to update the manifest data
|
|
393
|
+
* and write it again to disk.
|
|
394
|
+
*/
|
|
395
|
+
let shouldWrite = false;
|
|
396
|
+
await this.#setManifestPath();
|
|
397
|
+
try {
|
|
398
|
+
const yaml = await fs.readFile(this.#manifestPath, 'utf8');
|
|
399
|
+
data = YAML.parse(yaml);
|
|
400
|
+
log.debug(
|
|
401
|
+
`Parsed manifest file at ${this.#manifestPath}: ${JSON.stringify(data, null, 2)}`
|
|
402
|
+
);
|
|
403
|
+
} catch (err) {
|
|
404
|
+
if (err.code === 'ENOENT') {
|
|
405
|
+
log.debug(`No manifest file found at ${this.#manifestPath}; creating`);
|
|
406
|
+
data = _.cloneDeep(INITIAL_MANIFEST_DATA);
|
|
407
|
+
shouldWrite = true;
|
|
408
|
+
} else {
|
|
409
|
+
if (this.#manifestPath) {
|
|
410
|
+
throw new Error(
|
|
411
|
+
`Appium had trouble loading the extension installation ` +
|
|
412
|
+
`cache file (${this.#manifestPath}). It may be invalid YAML. Specific error: ${
|
|
413
|
+
err.message
|
|
414
|
+
}`
|
|
415
|
+
);
|
|
416
|
+
} else {
|
|
417
|
+
throw new Error(
|
|
418
|
+
`Appium encountered an unknown problem. Specific error: ${err.message}`
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
this.#data = data;
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* the only way `shouldWrite` is `true` is if we have a new file. a new
|
|
428
|
+
* file will get the latest schema revision, so we can skip the migration.
|
|
429
|
+
*/
|
|
430
|
+
if (!shouldWrite && (data.schemaRev ?? 0) < CURRENT_SCHEMA_REV) {
|
|
431
|
+
log.debug(
|
|
432
|
+
`Updating manifest schema from rev ${data.schemaRev ?? '(none)'} to ${CURRENT_SCHEMA_REV}`
|
|
433
|
+
);
|
|
434
|
+
shouldWrite = await migrate(this);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* we still may want to sync with installed extensions even if we have a
|
|
439
|
+
* new file. right now this is limited to the following cases:
|
|
440
|
+
* 1. we have a brand new manifest file
|
|
441
|
+
* 2. we have performed a migration on a manifest file
|
|
442
|
+
* 3. `appium` is a dependency within `package.json`, and `package.json`
|
|
443
|
+
* has changed since last time we checked.
|
|
444
|
+
*
|
|
445
|
+
* It may also make sense to sync with the extensions in an arbitrary
|
|
446
|
+
* `APPIUM_HOME`, but we don't do that here.
|
|
447
|
+
*/
|
|
448
|
+
if (
|
|
449
|
+
shouldWrite ||
|
|
450
|
+
((await env.hasAppiumDependency(this.appiumHome)) &&
|
|
451
|
+
(await packageDidChange(this.appiumHome)))
|
|
452
|
+
) {
|
|
453
|
+
log.debug('Discovering newly installed extensions...');
|
|
454
|
+
shouldWrite = (await this.syncWithInstalledExtensions()) || shouldWrite;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (shouldWrite) {
|
|
458
|
+
await this.write();
|
|
459
|
+
}
|
|
460
|
+
})();
|
|
461
|
+
|
|
462
|
+
try {
|
|
463
|
+
await this.#reading;
|
|
464
|
+
return this.#data;
|
|
465
|
+
} finally {
|
|
466
|
+
this.#reading = undefined;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Ensures the internal manifest path is set.
|
|
472
|
+
*
|
|
473
|
+
* Creates the directory if necessary.
|
|
474
|
+
* @returns {Promise<string>}
|
|
475
|
+
*/
|
|
476
|
+
async #setManifestPath() {
|
|
477
|
+
if (!this.#manifestPath) {
|
|
478
|
+
this.#manifestPath = await env.resolveManifestPath(this.#appiumHome);
|
|
479
|
+
|
|
480
|
+
/* istanbul ignore if */
|
|
481
|
+
if (path.relative(this.#appiumHome, this.#manifestPath).startsWith('.')) {
|
|
482
|
+
throw new Error(
|
|
483
|
+
`Mismatch between location of APPIUM_HOME and manifest file. APPIUM_HOME: ${
|
|
484
|
+
this.appiumHome
|
|
485
|
+
}, manifest file: ${this.#manifestPath}`
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return this.#manifestPath;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Writes the data if it need s writing.
|
|
495
|
+
*
|
|
496
|
+
* If the `schemaRev` prop needs updating, the file will be written.
|
|
497
|
+
*
|
|
498
|
+
* @todo If this becomes too much of a bottleneck, throttle it.
|
|
499
|
+
* @returns {Promise<boolean>} Whether the data was written
|
|
500
|
+
*/
|
|
501
|
+
async write() {
|
|
502
|
+
if (this.#writing) {
|
|
503
|
+
return this.#writing;
|
|
504
|
+
}
|
|
505
|
+
this.#writing = (async () => {
|
|
506
|
+
await this.#setManifestPath();
|
|
507
|
+
try {
|
|
508
|
+
await fs.mkdirp(path.dirname(this.#manifestPath));
|
|
509
|
+
} catch (err) {
|
|
510
|
+
throw new Error(
|
|
511
|
+
`Appium could not create the directory for the manifest file: ${path.dirname(
|
|
512
|
+
this.#manifestPath
|
|
513
|
+
)}. Original error: ${err.message}`
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
try {
|
|
517
|
+
await fs.writeFile(this.#manifestPath, YAML.stringify(this.#data), 'utf8');
|
|
518
|
+
return true;
|
|
519
|
+
} catch (err) {
|
|
520
|
+
throw new Error(
|
|
521
|
+
`Appium could not write to manifest at ${this.#manifestPath} using APPIUM_HOME ${
|
|
522
|
+
this.#appiumHome
|
|
523
|
+
}. Please ensure it is writable. Original error: ${err.message}`
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
})();
|
|
527
|
+
try {
|
|
528
|
+
return await this.#writing;
|
|
529
|
+
} finally {
|
|
530
|
+
this.#writing = undefined;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Type of the string referring to a driver (typically as a key or type string)
|
|
537
|
+
* @typedef {import('@appium/types').DriverType} DriverType
|
|
538
|
+
*/
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Type of the string referring to a plugin (typically as a key or type string)
|
|
542
|
+
* @typedef {import('@appium/types').PluginType} PluginType
|
|
543
|
+
*/
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* @typedef SyncWithInstalledExtensionsOpts
|
|
547
|
+
* @property {number} [depthLimit] - Maximum depth to recurse into subdirectories
|
|
548
|
+
*/
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* @typedef {import('appium/types').ManifestData} ManifestData
|
|
552
|
+
* @typedef {import('appium/types').InternalMetadata} InternalMetadata
|
|
553
|
+
*/
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* @template {ExtensionType} ExtType
|
|
557
|
+
* @typedef {import('appium/types').ExtPackageJson<ExtType>} ExtPackageJson
|
|
558
|
+
*/
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* @template {ExtensionType} ExtType
|
|
562
|
+
* @typedef {import('appium/types').ExtManifest<ExtType>} ExtManifest
|
|
563
|
+
*/
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* @template {ExtensionType} ExtType
|
|
567
|
+
* @typedef {import('appium/types').ExtRecord<ExtType>} ExtRecord
|
|
568
|
+
*/
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Either `driver` or `plugin` rn
|
|
572
|
+
* @typedef {import('@appium/types').ExtensionType} ExtensionType
|
|
573
|
+
*/
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import {fs} from '@appium/support';
|
|
2
|
+
import {isPackageChanged} from 'package-changed';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import {PKG_HASHFILE_RELATIVE_PATH} from '../constants';
|
|
5
|
+
import log from '../logger';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Determines if extensions have changed, and updates a hash the `package.json` in `appiumHome` if so.
|
|
9
|
+
*
|
|
10
|
+
* If they have, we need to sync them with the `extensions.yaml` manifest.
|
|
11
|
+
*
|
|
12
|
+
* _Warning: this makes a blocking call to `writeFileSync`._
|
|
13
|
+
* @param {string} appiumHome
|
|
14
|
+
* @returns {Promise<boolean>} `true` if `package.json` `appiumHome` changed
|
|
15
|
+
*/
|
|
16
|
+
export async function packageDidChange(appiumHome) {
|
|
17
|
+
const hashFilename = path.join(appiumHome, PKG_HASHFILE_RELATIVE_PATH);
|
|
18
|
+
|
|
19
|
+
// XXX: the types in `package-changed` seem to be wrong.
|
|
20
|
+
|
|
21
|
+
/** @type {boolean} */
|
|
22
|
+
let isChanged;
|
|
23
|
+
/** @type {() => void} */
|
|
24
|
+
let writeHash;
|
|
25
|
+
/** @type {string} */
|
|
26
|
+
let hash;
|
|
27
|
+
/** @type {string|undefined} */
|
|
28
|
+
let oldHash;
|
|
29
|
+
|
|
30
|
+
// first mkdirp the target dir.
|
|
31
|
+
const hashFilenameDir = path.dirname(hashFilename);
|
|
32
|
+
log.debug(`Creating hash file directory: ${hashFilenameDir}`);
|
|
33
|
+
try {
|
|
34
|
+
await fs.mkdirp(hashFilenameDir);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`Appium could not create the directory for hash file: ${hashFilenameDir}. Original error: ${err.message}`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
({isChanged, writeHash, oldHash, hash} = await isPackageChanged({
|
|
43
|
+
cwd: appiumHome,
|
|
44
|
+
hashFilename: PKG_HASHFILE_RELATIVE_PATH,
|
|
45
|
+
}));
|
|
46
|
+
} catch {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (isChanged) {
|
|
51
|
+
try {
|
|
52
|
+
writeHash();
|
|
53
|
+
log.debug(
|
|
54
|
+
`Updated hash of ${appiumHome}/package.json from: ${oldHash ?? '(none)'} to: ${hash}`
|
|
55
|
+
);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Appium could not write hash file: ${hashFilenameDir}. Original error: ${err.message}`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return isChanged;
|
|
64
|
+
}
|