@appium/base-driver 9.3.14 → 9.3.16
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/build/lib/basedriver/commands/index.d.ts +0 -1
- package/build/lib/basedriver/commands/index.d.ts.map +1 -1
- package/build/lib/basedriver/commands/index.js +0 -1
- package/build/lib/basedriver/commands/index.js.map +1 -1
- package/build/lib/basedriver/commands/log.d.ts +1 -1
- package/build/lib/basedriver/commands/log.d.ts.map +1 -1
- package/build/lib/basedriver/commands/log.js.map +1 -1
- package/build/lib/basedriver/core.d.ts +35 -111
- package/build/lib/basedriver/core.d.ts.map +1 -1
- package/build/lib/basedriver/core.js +21 -75
- package/build/lib/basedriver/core.js.map +1 -1
- package/build/lib/basedriver/desired-caps.d.ts +2 -2
- package/build/lib/basedriver/desired-caps.d.ts.map +1 -1
- package/build/lib/basedriver/device-settings.d.ts +4 -3
- package/build/lib/basedriver/device-settings.d.ts.map +1 -1
- package/build/lib/basedriver/device-settings.js +5 -2
- package/build/lib/basedriver/device-settings.js.map +1 -1
- package/build/lib/basedriver/driver.d.ts +8 -7
- package/build/lib/basedriver/driver.d.ts.map +1 -1
- package/build/lib/basedriver/driver.js +22 -10
- package/build/lib/basedriver/driver.js.map +1 -1
- package/build/lib/basedriver/helpers.d.ts +1 -1
- package/build/lib/basedriver/helpers.d.ts.map +1 -1
- package/build/lib/basedriver/helpers.js +8 -5
- package/build/lib/basedriver/helpers.js.map +1 -1
- package/build/lib/constants.d.ts +9 -8
- package/build/lib/constants.d.ts.map +1 -1
- package/build/lib/constants.js.map +1 -1
- package/build/lib/express/middleware.d.ts +1 -1
- package/build/lib/express/middleware.d.ts.map +1 -1
- package/build/lib/express/server.d.ts +3 -3
- package/build/lib/index.d.ts +2 -2
- package/build/lib/index.d.ts.map +1 -1
- package/build/lib/index.js +2 -1
- package/build/lib/index.js.map +1 -1
- package/build/lib/jsonwp-proxy/proxy.d.ts +6 -5
- package/build/lib/jsonwp-proxy/proxy.d.ts.map +1 -1
- package/build/lib/jsonwp-proxy/proxy.js +1 -0
- package/build/lib/jsonwp-proxy/proxy.js.map +1 -1
- package/build/lib/protocol/errors.d.ts +2 -2
- package/build/lib/protocol/errors.d.ts.map +1 -1
- package/build/lib/protocol/errors.js +1 -0
- package/build/lib/protocol/errors.js.map +1 -1
- package/build/lib/protocol/helpers.d.ts +2 -2
- package/build/lib/protocol/helpers.d.ts.map +1 -1
- package/build/lib/protocol/index.d.ts +17 -17
- package/build/lib/protocol/index.d.ts.map +1 -1
- package/build/lib/protocol/protocol.d.ts +2 -2
- package/build/lib/protocol/protocol.d.ts.map +1 -1
- package/build/lib/protocol/routes.d.ts +1 -1
- package/lib/basedriver/commands/index.ts +0 -1
- package/lib/basedriver/commands/log.ts +3 -3
- package/lib/basedriver/{core.js → core.ts} +74 -149
- package/lib/basedriver/device-settings.js +6 -2
- package/lib/basedriver/driver.ts +45 -28
- package/lib/basedriver/helpers.js +8 -9
- package/lib/{constants.js → constants.ts} +2 -1
- package/lib/index.js +1 -1
- package/lib/jsonwp-proxy/proxy.js +1 -0
- package/lib/protocol/errors.js +1 -0
- package/package.json +6 -6
- package/build/lib/basedriver/commands/settings.d.ts +0 -6
- package/build/lib/basedriver/commands/settings.d.ts.map +0 -1
- package/build/lib/basedriver/commands/settings.js +0 -19
- package/build/lib/basedriver/commands/settings.js.map +0 -1
- package/lib/basedriver/commands/settings.ts +0 -26
|
@@ -9,7 +9,7 @@ import {errors} from '../protocol/errors';
|
|
|
9
9
|
export const MAX_SETTINGS_SIZE = 20 * 1024 * 1024; // 20 MB
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* @template {
|
|
12
|
+
* @template {StringRecord} T
|
|
13
13
|
* @implements {IDeviceSettings<T>}
|
|
14
14
|
*/
|
|
15
15
|
export class DeviceSettings {
|
|
@@ -74,6 +74,10 @@ export class DeviceSettings {
|
|
|
74
74
|
export default DeviceSettings;
|
|
75
75
|
|
|
76
76
|
/**
|
|
77
|
-
* @
|
|
77
|
+
* @typedef {import('@appium/types').StringRecord} StringRecord
|
|
78
|
+
*/
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @template {StringRecord} [T=StringRecord]
|
|
78
82
|
* @typedef {import('@appium/types').IDeviceSettings<T>} IDeviceSettings
|
|
79
83
|
*/
|
package/lib/basedriver/driver.ts
CHANGED
|
@@ -1,28 +1,29 @@
|
|
|
1
|
-
import {validateCaps, processCapabilities} from './capabilities';
|
|
2
|
-
import {DriverCore} from './core';
|
|
3
1
|
import {util} from '@appium/support';
|
|
2
|
+
import {
|
|
3
|
+
BASE_DESIRED_CAP_CONSTRAINTS,
|
|
4
|
+
type AppiumServer,
|
|
5
|
+
type BaseDriverCapConstraints,
|
|
6
|
+
type Capabilities,
|
|
7
|
+
type Constraints,
|
|
8
|
+
type DefaultCreateSessionResult,
|
|
9
|
+
type Driver,
|
|
10
|
+
type DriverCaps,
|
|
11
|
+
type DriverData,
|
|
12
|
+
type MultiSessionData,
|
|
13
|
+
type ServerArgs,
|
|
14
|
+
type StringRecord,
|
|
15
|
+
type W3CDriverCaps,
|
|
16
|
+
type InitialOpts,
|
|
17
|
+
type DefaultDeleteSessionResult,
|
|
18
|
+
type SingularSessionData,
|
|
19
|
+
} from '@appium/types';
|
|
4
20
|
import B from 'bluebird';
|
|
5
21
|
import _ from 'lodash';
|
|
6
22
|
import {fixCaps, isW3cCaps} from '../helpers/capabilities';
|
|
7
23
|
import {DELETE_SESSION_COMMAND, determineProtocol, errors} from '../protocol';
|
|
24
|
+
import {processCapabilities, validateCaps} from './capabilities';
|
|
25
|
+
import {DriverCore} from './core';
|
|
8
26
|
import helpers from './helpers';
|
|
9
|
-
import {
|
|
10
|
-
AppiumServer,
|
|
11
|
-
BaseDriverCapConstraints,
|
|
12
|
-
BASE_DESIRED_CAP_CONSTRAINTS,
|
|
13
|
-
Capabilities,
|
|
14
|
-
Constraints,
|
|
15
|
-
DefaultCreateSessionResult,
|
|
16
|
-
Driver,
|
|
17
|
-
DriverCaps,
|
|
18
|
-
DriverData,
|
|
19
|
-
DriverOpts,
|
|
20
|
-
MultiSessionData,
|
|
21
|
-
ServerArgs,
|
|
22
|
-
SingularSessionData,
|
|
23
|
-
StringRecord,
|
|
24
|
-
W3CDriverCaps,
|
|
25
|
-
} from '@appium/types';
|
|
26
27
|
|
|
27
28
|
const EVENT_SESSION_INIT = 'newSessionRequested';
|
|
28
29
|
const EVENT_SESSION_START = 'newSessionStarted';
|
|
@@ -31,26 +32,26 @@ const EVENT_SESSION_QUIT_DONE = 'quitSessionFinished';
|
|
|
31
32
|
const ON_UNEXPECTED_SHUTDOWN_EVENT = 'onUnexpectedShutdown';
|
|
32
33
|
|
|
33
34
|
export class BaseDriver<
|
|
34
|
-
C extends Constraints,
|
|
35
|
+
const C extends Constraints,
|
|
35
36
|
CArgs extends StringRecord = StringRecord,
|
|
36
37
|
Settings extends StringRecord = StringRecord,
|
|
38
|
+
CreateResult = DefaultCreateSessionResult<C>,
|
|
39
|
+
DeleteResult = DefaultDeleteSessionResult,
|
|
37
40
|
SessionData extends StringRecord = StringRecord
|
|
38
41
|
>
|
|
39
42
|
extends DriverCore<C, Settings>
|
|
40
|
-
implements Driver<C, CArgs>
|
|
43
|
+
implements Driver<C, CArgs, Settings, CreateResult, DeleteResult, SessionData>
|
|
41
44
|
{
|
|
42
45
|
cliArgs: CArgs & ServerArgs;
|
|
43
|
-
|
|
44
46
|
caps: DriverCaps<C>;
|
|
45
47
|
originalCaps: W3CDriverCaps<C>;
|
|
46
48
|
desiredCapConstraints: C;
|
|
47
|
-
opts: DriverOpts<C>;
|
|
48
49
|
server?: AppiumServer;
|
|
49
50
|
serverHost?: string;
|
|
50
51
|
serverPort?: number;
|
|
51
52
|
serverPath?: string;
|
|
52
53
|
|
|
53
|
-
constructor(opts:
|
|
54
|
+
constructor(opts: InitialOpts, shouldValidateCaps = true) {
|
|
54
55
|
super(opts, shouldValidateCaps);
|
|
55
56
|
|
|
56
57
|
this.caps = {} as DriverCaps<C>;
|
|
@@ -63,7 +64,7 @@ export class BaseDriver<
|
|
|
63
64
|
*
|
|
64
65
|
* Subclasses _shouldn't_ need to use this. If you need to use this, please create
|
|
65
66
|
* an issue:
|
|
66
|
-
* @see https://github.com/appium/appium/issues/new
|
|
67
|
+
* @see {@link https://github.com/appium/appium/issues/new}
|
|
67
68
|
*/
|
|
68
69
|
protected get _desiredCapConstraints(): Readonly<BaseDriverCapConstraints & C> {
|
|
69
70
|
return Object.freeze(_.merge({}, BASE_DESIRED_CAP_CONSTRAINTS, this.desiredCapConstraints));
|
|
@@ -234,7 +235,7 @@ export class BaseDriver<
|
|
|
234
235
|
w3cCapabilities?: W3CDriverCaps<C>,
|
|
235
236
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
236
237
|
driverData?: DriverData[]
|
|
237
|
-
): Promise<
|
|
238
|
+
): Promise<CreateResult> {
|
|
238
239
|
if (this.sessionId !== null) {
|
|
239
240
|
throw new errors.SessionNotCreatedError(
|
|
240
241
|
'Cannot create a new session while one is in progress'
|
|
@@ -311,7 +312,7 @@ export class BaseDriver<
|
|
|
311
312
|
|
|
312
313
|
this.log.info(`Session created with session id: ${this.sessionId}`);
|
|
313
314
|
|
|
314
|
-
return [this.sessionId, caps];
|
|
315
|
+
return [this.sessionId, caps] as CreateResult;
|
|
315
316
|
}
|
|
316
317
|
async getSessions() {
|
|
317
318
|
const ret: MultiSessionData<C>[] = [];
|
|
@@ -330,7 +331,9 @@ export class BaseDriver<
|
|
|
330
331
|
* Returns capabilities for the session and event history (if applicable)
|
|
331
332
|
*/
|
|
332
333
|
async getSession() {
|
|
333
|
-
return
|
|
334
|
+
return (
|
|
335
|
+
this.caps.eventTimings ? {...this.caps, events: this.eventHistory} : this.caps
|
|
336
|
+
) as SingularSessionData<C, SessionData>;
|
|
334
337
|
}
|
|
335
338
|
|
|
336
339
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -380,6 +383,20 @@ export class BaseDriver<
|
|
|
380
383
|
|
|
381
384
|
return true;
|
|
382
385
|
}
|
|
386
|
+
|
|
387
|
+
async updateSettings(newSettings: Settings) {
|
|
388
|
+
if (!this.settings) {
|
|
389
|
+
this.log.errorAndThrow('Cannot update settings; settings object not found');
|
|
390
|
+
}
|
|
391
|
+
return await this.settings.update(newSettings);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
async getSettings() {
|
|
395
|
+
if (!this.settings) {
|
|
396
|
+
this.log.errorAndThrow('Cannot get settings; settings object not found');
|
|
397
|
+
}
|
|
398
|
+
return this.settings.getSettings();
|
|
399
|
+
}
|
|
383
400
|
}
|
|
384
401
|
|
|
385
402
|
export * from './commands';
|
|
@@ -141,6 +141,9 @@ async function configureApp(
|
|
|
141
141
|
const isUrl = protocol === null ? false : ['http:', 'https:'].includes(protocol);
|
|
142
142
|
|
|
143
143
|
const cachedAppInfo = APPLICATIONS_CACHE.get(app);
|
|
144
|
+
if (cachedAppInfo) {
|
|
145
|
+
logger.debug(`Cached app data: ${JSON.stringify(cachedAppInfo, null, 2)}`);
|
|
146
|
+
}
|
|
144
147
|
|
|
145
148
|
return await APPLICATIONS_CACHE_GUARD.acquire(app, async () => {
|
|
146
149
|
if (isUrl) {
|
|
@@ -150,27 +153,23 @@ async function configureApp(
|
|
|
150
153
|
...DEFAULT_REQ_HEADERS,
|
|
151
154
|
};
|
|
152
155
|
if (cachedAppInfo?.etag) {
|
|
153
|
-
reqHeaders['if-none-match'] =
|
|
156
|
+
reqHeaders['if-none-match'] = cachedAppInfo.etag;
|
|
154
157
|
} else if (cachedAppInfo?.lastModified) {
|
|
155
|
-
reqHeaders['if-modified-since'] =
|
|
158
|
+
reqHeaders['if-modified-since'] = cachedAppInfo.lastModified.toUTCString();
|
|
156
159
|
}
|
|
157
160
|
|
|
158
161
|
let {headers, stream, status} = await queryAppLink(newApp, reqHeaders);
|
|
159
162
|
try {
|
|
160
163
|
if (!_.isEmpty(headers)) {
|
|
161
|
-
logger.debug(`Etag: ${
|
|
164
|
+
logger.debug(`Etag: ${headers.etag}`);
|
|
162
165
|
if (headers.etag) {
|
|
163
166
|
remoteAppProps.etag = headers.etag;
|
|
164
167
|
}
|
|
165
|
-
logger.debug(
|
|
166
|
-
`Last-Modified: ${remoteAppProps?.['last-modified']} -> ${headers['last-modified']}`
|
|
167
|
-
);
|
|
168
|
+
logger.debug(`Last-Modified: ${headers['last-modified']}`);
|
|
168
169
|
if (headers['last-modified']) {
|
|
169
170
|
remoteAppProps.lastModified = new Date(headers['last-modified']);
|
|
170
171
|
}
|
|
171
|
-
logger.debug(
|
|
172
|
-
`Cache-Control: ${remoteAppProps?.['cache-control']} -> ${headers['cache-control']}`
|
|
173
|
-
);
|
|
172
|
+
logger.debug(`Cache-Control: ${headers['cache-control']}`);
|
|
174
173
|
if (headers['cache-control']) {
|
|
175
174
|
remoteAppProps.immutable = /\bimmutable\b/i.test(headers['cache-control']);
|
|
176
175
|
const maxAgeMatch = /\bmax-age=(\d+)\b/i.exec(headers['cache-control']);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {util} from '@appium/support';
|
|
2
|
+
import {Protocol} from '@appium/types';
|
|
2
3
|
|
|
3
4
|
// The default maximum length of a single log record
|
|
4
5
|
// containing http request/response body
|
|
@@ -12,7 +13,7 @@ const W3C_ELEMENT_KEY = util.W3C_WEB_ELEMENT_IDENTIFIER;
|
|
|
12
13
|
const PROTOCOLS = {
|
|
13
14
|
W3C: 'W3C',
|
|
14
15
|
MJSONWP: 'MJSONWP',
|
|
15
|
-
}
|
|
16
|
+
} as const satisfies Record<Protocol, Protocol>;
|
|
16
17
|
|
|
17
18
|
// Before Appium 2.0, this default value was '/wd/hub' by historical reasons.
|
|
18
19
|
const DEFAULT_BASE_PATH = '';
|
package/lib/index.js
CHANGED
|
@@ -15,7 +15,7 @@ export default BaseDriver;
|
|
|
15
15
|
// MJSONWP exports
|
|
16
16
|
export * from './protocol';
|
|
17
17
|
export {errorFromMJSONWPStatusCode as errorFromCode} from './protocol';
|
|
18
|
-
export {DEFAULT_BASE_PATH, PROTOCOLS} from './constants';
|
|
18
|
+
export {DEFAULT_BASE_PATH, PROTOCOLS, W3C_ELEMENT_KEY} from './constants';
|
|
19
19
|
|
|
20
20
|
// Express exports
|
|
21
21
|
export {STATIC_DIR} from './express/static';
|
package/lib/protocol/errors.js
CHANGED
|
@@ -1078,6 +1078,7 @@ function getResponseForW3CError(err) {
|
|
|
1078
1078
|
w3cLog.debug(`Bad parameters: ${err}`);
|
|
1079
1079
|
w3cErrorString = BadParametersError.error();
|
|
1080
1080
|
} else {
|
|
1081
|
+
// @ts-expect-error unclear what the problem is here
|
|
1081
1082
|
w3cErrorString = err.error;
|
|
1082
1083
|
}
|
|
1083
1084
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appium/base-driver",
|
|
3
|
-
"version": "9.3.
|
|
3
|
+
"version": "9.3.16",
|
|
4
4
|
"description": "Base driver class for Appium drivers",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"automation",
|
|
@@ -37,15 +37,15 @@
|
|
|
37
37
|
"!build/tsconfig.tsbuildinfo"
|
|
38
38
|
],
|
|
39
39
|
"scripts": {
|
|
40
|
-
"test": "
|
|
41
|
-
"test:e2e": "mocha
|
|
40
|
+
"test": "run-p test:unit test:types",
|
|
41
|
+
"test:e2e": "mocha --timeout 20s --slow 10s \"./test/e2e/**/*.spec.js\"",
|
|
42
42
|
"test:smoke": "node ./index.js",
|
|
43
43
|
"test:unit": "mocha \"./test/unit/**/*.spec.js\"",
|
|
44
44
|
"test:types": "tsd"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@appium/support": "^4.1.
|
|
48
|
-
"@appium/types": "^0.13.
|
|
47
|
+
"@appium/support": "^4.1.3",
|
|
48
|
+
"@appium/types": "^0.13.2",
|
|
49
49
|
"@colors/colors": "1.5.0",
|
|
50
50
|
"@types/async-lock": "1.4.0",
|
|
51
51
|
"@types/bluebird": "3.5.38",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"publishConfig": {
|
|
78
78
|
"access": "public"
|
|
79
79
|
},
|
|
80
|
-
"gitHead": "
|
|
80
|
+
"gitHead": "ec57ff4c2a1ae3ecbab11bd02d4249ca67626d83",
|
|
81
81
|
"typedoc": {
|
|
82
82
|
"entryPoint": "./lib/index.js"
|
|
83
83
|
},
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../../../lib/basedriver/commands/settings.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,WAAW,EAAE,iBAAiB,EAAe,MAAM,eAAe,CAAC;AAG3E,OAAO,QAAQ,WAAW,CAAC;IAEzB,UAAU,UAAU,CAAC,CAAC,SAAS,WAAW,CAAE,SAAQ,iBAAiB;KAAG;CACzE"}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const mixin_1 = require("./mixin");
|
|
4
|
-
const SettingsCommands = {
|
|
5
|
-
async updateSettings(newSettings) {
|
|
6
|
-
if (!this.settings) {
|
|
7
|
-
this.log.errorAndThrow('Cannot update settings; settings object not found');
|
|
8
|
-
}
|
|
9
|
-
return await this.settings.update(newSettings);
|
|
10
|
-
},
|
|
11
|
-
async getSettings() {
|
|
12
|
-
if (!this.settings) {
|
|
13
|
-
this.log.errorAndThrow('Cannot get settings; settings object not found');
|
|
14
|
-
}
|
|
15
|
-
return this.settings.getSettings();
|
|
16
|
-
},
|
|
17
|
-
};
|
|
18
|
-
(0, mixin_1.mixin)(SettingsCommands);
|
|
19
|
-
//# sourceMappingURL=settings.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../../../lib/basedriver/commands/settings.ts"],"names":[],"mappings":";;AAEA,mCAA8B;AAO9B,MAAM,gBAAgB,GAAsB;IAC1C,KAAK,CAAC,cAAc,CAA6C,WAAyB;QACxF,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE;YAClB,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,mDAAmD,CAAC,CAAC;SAC7E;QACD,OAAO,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IACjD,CAAC;IAED,KAAK,CAAC,WAAW;QACf,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE;YAClB,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,gDAAgD,CAAC,CAAC;SAC1E;QACD,OAAO,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;IACrC,CAAC;CACF,CAAC;AAEF,IAAA,aAAK,EAAC,gBAAgB,CAAC,CAAC"}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import {BaseDriver} from '../driver';
|
|
2
|
-
import {Constraints, ISettingsCommands, StringRecord} from '@appium/types';
|
|
3
|
-
import {mixin} from './mixin';
|
|
4
|
-
|
|
5
|
-
declare module '../driver' {
|
|
6
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
7
|
-
interface BaseDriver<C extends Constraints> extends ISettingsCommands {}
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const SettingsCommands: ISettingsCommands = {
|
|
11
|
-
async updateSettings<C extends Constraints>(this: BaseDriver<C>, newSettings: StringRecord) {
|
|
12
|
-
if (!this.settings) {
|
|
13
|
-
this.log.errorAndThrow('Cannot update settings; settings object not found');
|
|
14
|
-
}
|
|
15
|
-
return await this.settings.update(newSettings);
|
|
16
|
-
},
|
|
17
|
-
|
|
18
|
-
async getSettings<C extends Constraints>(this: BaseDriver<C>) {
|
|
19
|
-
if (!this.settings) {
|
|
20
|
-
this.log.errorAndThrow('Cannot get settings; settings object not found');
|
|
21
|
-
}
|
|
22
|
-
return this.settings.getSettings();
|
|
23
|
-
},
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
mixin(SettingsCommands);
|