appium 3.1.2 → 3.2.1
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 +4 -4
- package/build/lib/appium.d.ts +2 -2
- package/build/lib/appium.d.ts.map +1 -1
- package/build/lib/appium.js +5 -7
- package/build/lib/appium.js.map +1 -1
- package/build/lib/bidi-commands.d.ts +1 -1
- package/build/lib/bidi-commands.d.ts.map +1 -1
- package/build/lib/bidi-commands.js.map +1 -1
- package/build/lib/cli/extension-command.d.ts +109 -0
- package/build/lib/cli/extension-command.d.ts.map +1 -1
- package/build/lib/cli/extension-command.js +255 -96
- package/build/lib/cli/extension-command.js.map +1 -1
- package/build/lib/cli/parser.d.ts +10 -0
- package/build/lib/cli/parser.d.ts.map +1 -1
- package/build/lib/cli/parser.js +20 -2
- package/build/lib/cli/parser.js.map +1 -1
- package/build/lib/constants.js +3 -3
- package/build/lib/constants.js.map +1 -1
- package/build/lib/extension/extension-config.js +7 -7
- package/build/lib/extension/extension-config.js.map +1 -1
- package/build/lib/extension/manifest.js +6 -6
- package/build/lib/extension/manifest.js.map +1 -1
- package/build/lib/extension/package-changed.js +3 -3
- package/build/lib/extension/package-changed.js.map +1 -1
- package/build/lib/inspector-commands.d.ts.map +1 -1
- package/build/lib/logger.d.ts +1 -1
- package/build/lib/logger.d.ts.map +1 -1
- package/build/lib/logsink.d.ts.map +1 -1
- package/build/lib/logsink.js +9 -7
- package/build/lib/logsink.js.map +1 -1
- package/build/lib/main.d.ts.map +1 -1
- package/build/lib/main.js +11 -2
- package/build/lib/main.js.map +1 -1
- package/build/lib/schema/cli-args.d.ts.map +1 -1
- package/build/lib/schema/cli-args.js +5 -0
- package/build/lib/schema/cli-args.js.map +1 -1
- package/build/lib/schema/cli-transformers.js +5 -5
- package/build/lib/schema/cli-transformers.js.map +1 -1
- package/build/lib/schema/schema.js +2 -2
- package/build/lib/schema/schema.js.map +1 -1
- package/build/lib/utils.d.ts +1 -1
- package/build/lib/utils.d.ts.map +1 -1
- package/build/lib/utils.js +8 -8
- package/build/lib/utils.js.map +1 -1
- package/build/package.json +99 -0
- package/build/types/cli.d.ts +3 -3
- package/build/types/cli.d.ts.map +1 -1
- package/build/types/index.d.ts +1 -1
- package/build/types/index.d.ts.map +1 -1
- package/build/types/manifest/base.d.ts +3 -3
- package/build/types/manifest/base.d.ts.map +1 -1
- package/build/types/manifest/v3.d.ts +3 -3
- package/build/types/manifest/v3.d.ts.map +1 -1
- package/build/types/manifest/v4.d.ts +3 -3
- package/build/types/manifest/v4.d.ts.map +1 -1
- package/index.js +1 -3
- package/lib/appium.js +5 -7
- package/lib/bidi-commands.ts +4 -6
- package/lib/cli/extension-command.js +283 -105
- package/lib/cli/parser.js +21 -2
- package/lib/constants.js +1 -1
- package/lib/extension/extension-config.js +2 -2
- package/lib/extension/manifest.js +1 -1
- package/lib/extension/package-changed.js +1 -1
- package/lib/inspector-commands.ts +1 -1
- package/lib/logsink.js +11 -7
- package/lib/main.js +13 -3
- package/lib/schema/cli-args.js +6 -0
- package/lib/schema/cli-transformers.js +1 -1
- package/lib/schema/schema.js +1 -1
- package/lib/utils.js +3 -3
- package/package.json +22 -24
- package/scripts/autoinstall-extensions.js +7 -10
- package/tsconfig.json +5 -2
- package/types/cli.ts +4 -4
- package/types/index.ts +1 -1
- package/types/manifest/base.ts +3 -3
- package/types/manifest/v3.ts +3 -3
- package/types/manifest/v4.ts +3 -3
package/lib/appium.js
CHANGED
|
@@ -560,16 +560,14 @@ class AppiumDriver extends DriverCore {
|
|
|
560
560
|
/**
|
|
561
561
|
* Get the appropriate plugins for a session (or sessionless plugins)
|
|
562
562
|
*
|
|
563
|
-
* @param {
|
|
563
|
+
* @param {string|null} [sessionId=null] - the sessionId (or null) to use to find plugins
|
|
564
564
|
* @returns {Array<import('@appium/types').Plugin>} - array of plugin instances
|
|
565
565
|
*/
|
|
566
566
|
pluginsForSession(sessionId = null) {
|
|
567
567
|
if (sessionId) {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
}
|
|
572
|
-
return this.sessionPlugins[sessionId];
|
|
568
|
+
const driver = this.sessions[sessionId];
|
|
569
|
+
return this.sessionPlugins[sessionId]
|
|
570
|
+
?? (driver ? this.createPluginInstances(generateDriverLogPrefix(driver)) : []);
|
|
573
571
|
}
|
|
574
572
|
|
|
575
573
|
if (_.isEmpty(this.sessionlessPlugins)) {
|
|
@@ -598,7 +596,7 @@ class AppiumDriver extends DriverCore {
|
|
|
598
596
|
|
|
599
597
|
/**
|
|
600
598
|
* Creates instances of all of the enabled Plugin classes
|
|
601
|
-
* @param {string|null} driverId - ID to use for linking a driver to a plugin in logs
|
|
599
|
+
* @param {string|null} [driverId=null] - ID to use for linking a driver to a plugin in logs
|
|
602
600
|
* @returns {Plugin[]}
|
|
603
601
|
*/
|
|
604
602
|
createPluginInstances(driverId = null) {
|
package/lib/bidi-commands.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import _ from 'lodash';
|
|
2
2
|
import B from 'bluebird';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
ExtensionCore,
|
|
6
|
-
} from '@appium/base-driver';
|
|
3
|
+
import type {ExtensionCore} from '@appium/base-driver';
|
|
4
|
+
import {errors} from '@appium/base-driver';
|
|
7
5
|
import {BIDI_BASE_PATH, BIDI_EVENT_NAME} from './constants';
|
|
8
6
|
import WebSocket from 'ws';
|
|
9
7
|
import os from 'node:os';
|
|
@@ -23,7 +21,7 @@ import type {
|
|
|
23
21
|
BiDiResultData
|
|
24
22
|
} from '@appium/types';
|
|
25
23
|
|
|
26
|
-
type ExtensionPlugin = Plugin & ExtensionCore
|
|
24
|
+
type ExtensionPlugin = Plugin & ExtensionCore;
|
|
27
25
|
type AnyDriver = ExternalDriver | AppiumDriver;
|
|
28
26
|
type SendData = (data: string | Buffer) => Promise<void>;
|
|
29
27
|
type LogSocketError = (err: Error) => void;
|
|
@@ -448,7 +446,7 @@ function initBidiEventListeners(
|
|
|
448
446
|
// sure the client is subscribed and then pass it on
|
|
449
447
|
const eventLogCounts: Record<string, number> = BIDI_EVENTS_MAP.get(bidiHandlerDriver) ?? {};
|
|
450
448
|
BIDI_EVENTS_MAP.set(bidiHandlerDriver, eventLogCounts);
|
|
451
|
-
const eventListenerFactory = (extType: 'driver'|'plugin', ext: ExtensionCore) => {
|
|
449
|
+
const eventListenerFactory = (extType: 'driver' | 'plugin', ext: ExtensionCore) => {
|
|
452
450
|
const eventListener = async ({context, method, params = {}}) => {
|
|
453
451
|
// if the driver didn't specify a context, use the empty context
|
|
454
452
|
if (!context) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import B from 'bluebird';
|
|
2
2
|
import _ from 'lodash';
|
|
3
|
-
import path from 'path';
|
|
3
|
+
import path from 'node:path';
|
|
4
4
|
import {npm, util, env, console, fs, system} from '@appium/support';
|
|
5
5
|
import {spinWith, RingBuffer} from './utils';
|
|
6
6
|
import {
|
|
@@ -12,14 +12,15 @@ import {
|
|
|
12
12
|
} from '../extension/extension-config';
|
|
13
13
|
import {SubProcess} from 'teen_process';
|
|
14
14
|
import {packageDidChange} from '../extension/package-changed';
|
|
15
|
-
import {spawn} from 'child_process';
|
|
15
|
+
import {spawn} from 'node:child_process';
|
|
16
16
|
import {inspect} from 'node:util';
|
|
17
|
-
import {pathToFileURL} from 'url';
|
|
17
|
+
import {pathToFileURL} from 'node:url';
|
|
18
18
|
import {Doctor, EXIT_CODE as DOCTOR_EXIT_CODE} from '../doctor/doctor';
|
|
19
19
|
import {getAppiumModuleRoot, npmPackage} from '../utils';
|
|
20
20
|
import * as semver from 'semver';
|
|
21
21
|
|
|
22
22
|
const UPDATE_ALL = 'installed';
|
|
23
|
+
const MAX_CONCURRENT_REPO_FETCHES = 5;
|
|
23
24
|
|
|
24
25
|
class NotUpdatableError extends Error {}
|
|
25
26
|
class NoUpdatesAvailableError extends Error {}
|
|
@@ -96,6 +97,7 @@ class ExtensionCliCommand {
|
|
|
96
97
|
* For TS to understand that a function throws an exception, it must actually throw an exception--
|
|
97
98
|
* in other words, _calling_ a function which is guaranteed to throw an exception is not enough--
|
|
98
99
|
* nor is something like `@returns {never}` which does not imply a thrown exception.
|
|
100
|
+
*
|
|
99
101
|
* @param {string} message
|
|
100
102
|
* @protected
|
|
101
103
|
* @throws {Error}
|
|
@@ -121,18 +123,45 @@ class ExtensionCliCommand {
|
|
|
121
123
|
|
|
122
124
|
/**
|
|
123
125
|
* List extensions
|
|
126
|
+
*
|
|
124
127
|
* @template {ExtensionType} ExtType
|
|
125
128
|
* @param {ListOptions} opts
|
|
126
129
|
* @return {Promise<ExtensionList<ExtType>>} map of extension names to extension data
|
|
127
130
|
*/
|
|
128
131
|
async list({showInstalled, showUpdates, verbose = false}) {
|
|
129
|
-
|
|
132
|
+
const listData = this._buildListData(showInstalled);
|
|
133
|
+
|
|
134
|
+
const lsMsg =
|
|
135
|
+
`Listing ${showInstalled ? 'installed' : 'available'} ${this.type}s` +
|
|
136
|
+
(verbose ? ' (verbose mode)' : ' (rerun with --verbose for more info)');
|
|
137
|
+
await this._checkForUpdates(listData, showUpdates, lsMsg);
|
|
138
|
+
|
|
139
|
+
if (this.isJsonOutput) {
|
|
140
|
+
await this._addRepositoryUrlsToListData(listData);
|
|
141
|
+
return listData;
|
|
142
|
+
}
|
|
143
|
+
|
|
130
144
|
if (verbose) {
|
|
131
|
-
|
|
145
|
+
await this._addRepositoryUrlsToListData(listData);
|
|
146
|
+
this.log.log(inspect(listData, {colors: true, depth: null}));
|
|
147
|
+
return listData;
|
|
132
148
|
}
|
|
149
|
+
|
|
150
|
+
return await this._displayNormalListOutput(listData, showUpdates);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Build the initial list data structure from installed and known extensions
|
|
155
|
+
*
|
|
156
|
+
* @template {ExtensionType} ExtType
|
|
157
|
+
* @param {boolean} showInstalled
|
|
158
|
+
* @returns {ExtensionList<ExtType>}
|
|
159
|
+
* @private
|
|
160
|
+
*/
|
|
161
|
+
_buildListData(showInstalled) {
|
|
133
162
|
const installedNames = Object.keys(this.config.installedExtensions);
|
|
134
163
|
const knownNames = Object.keys(this.knownExtensions);
|
|
135
|
-
|
|
164
|
+
return [...installedNames, ...knownNames].reduce((acc, name) => {
|
|
136
165
|
if (!acc[name]) {
|
|
137
166
|
if (installedNames.includes(name)) {
|
|
138
167
|
acc[name] = {
|
|
@@ -148,99 +177,233 @@ class ExtensionCliCommand {
|
|
|
148
177
|
}
|
|
149
178
|
return acc;
|
|
150
179
|
}, /** @type {ExtensionList<ExtType>} */ ({}));
|
|
180
|
+
}
|
|
151
181
|
|
|
152
|
-
|
|
182
|
+
/**
|
|
183
|
+
* Check for available updates for installed extensions
|
|
184
|
+
*
|
|
185
|
+
* @template {ExtensionType} ExtType
|
|
186
|
+
* @param {ExtensionList<ExtType>} listData
|
|
187
|
+
* @param {boolean} showUpdates
|
|
188
|
+
* @param {string} lsMsg
|
|
189
|
+
* @returns {Promise<void>}
|
|
190
|
+
* @private
|
|
191
|
+
*/
|
|
192
|
+
async _checkForUpdates(listData, showUpdates, lsMsg) {
|
|
153
193
|
await spinWith(this.isJsonOutput, lsMsg, async () => {
|
|
194
|
+
// We'd like to still show lsMsg even if showUpdates is false
|
|
154
195
|
if (!showUpdates) {
|
|
155
196
|
return;
|
|
156
197
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
198
|
+
|
|
199
|
+
// Filter to only extensions that need update checks (installed npm packages)
|
|
200
|
+
const extensionsToCheck = _.toPairs(listData).filter(
|
|
201
|
+
([, data]) => data.installed && data.installType === INSTALL_TYPE_NPM
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
await B.map(
|
|
205
|
+
extensionsToCheck,
|
|
206
|
+
async ([ext, data]) => {
|
|
207
|
+
try {
|
|
208
|
+
const updates = await this.checkForExtensionUpdate(ext);
|
|
209
|
+
data.updateVersion = updates.safeUpdate;
|
|
210
|
+
data.unsafeUpdateVersion = updates.unsafeUpdate;
|
|
211
|
+
data.upToDate = updates.safeUpdate === null && updates.unsafeUpdate === null;
|
|
212
|
+
} catch (e) {
|
|
213
|
+
data.updateError = e.message;
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
{concurrency: MAX_CONCURRENT_REPO_FETCHES}
|
|
217
|
+
);
|
|
172
218
|
});
|
|
219
|
+
}
|
|
173
220
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
221
|
+
/**
|
|
222
|
+
* Add repository URLs to list data for all extensions
|
|
223
|
+
*
|
|
224
|
+
* @template {ExtensionType} ExtType
|
|
225
|
+
* @param {ExtensionList<ExtType>} listData
|
|
226
|
+
* @returns {Promise<void>}
|
|
227
|
+
* @private
|
|
228
|
+
*/
|
|
229
|
+
async _addRepositoryUrlsToListData(listData) {
|
|
230
|
+
await spinWith(this.isJsonOutput, 'Fetching repository information', async () => {
|
|
231
|
+
await B.map(
|
|
232
|
+
_.values(listData),
|
|
233
|
+
async (data) => {
|
|
234
|
+
const repoUrl = await this._getRepositoryUrl(data);
|
|
235
|
+
if (repoUrl) {
|
|
236
|
+
data.repositoryUrl = repoUrl;
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
{concurrency: MAX_CONCURRENT_REPO_FETCHES}
|
|
240
|
+
);
|
|
241
|
+
});
|
|
242
|
+
}
|
|
180
243
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
244
|
+
/**
|
|
245
|
+
* Display normal formatted output
|
|
246
|
+
*
|
|
247
|
+
* @template {ExtensionType} ExtType
|
|
248
|
+
* @param {ExtensionList<ExtType>} listData
|
|
249
|
+
* @param {boolean} showUpdates
|
|
250
|
+
* @returns {Promise<ExtensionList<ExtType>>}
|
|
251
|
+
* @private
|
|
252
|
+
*/
|
|
253
|
+
async _displayNormalListOutput(listData, showUpdates) {
|
|
254
|
+
for (const [name, data] of _.toPairs(listData)) {
|
|
255
|
+
const line = await this._formatExtensionLine(name, data, showUpdates);
|
|
256
|
+
this.log.log(line);
|
|
185
257
|
}
|
|
186
258
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
259
|
+
return listData;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Format a single extension line for display
|
|
264
|
+
*
|
|
265
|
+
* @template {ExtensionType} ExtType
|
|
266
|
+
* @param {string} name
|
|
267
|
+
* @param {ExtensionListData<ExtType>} data
|
|
268
|
+
* @param {boolean} showUpdates
|
|
269
|
+
* @returns {Promise<string>}
|
|
270
|
+
* @private
|
|
271
|
+
*/
|
|
272
|
+
async _formatExtensionLine(name, data, showUpdates) {
|
|
273
|
+
if (data.installed) {
|
|
274
|
+
const installTxt = this._formatInstallText(/** @type {InstalledExtensionListData<ExtType>} */ (data));
|
|
275
|
+
const updateTxt = showUpdates ? this._formatUpdateText(/** @type {InstalledExtensionListData<ExtType>} */ (data)) : '';
|
|
276
|
+
return `- ${name.yellow}${installTxt}${updateTxt}`;
|
|
190
277
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
278
|
+
const installTxt = ' [not installed]'.grey;
|
|
279
|
+
return `- ${name.yellow}${installTxt}`;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Format installation status text
|
|
284
|
+
*
|
|
285
|
+
* @template {ExtensionType} ExtType
|
|
286
|
+
* @param {InstalledExtensionListData<ExtType>} data
|
|
287
|
+
* @returns {string}
|
|
288
|
+
* @private
|
|
289
|
+
*/
|
|
290
|
+
_formatInstallText(data) {
|
|
291
|
+
const {installType, installSpec, version} = data;
|
|
292
|
+
let typeTxt;
|
|
293
|
+
switch (installType) {
|
|
294
|
+
case INSTALL_TYPE_GIT:
|
|
295
|
+
case INSTALL_TYPE_GITHUB:
|
|
296
|
+
typeTxt = `(cloned from ${installSpec})`.yellow;
|
|
297
|
+
break;
|
|
298
|
+
case INSTALL_TYPE_LOCAL:
|
|
299
|
+
typeTxt = `(linked from ${installSpec})`.magenta;
|
|
300
|
+
break;
|
|
301
|
+
case INSTALL_TYPE_DEV:
|
|
302
|
+
typeTxt = '(dev mode)';
|
|
303
|
+
break;
|
|
304
|
+
default:
|
|
305
|
+
typeTxt = '(npm)';
|
|
306
|
+
}
|
|
307
|
+
return `@${version.yellow} ${('[installed ' + typeTxt + ']').green}`;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Format update information text
|
|
312
|
+
*
|
|
313
|
+
* @template {ExtensionType} ExtType
|
|
314
|
+
* @param {InstalledExtensionListData<ExtType>} data
|
|
315
|
+
* @returns {string}
|
|
316
|
+
* @private
|
|
317
|
+
*/
|
|
318
|
+
_formatUpdateText(data) {
|
|
319
|
+
const {updateVersion, unsafeUpdateVersion, upToDate, updateError} = data;
|
|
320
|
+
if (updateError) {
|
|
321
|
+
return ` [Cannot check for updates: ${updateError}]`.red;
|
|
322
|
+
}
|
|
323
|
+
let txt = '';
|
|
324
|
+
if (updateVersion) {
|
|
325
|
+
txt += ` [${updateVersion} available]`.magenta;
|
|
326
|
+
}
|
|
327
|
+
if (upToDate) {
|
|
328
|
+
txt += ` [Up to date]`.green;
|
|
329
|
+
}
|
|
330
|
+
if (unsafeUpdateVersion) {
|
|
331
|
+
txt += ` [${unsafeUpdateVersion} available (potentially unsafe)]`.cyan;
|
|
332
|
+
}
|
|
333
|
+
return txt;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Get repository URL from package data
|
|
338
|
+
*
|
|
339
|
+
* @template {ExtensionType} ExtType
|
|
340
|
+
* @param {ExtensionListData<ExtType>} data
|
|
341
|
+
* @returns {Promise<string|null>}
|
|
342
|
+
* @private
|
|
343
|
+
*/
|
|
344
|
+
async _getRepositoryUrl(data) {
|
|
345
|
+
if (data.installed && data.installPath) {
|
|
346
|
+
return await this._getRepositoryUrlFromInstalled(
|
|
347
|
+
/** @type {InstalledExtensionListData<ExtType>} */ (data)
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
if (data.pkgName && !data.installed) {
|
|
351
|
+
return await this._getRepositoryUrlFromNpm(data.pkgName);
|
|
352
|
+
}
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Get repository URL from installed extension's package.json
|
|
358
|
+
*
|
|
359
|
+
* @template {ExtensionType} ExtType
|
|
360
|
+
* @param {InstalledExtensionListData<ExtType>} data
|
|
361
|
+
* @returns {Promise<string|null>}
|
|
362
|
+
* @private
|
|
363
|
+
*/
|
|
364
|
+
async _getRepositoryUrlFromInstalled(data) {
|
|
365
|
+
try {
|
|
366
|
+
const pkgJsonPath = path.join(data.installPath, 'package.json');
|
|
367
|
+
if (await fs.exists(pkgJsonPath)) {
|
|
368
|
+
const pkg = JSON.parse(await fs.readFile(pkgJsonPath, 'utf8'));
|
|
369
|
+
if (pkg.repository) {
|
|
370
|
+
if (typeof pkg.repository === 'string') {
|
|
371
|
+
return pkg.repository;
|
|
372
|
+
}
|
|
373
|
+
if (pkg.repository.url) {
|
|
374
|
+
return pkg.repository.url.replace(/^git\+/, '').replace(/\.git$/, '');
|
|
236
375
|
}
|
|
237
376
|
}
|
|
238
377
|
}
|
|
239
|
-
|
|
240
|
-
|
|
378
|
+
} catch {
|
|
379
|
+
// Ignore errors reading package.json
|
|
241
380
|
}
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
242
383
|
|
|
243
|
-
|
|
384
|
+
/**
|
|
385
|
+
* Get repository URL from npm for a package name
|
|
386
|
+
*
|
|
387
|
+
* @param {string} pkgName
|
|
388
|
+
* @returns {Promise<string|null>}
|
|
389
|
+
* @private
|
|
390
|
+
*/
|
|
391
|
+
async _getRepositoryUrlFromNpm(pkgName) {
|
|
392
|
+
try {
|
|
393
|
+
const repoInfo = await npm.getPackageInfo(pkgName, ['repository']);
|
|
394
|
+
// When requesting only 'repository', npm.getPackageInfo returns the repository object directly
|
|
395
|
+
if (repoInfo) {
|
|
396
|
+
if (typeof repoInfo === 'string') {
|
|
397
|
+
return repoInfo;
|
|
398
|
+
}
|
|
399
|
+
if (repoInfo.url) {
|
|
400
|
+
return repoInfo.url.replace(/^git\+/, '').replace(/\.git$/, '');
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
} catch {
|
|
404
|
+
// Ignore errors fetching from npm
|
|
405
|
+
}
|
|
406
|
+
return null;
|
|
244
407
|
}
|
|
245
408
|
|
|
246
409
|
/**
|
|
@@ -438,34 +601,24 @@ class ExtensionCliCommand {
|
|
|
438
601
|
* @returns {Promise<ExtInstallReceipt<ExtType>>}
|
|
439
602
|
*/
|
|
440
603
|
async installViaNpm({installSpec, pkgName, pkgVer, installType}) {
|
|
441
|
-
const
|
|
604
|
+
const installMsg = `Installing '${installSpec}'`;
|
|
605
|
+
const validateMsg = `Validating '${installSpec}'`;
|
|
442
606
|
|
|
443
607
|
// the string used for installation is either <name>@<ver> in the case of a standard NPM
|
|
444
608
|
// package, or whatever the user sent in otherwise.
|
|
445
609
|
const installStr = installType === INSTALL_TYPE_NPM ? `${pkgName}${pkgVer ? `@${pkgVer}` : ''}` : installSpec;
|
|
446
610
|
const appiumHome = this.config.appiumHome;
|
|
447
611
|
try {
|
|
448
|
-
const {pkg, installPath} = await spinWith(
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
612
|
+
const {pkg, installPath} = await spinWith(
|
|
613
|
+
this.isJsonOutput,
|
|
614
|
+
installMsg,
|
|
615
|
+
async () => await npm.installPackage(appiumHome, installStr, {pkgName, installType})
|
|
616
|
+
);
|
|
617
|
+
|
|
618
|
+
await spinWith(this.isJsonOutput, validateMsg, async () => {
|
|
453
619
|
this.validatePackageJson(pkg, installSpec);
|
|
454
|
-
return {pkg, installPath};
|
|
455
620
|
});
|
|
456
621
|
|
|
457
|
-
/** @type {Promise<void>[]} */
|
|
458
|
-
const symlinkInjectionPromises = _.uniq([
|
|
459
|
-
...Object.values(this.config.installedExtensions).map((ext) => ext.installPath),
|
|
460
|
-
installPath,
|
|
461
|
-
]).map((instPath) => injectAppiumSymlink.bind(this)(path.join(instPath, 'node_modules')));
|
|
462
|
-
// After the extension is installed, we try to inject the appium module symlink
|
|
463
|
-
// into the extension's node_modules folder if it is not there yet.
|
|
464
|
-
// We also inject the symlink into other installed extensions' node_modules folders
|
|
465
|
-
// as these might be cleaned up unexpectedly by npm
|
|
466
|
-
// (see https://github.com/appium/python-client/pull/1177#issuecomment-3419826643).
|
|
467
|
-
await Promise.all(symlinkInjectionPromises);
|
|
468
|
-
|
|
469
622
|
return this.getInstallationReceipt({
|
|
470
623
|
pkg,
|
|
471
624
|
installPath,
|
|
@@ -971,11 +1124,35 @@ class ExtensionCliCommand {
|
|
|
971
1124
|
* This is needed to ensure proper module resolution for installed extensions,
|
|
972
1125
|
* especially ESM ones.
|
|
973
1126
|
*
|
|
974
|
-
* @
|
|
1127
|
+
* @param {ExtensionConfig<ExtensionType>} driverConfig
|
|
1128
|
+
* @param {ExtensionConfig<ExtensionType>} pluginConfig
|
|
1129
|
+
* @param {import('@appium/types').AppiumLogger} logger
|
|
1130
|
+
*/
|
|
1131
|
+
export async function injectAppiumSymlinks(driverConfig, pluginConfig, logger) {
|
|
1132
|
+
const installPaths = _.compact([
|
|
1133
|
+
...Object.values(driverConfig.installedExtensions || {}),
|
|
1134
|
+
...Object.values(pluginConfig.installedExtensions || {})
|
|
1135
|
+
].filter((details) => details.installType === INSTALL_TYPE_NPM)
|
|
1136
|
+
.map((details) => details.installPath));
|
|
1137
|
+
// After the extension is installed, we try to inject the appium module symlink
|
|
1138
|
+
// into the extension's node_modules folder if it is not there yet.
|
|
1139
|
+
// We also inject the symlink into other installed extensions' node_modules folders
|
|
1140
|
+
// as these might be cleaned up unexpectedly by npm
|
|
1141
|
+
// (see https://github.com/appium/python-client/pull/1177#issuecomment-3419826643).
|
|
1142
|
+
await Promise.all(
|
|
1143
|
+
installPaths.map((installPath) => injectAppiumSymlink(path.join(installPath, 'node_modules'), logger))
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
/**
|
|
1148
|
+
* This is needed to ensure proper module resolution for installed extensions,
|
|
1149
|
+
* especially ESM ones.
|
|
1150
|
+
*
|
|
975
1151
|
* @param {string} dstFolder The destination folder where the symlink should be created
|
|
1152
|
+
* @param {import('@appium/types').AppiumLogger} logger
|
|
976
1153
|
* @returns {Promise<void>}
|
|
977
1154
|
*/
|
|
978
|
-
async function injectAppiumSymlink(dstFolder) {
|
|
1155
|
+
async function injectAppiumSymlink(dstFolder, logger) {
|
|
979
1156
|
let appiumModuleRoot;
|
|
980
1157
|
try {
|
|
981
1158
|
appiumModuleRoot = getAppiumModuleRoot();
|
|
@@ -985,7 +1162,7 @@ async function injectAppiumSymlink(dstFolder) {
|
|
|
985
1162
|
}
|
|
986
1163
|
} catch (error) {
|
|
987
1164
|
// This error is not fatal, we may still doing just fine if the module being loaded is a CJS one
|
|
988
|
-
|
|
1165
|
+
logger.info(
|
|
989
1166
|
`Cannot create a symlink to the appium module '${appiumModuleRoot}' in '${dstFolder}'. ` +
|
|
990
1167
|
`Original error: ${error.message}`
|
|
991
1168
|
);
|
|
@@ -1013,6 +1190,7 @@ export {ExtensionCliCommand as ExtensionCommand};
|
|
|
1013
1190
|
* @property {string|null} unsafeUpdateVersion - Same as above, but a major version bump
|
|
1014
1191
|
* @property {string} [updateError] - Update check error message (if present)
|
|
1015
1192
|
* @property {boolean} [devMode] - If Appium is run from an extension's working copy
|
|
1193
|
+
* @property {string} [repositoryUrl] - Repository URL for the extension (if available)
|
|
1016
1194
|
*/
|
|
1017
1195
|
|
|
1018
1196
|
/**
|
package/lib/cli/parser.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {fs} from '@appium/support';
|
|
2
2
|
import {ArgumentParser} from 'argparse';
|
|
3
3
|
import _ from 'lodash';
|
|
4
|
-
import path from 'path';
|
|
4
|
+
import path from 'node:path';
|
|
5
5
|
import {
|
|
6
6
|
DRIVER_TYPE,
|
|
7
7
|
EXT_SUBCOMMAND_DOCTOR,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
SERVER_SUBCOMMAND,
|
|
15
15
|
SETUP_SUBCOMMAND
|
|
16
16
|
} from '../constants';
|
|
17
|
-
import {finalizeSchema, getArgSpec, hasArgSpec} from '../schema';
|
|
17
|
+
import {finalizeSchema, getAllArgSpecs, getArgSpec, hasArgSpec} from '../schema';
|
|
18
18
|
import {rootDir} from '../config';
|
|
19
19
|
import {getExtensionArgs, getServerArgs} from './args';
|
|
20
20
|
import {
|
|
@@ -158,6 +158,25 @@ class ArgParser {
|
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Normalize hyphenated server arg keys (e.g. "log-level") to dest form (e.g. "loglevel").
|
|
163
|
+
* Use when server args come from programmatic init rather than the CLI, so they match
|
|
164
|
+
* the shape produced by parseArgs() / _transformParsedArgs().
|
|
165
|
+
* Mutates the given object.
|
|
166
|
+
*
|
|
167
|
+
* @param {object} obj - Object that may contain server args with schema property names
|
|
168
|
+
* @returns {object} The same object with keys normalized
|
|
169
|
+
*/
|
|
170
|
+
static normalizeServerArgs(obj) {
|
|
171
|
+
for (const spec of getAllArgSpecs().values()) {
|
|
172
|
+
if (!spec.extType && obj[spec.name] !== undefined && spec.rawDest !== spec.name) {
|
|
173
|
+
obj[spec.rawDest] = obj[spec.name] ?? obj[spec.rawDest];
|
|
174
|
+
delete obj[spec.name];
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return obj;
|
|
178
|
+
}
|
|
179
|
+
|
|
161
180
|
/**
|
|
162
181
|
* Given an object full of arguments as returned by `argparser.parse_args`,
|
|
163
182
|
* expand the ones for extensions into a nested object structure and rename
|
package/lib/constants.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {util, fs, system} from '@appium/support';
|
|
2
2
|
import B from 'bluebird';
|
|
3
3
|
import _ from 'lodash';
|
|
4
|
-
import path from 'path';
|
|
4
|
+
import path from 'node:path';
|
|
5
5
|
import resolveFrom from 'resolve-from';
|
|
6
6
|
import {satisfies} from 'semver';
|
|
7
7
|
import {commandClasses} from '../cli/extension';
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
isAllowedSchemaFileExtension,
|
|
13
13
|
registerSchema,
|
|
14
14
|
} from '../schema/schema';
|
|
15
|
-
import { pathToFileURL } from 'url';
|
|
15
|
+
import { pathToFileURL } from 'node:url';
|
|
16
16
|
|
|
17
17
|
const DEFAULT_ENTRY_POINT = 'index.js';
|
|
18
18
|
/**
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import B from 'bluebird';
|
|
6
6
|
import {env, fs} from '@appium/support';
|
|
7
7
|
import _ from 'lodash';
|
|
8
|
-
import path from 'path';
|
|
8
|
+
import path from 'node:path';
|
|
9
9
|
import * as YAML from 'yaml';
|
|
10
10
|
import {CURRENT_SCHEMA_REV, DRIVER_TYPE, PLUGIN_TYPE} from '../constants';
|
|
11
11
|
import {INSTALL_TYPE_NPM, INSTALL_TYPE_DEV} from './extension-config';
|
|
@@ -19,7 +19,7 @@ import type {
|
|
|
19
19
|
BiDiCommandNamesToInfosMap,
|
|
20
20
|
ExecuteMethodMap,
|
|
21
21
|
} from '@appium/types';
|
|
22
|
-
import type {
|
|
22
|
+
import type {AppiumDriver} from './appium';
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
export async function listCommands(this: AppiumDriver, sessionId?: string): Promise<ListCommandsResponse> {
|