appium-adb 14.3.4 → 14.4.0
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/CHANGELOG.md +12 -0
- package/build/lib/adb.d.ts +9 -4
- package/build/lib/adb.d.ts.map +1 -1
- package/build/lib/adb.js +8 -9
- package/build/lib/adb.js.map +1 -1
- package/build/lib/index.d.ts +1 -1
- package/build/lib/index.d.ts.map +1 -1
- package/build/lib/index.js +2 -2
- package/build/lib/index.js.map +1 -1
- package/build/lib/logcat.d.ts.map +1 -1
- package/build/lib/logcat.js +6 -14
- package/build/lib/logcat.js.map +1 -1
- package/build/lib/tools/aab-utils.d.ts.map +1 -1
- package/build/lib/tools/aab-utils.js +2 -2
- package/build/lib/tools/aab-utils.js.map +1 -1
- package/build/lib/tools/android-manifest.d.ts.map +1 -1
- package/build/lib/tools/android-manifest.js +11 -12
- package/build/lib/tools/android-manifest.js.map +1 -1
- package/build/lib/tools/apk-signing.d.ts.map +1 -1
- package/build/lib/tools/apk-signing.js +18 -19
- package/build/lib/tools/apk-signing.js.map +1 -1
- package/build/lib/tools/apk-utils.d.ts.map +1 -1
- package/build/lib/tools/apk-utils.js +39 -40
- package/build/lib/tools/apk-utils.js.map +1 -1
- package/build/lib/tools/apks-utils.d.ts.map +1 -1
- package/build/lib/tools/apks-utils.js +8 -9
- package/build/lib/tools/apks-utils.js.map +1 -1
- package/build/lib/tools/app-commands.d.ts.map +1 -1
- package/build/lib/tools/app-commands.js +37 -36
- package/build/lib/tools/app-commands.js.map +1 -1
- package/build/lib/tools/device-settings.d.ts.map +1 -1
- package/build/lib/tools/device-settings.js +17 -18
- package/build/lib/tools/device-settings.js.map +1 -1
- package/build/lib/tools/emulator-commands.d.ts.map +1 -1
- package/build/lib/tools/emulator-commands.js +22 -23
- package/build/lib/tools/emulator-commands.js.map +1 -1
- package/build/lib/tools/fs-commands.d.ts.map +1 -1
- package/build/lib/tools/fs-commands.js +5 -4
- package/build/lib/tools/fs-commands.js.map +1 -1
- package/build/lib/tools/general-commands.d.ts.map +1 -1
- package/build/lib/tools/general-commands.js +29 -29
- package/build/lib/tools/general-commands.js.map +1 -1
- package/build/lib/tools/keyboard-commands.d.ts.map +1 -1
- package/build/lib/tools/keyboard-commands.js +6 -4
- package/build/lib/tools/keyboard-commands.js.map +1 -1
- package/build/lib/tools/lockmgmt.d.ts.map +1 -1
- package/build/lib/tools/lockmgmt.js +17 -20
- package/build/lib/tools/lockmgmt.js.map +1 -1
- package/build/lib/tools/logcat-commands.d.ts.map +1 -1
- package/build/lib/tools/logcat-commands.js +5 -9
- package/build/lib/tools/logcat-commands.js.map +1 -1
- package/build/lib/tools/network-commands.d.ts.map +1 -1
- package/build/lib/tools/network-commands.js +1 -5
- package/build/lib/tools/network-commands.js.map +1 -1
- package/build/lib/tools/process-commands.d.ts.map +1 -1
- package/build/lib/tools/process-commands.js +11 -12
- package/build/lib/tools/process-commands.js.map +1 -1
- package/build/lib/tools/system-calls.d.ts +12 -5
- package/build/lib/tools/system-calls.d.ts.map +1 -1
- package/build/lib/tools/system-calls.js +56 -50
- package/build/lib/tools/system-calls.js.map +1 -1
- package/build/lib/tools/types.d.ts +1 -1
- package/build/lib/tools/types.d.ts.map +1 -1
- package/build/lib/utils/helpers/constants.d.ts +6 -0
- package/build/lib/utils/helpers/constants.d.ts.map +1 -0
- package/build/lib/utils/helpers/constants.js +9 -0
- package/build/lib/utils/helpers/constants.js.map +1 -0
- package/build/lib/utils/helpers/index.d.ts +6 -0
- package/build/lib/utils/helpers/index.d.ts.map +1 -0
- package/build/lib/utils/helpers/index.js +21 -0
- package/build/lib/utils/helpers/index.js.map +1 -0
- package/build/lib/utils/helpers/install.d.ts +16 -0
- package/build/lib/utils/helpers/install.d.ts.map +1 -0
- package/build/lib/utils/helpers/install.js +39 -0
- package/build/lib/utils/helpers/install.js.map +1 -0
- package/build/lib/utils/helpers/manifest.d.ts +10 -0
- package/build/lib/utils/helpers/manifest.d.ts.map +1 -0
- package/build/lib/utils/helpers/manifest.js +124 -0
- package/build/lib/utils/helpers/manifest.js.map +1 -0
- package/build/lib/utils/helpers/resource.d.ts +11 -0
- package/build/lib/utils/helpers/resource.d.ts.map +1 -0
- package/build/lib/utils/helpers/resource.js +59 -0
- package/build/lib/utils/helpers/resource.js.map +1 -0
- package/build/lib/utils/helpers/sdk.d.ts +20 -0
- package/build/lib/utils/helpers/sdk.d.ts.map +1 -0
- package/build/lib/utils/helpers/sdk.js +80 -0
- package/build/lib/utils/helpers/sdk.js.map +1 -0
- package/build/lib/utils/index.d.ts +40 -0
- package/build/lib/utils/index.d.ts.map +1 -0
- package/build/lib/utils/index.js +58 -0
- package/build/lib/utils/index.js.map +1 -0
- package/build/lib/utils/lodash/core.d.ts +9 -0
- package/build/lib/utils/lodash/core.d.ts.map +1 -0
- package/build/lib/utils/lodash/core.js +60 -0
- package/build/lib/utils/lodash/core.js.map +1 -0
- package/build/lib/utils/lodash/index.d.ts +2 -0
- package/build/lib/utils/lodash/index.d.ts.map +1 -0
- package/build/lib/utils/lodash/index.js +18 -0
- package/build/lib/utils/lodash/index.js.map +1 -0
- package/lib/adb.ts +14 -6
- package/lib/index.ts +1 -1
- package/lib/logcat.ts +6 -11
- package/lib/tools/aab-utils.ts +1 -1
- package/lib/tools/android-manifest.ts +4 -4
- package/lib/tools/apk-signing.ts +11 -7
- package/lib/tools/apk-utils.ts +41 -42
- package/lib/tools/apks-utils.ts +4 -5
- package/lib/tools/app-commands.ts +40 -34
- package/lib/tools/device-settings.ts +17 -15
- package/lib/tools/emulator-commands.ts +22 -23
- package/lib/tools/fs-commands.ts +5 -4
- package/lib/tools/general-commands.ts +32 -27
- package/lib/tools/keyboard-commands.ts +6 -4
- package/lib/tools/lockmgmt.ts +17 -13
- package/lib/tools/logcat-commands.ts +5 -6
- package/lib/tools/network-commands.ts +1 -2
- package/lib/tools/process-commands.ts +11 -9
- package/lib/tools/system-calls.ts +54 -46
- package/lib/tools/types.ts +1 -1
- package/lib/utils/helpers/constants.ts +6 -0
- package/lib/utils/helpers/index.ts +10 -0
- package/lib/utils/helpers/install.ts +50 -0
- package/lib/utils/helpers/manifest.ts +127 -0
- package/lib/utils/helpers/resource.ts +63 -0
- package/lib/utils/helpers/sdk.ts +81 -0
- package/lib/utils/index.ts +25 -0
- package/lib/utils/lodash/core.ts +65 -0
- package/lib/utils/lodash/index.ts +1 -0
- package/package.json +3 -5
- package/build/lib/helpers.d.ts +0 -47
- package/build/lib/helpers.d.ts.map +0 -1
- package/build/lib/helpers.js +0 -283
- package/build/lib/helpers.js.map +0 -1
- package/lib/helpers.ts +0 -323
|
@@ -56,7 +56,9 @@ export async function isSoftKeyboardPresent(this: ADB): Promise<KeyboardState> {
|
|
|
56
56
|
canCloseKeyboard: !!(inputViewShownMatch && inputViewShownMatch[1] === 'true'),
|
|
57
57
|
};
|
|
58
58
|
} catch (e) {
|
|
59
|
-
throw new Error(`Error finding softkeyboard. Original error: ${(e as Error).message}
|
|
59
|
+
throw new Error(`Error finding softkeyboard. Original error: ${(e as Error).message}`, {
|
|
60
|
+
cause: e,
|
|
61
|
+
});
|
|
60
62
|
}
|
|
61
63
|
}
|
|
62
64
|
|
|
@@ -81,7 +83,7 @@ export async function availableIMEs(this: ADB): Promise<string[]> {
|
|
|
81
83
|
return getIMEListFromOutput(await this.shell(['ime', 'list', '-a']));
|
|
82
84
|
} catch (e) {
|
|
83
85
|
const err = e as Error;
|
|
84
|
-
throw new Error(`Error getting available IME's. Original error: ${err.message}
|
|
86
|
+
throw new Error(`Error getting available IME's. Original error: ${err.message}`, {cause: e});
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
89
|
|
|
@@ -95,7 +97,7 @@ export async function enabledIMEs(this: ADB): Promise<string[]> {
|
|
|
95
97
|
return getIMEListFromOutput(await this.shell(['ime', 'list']));
|
|
96
98
|
} catch (e) {
|
|
97
99
|
const err = e as Error;
|
|
98
|
-
throw new Error(`Error getting enabled IME's. Original error: ${err.message}
|
|
100
|
+
throw new Error(`Error getting enabled IME's. Original error: ${err.message}`, {cause: e});
|
|
99
101
|
}
|
|
100
102
|
}
|
|
101
103
|
|
|
@@ -140,7 +142,7 @@ export async function defaultIME(this: ADB): Promise<string | null> {
|
|
|
140
142
|
return engine.trim();
|
|
141
143
|
} catch (e) {
|
|
142
144
|
const err = e as Error;
|
|
143
|
-
throw new Error(`Error getting default IME. Original error: ${err.message}
|
|
145
|
+
throw new Error(`Error getting default IME. Original error: ${err.message}`, {cause: e});
|
|
144
146
|
}
|
|
145
147
|
}
|
|
146
148
|
|
package/lib/tools/lockmgmt.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {log} from '../logger';
|
|
2
|
-
import _ from 'lodash';
|
|
3
2
|
import {sleep, waitForCondition} from 'asyncbox';
|
|
4
3
|
import type {ADB} from '../adb';
|
|
4
|
+
import {util} from '@appium/support';
|
|
5
5
|
|
|
6
6
|
const CREDENTIAL_CANNOT_BE_NULL_OR_EMPTY_ERROR = `Credential can't be null or empty`;
|
|
7
7
|
const CREDENTIAL_DID_NOT_MATCH_ERROR = `didn't match`;
|
|
@@ -17,13 +17,13 @@ const HIDE_KEYBOARD_WAIT_TIME = 100;
|
|
|
17
17
|
* @return True if the management is supported. The result is cached per ADB instance
|
|
18
18
|
*/
|
|
19
19
|
export async function isLockManagementSupported(this: ADB): Promise<boolean> {
|
|
20
|
-
if (
|
|
20
|
+
if (typeof this._isLockManagementSupported !== 'boolean') {
|
|
21
21
|
const passFlag = '__PASS__';
|
|
22
22
|
let output = '';
|
|
23
23
|
try {
|
|
24
24
|
output = await this.shell([`locksettings help && echo ${passFlag}`]);
|
|
25
25
|
} catch {}
|
|
26
|
-
this._isLockManagementSupported =
|
|
26
|
+
this._isLockManagementSupported = output.includes(passFlag);
|
|
27
27
|
log.debug(
|
|
28
28
|
`Extended lock settings management is ` +
|
|
29
29
|
`${this._isLockManagementSupported ? '' : 'not '}supported`,
|
|
@@ -52,12 +52,12 @@ export async function verifyLockCredential(
|
|
|
52
52
|
const {stdout, stderr} = await this.shell(buildCommand('verify', credential), {
|
|
53
53
|
outputFormat: this.EXEC_OUTPUT_FORMAT.FULL,
|
|
54
54
|
});
|
|
55
|
-
if (
|
|
55
|
+
if (stdout?.includes('verified successfully')) {
|
|
56
56
|
return true;
|
|
57
57
|
}
|
|
58
58
|
if (
|
|
59
59
|
[`didn't match`, CREDENTIAL_CANNOT_BE_NULL_OR_EMPTY_ERROR].some((x) =>
|
|
60
|
-
|
|
60
|
+
(stderr || stdout)?.includes(x),
|
|
61
61
|
)
|
|
62
62
|
) {
|
|
63
63
|
return false;
|
|
@@ -71,6 +71,7 @@ export async function verifyLockCredential(
|
|
|
71
71
|
(e as Error & {stderr?: string; stdout?: string}).stdout ||
|
|
72
72
|
(e as Error).message
|
|
73
73
|
}`,
|
|
74
|
+
{cause: e},
|
|
74
75
|
);
|
|
75
76
|
}
|
|
76
77
|
}
|
|
@@ -97,7 +98,7 @@ export async function clearLockCredential(
|
|
|
97
98
|
});
|
|
98
99
|
if (
|
|
99
100
|
!['user has no password', 'Lock credential cleared'].some((x) =>
|
|
100
|
-
|
|
101
|
+
(stderr || stdout)?.includes(x),
|
|
101
102
|
)
|
|
102
103
|
) {
|
|
103
104
|
throw new Error(stderr || stdout);
|
|
@@ -110,6 +111,7 @@ export async function clearLockCredential(
|
|
|
110
111
|
(e as Error & {stderr?: string; stdout?: string}).stdout ||
|
|
111
112
|
(e as Error).message
|
|
112
113
|
}`,
|
|
114
|
+
{cause: e},
|
|
113
115
|
);
|
|
114
116
|
}
|
|
115
117
|
}
|
|
@@ -129,7 +131,7 @@ export async function isLockEnabled(this: ADB): Promise<boolean> {
|
|
|
129
131
|
if (
|
|
130
132
|
/\bfalse\b/.test(stdout) ||
|
|
131
133
|
[CREDENTIAL_DID_NOT_MATCH_ERROR, CREDENTIAL_CANNOT_BE_NULL_OR_EMPTY_ERROR].some((x) =>
|
|
132
|
-
|
|
134
|
+
(stderr || stdout)?.includes(x),
|
|
133
135
|
)
|
|
134
136
|
) {
|
|
135
137
|
return true;
|
|
@@ -141,6 +143,7 @@ export async function isLockEnabled(this: ADB): Promise<boolean> {
|
|
|
141
143
|
} catch (e) {
|
|
142
144
|
throw new Error(
|
|
143
145
|
`Cannot check if device lock is enabled. Original error: ${(e as Error).message}`,
|
|
146
|
+
{cause: e},
|
|
144
147
|
);
|
|
145
148
|
}
|
|
146
149
|
}
|
|
@@ -174,7 +177,7 @@ export async function setLockCredential(
|
|
|
174
177
|
`Only the following credential types are supported: ${SUPPORTED_LOCK_CREDENTIAL_TYPES}`,
|
|
175
178
|
);
|
|
176
179
|
}
|
|
177
|
-
if (
|
|
180
|
+
if (util.isEmpty(credential) && !Number.isInteger(credential)) {
|
|
178
181
|
throw new Error('Device lock credential cannot be empty');
|
|
179
182
|
}
|
|
180
183
|
const cmd = buildCommand(`set-${credentialType}`, oldCredential, credential);
|
|
@@ -182,7 +185,7 @@ export async function setLockCredential(
|
|
|
182
185
|
const {stdout, stderr} = await this.shell(cmd, {
|
|
183
186
|
outputFormat: this.EXEC_OUTPUT_FORMAT.FULL,
|
|
184
187
|
});
|
|
185
|
-
if (!
|
|
188
|
+
if (!stdout?.includes('set to')) {
|
|
186
189
|
throw new Error(stderr || stdout);
|
|
187
190
|
}
|
|
188
191
|
} catch (e) {
|
|
@@ -193,6 +196,7 @@ export async function setLockCredential(
|
|
|
193
196
|
(e as Error & {stderr?: string; stdout?: string}).stdout ||
|
|
194
197
|
(e as Error).message
|
|
195
198
|
}`,
|
|
199
|
+
{cause: e},
|
|
196
200
|
);
|
|
197
201
|
}
|
|
198
202
|
}
|
|
@@ -295,9 +299,9 @@ export async function lock(this: ADB): Promise<void> {
|
|
|
295
299
|
*/
|
|
296
300
|
export function isShowingLockscreen(dumpsys: string): boolean {
|
|
297
301
|
return (
|
|
298
|
-
|
|
302
|
+
['mShowingLockscreen=true', 'mDreamingLockscreen=true'].some((x) => dumpsys.includes(x)) ||
|
|
299
303
|
// `mIsShowing` and `mInputRestricted` are `true` in lock condition. `false` is unlock condition.
|
|
300
|
-
|
|
304
|
+
[/KeyguardStateMonitor[\n\s]+mIsShowing=true/, /\s+mInputRestricted=true/].every((x) =>
|
|
301
305
|
x.test(dumpsys),
|
|
302
306
|
)
|
|
303
307
|
);
|
|
@@ -327,10 +331,10 @@ function buildCommand(
|
|
|
327
331
|
...args: string[]
|
|
328
332
|
): string[] {
|
|
329
333
|
const cmd = ['locksettings', verb];
|
|
330
|
-
if (oldCredential && !
|
|
334
|
+
if (oldCredential && !util.isEmpty(oldCredential)) {
|
|
331
335
|
cmd.push('--old', oldCredential);
|
|
332
336
|
}
|
|
333
|
-
if (
|
|
337
|
+
if (args.length > 0) {
|
|
334
338
|
cmd.push(...args);
|
|
335
339
|
}
|
|
336
340
|
return cmd;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import _ from 'lodash';
|
|
2
1
|
import {Logcat} from '../logcat';
|
|
3
2
|
import type {ADB} from '../adb';
|
|
4
3
|
import type {LogcatOpts, LogEntry, LogcatListener} from './types';
|
|
@@ -10,7 +9,7 @@ import type {LogcatOpts, LogEntry, LogcatListener} from './types';
|
|
|
10
9
|
* @throws {Error} If restart fails.
|
|
11
10
|
*/
|
|
12
11
|
export async function startLogcat(this: ADB, opts: LogcatOpts = {}): Promise<void> {
|
|
13
|
-
if (
|
|
12
|
+
if (this.logcat) {
|
|
14
13
|
throw new Error("Trying to start logcat capture but it's already started!");
|
|
15
14
|
}
|
|
16
15
|
|
|
@@ -30,7 +29,7 @@ export async function startLogcat(this: ADB, opts: LogcatOpts = {}): Promise<voi
|
|
|
30
29
|
*/
|
|
31
30
|
export async function stopLogcat(this: ADB): Promise<void> {
|
|
32
31
|
const logcat = this.logcat;
|
|
33
|
-
if (!logcat
|
|
32
|
+
if (!logcat) {
|
|
34
33
|
return;
|
|
35
34
|
}
|
|
36
35
|
try {
|
|
@@ -49,7 +48,7 @@ export async function stopLogcat(this: ADB): Promise<void> {
|
|
|
49
48
|
*/
|
|
50
49
|
export function getLogcatLogs(this: ADB): LogEntry[] {
|
|
51
50
|
const logcat = this.logcat;
|
|
52
|
-
if (!logcat
|
|
51
|
+
if (!logcat) {
|
|
53
52
|
throw new Error(`Can't get logcat logs since logcat hasn't started`);
|
|
54
53
|
}
|
|
55
54
|
return logcat.getLogs();
|
|
@@ -63,7 +62,7 @@ export function getLogcatLogs(this: ADB): LogEntry[] {
|
|
|
63
62
|
*/
|
|
64
63
|
export function setLogcatListener(this: ADB, listener: LogcatListener): void {
|
|
65
64
|
const logcat = this.logcat;
|
|
66
|
-
if (!logcat
|
|
65
|
+
if (!logcat) {
|
|
67
66
|
throw new Error("Logcat process hasn't been started");
|
|
68
67
|
}
|
|
69
68
|
logcat.on('output', listener);
|
|
@@ -79,7 +78,7 @@ export function setLogcatListener(this: ADB, listener: LogcatListener): void {
|
|
|
79
78
|
*/
|
|
80
79
|
export function removeLogcatListener(this: ADB, listener: LogcatListener): void {
|
|
81
80
|
const logcat = this.logcat;
|
|
82
|
-
if (!logcat
|
|
81
|
+
if (!logcat) {
|
|
83
82
|
throw new Error("Logcat process hasn't been started");
|
|
84
83
|
}
|
|
85
84
|
logcat.removeListener('output', listener);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {EOL} from 'node:os';
|
|
2
|
-
import _ from 'lodash';
|
|
3
2
|
import {log} from '../logger';
|
|
4
3
|
import type {ADB} from '../adb';
|
|
5
4
|
import type {PortFamily, PortInfo} from './types';
|
|
@@ -126,7 +125,7 @@ export async function listPorts(this: ADB, family: PortFamily = '4'): Promise<Po
|
|
|
126
125
|
const sourceProcName = `/proc/net/tcp${family === '6' ? '6' : ''}`;
|
|
127
126
|
const output = await this.shell(['cat', sourceProcName]);
|
|
128
127
|
const lines = output.split('\n');
|
|
129
|
-
if (
|
|
128
|
+
if (!lines.length) {
|
|
130
129
|
log.debug(output);
|
|
131
130
|
throw new Error(`Cannot parse the payload of ${sourceProcName}`);
|
|
132
131
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import _ from 'lodash';
|
|
2
1
|
import {log} from '../logger';
|
|
2
|
+
import {util} from '@appium/support';
|
|
3
3
|
import type {ExecError} from 'teen_process';
|
|
4
4
|
import type {ADB} from '../adb';
|
|
5
5
|
|
|
@@ -19,7 +19,7 @@ const PS_TITLE_PATTERN: RegExp = new RegExp(
|
|
|
19
19
|
* @returns the output of `ps` command where all processes are included
|
|
20
20
|
*/
|
|
21
21
|
export async function listProcessStatus(this: ADB): Promise<string> {
|
|
22
|
-
if (
|
|
22
|
+
if (typeof this._doesPsSupportAOption !== 'boolean') {
|
|
23
23
|
try {
|
|
24
24
|
this._doesPsSupportAOption = /^-A\b/m.test(await this.shell(['ps', '--help']));
|
|
25
25
|
} catch (e: unknown) {
|
|
@@ -108,7 +108,7 @@ export async function getProcessIdsByName(this: ADB, name: string): Promise<numb
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
return
|
|
111
|
+
return util.uniq(pids);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
/**
|
|
@@ -126,14 +126,16 @@ export async function killProcessesByName(
|
|
|
126
126
|
try {
|
|
127
127
|
log.debug(`Attempting to kill all ${name} processes`);
|
|
128
128
|
const pids: number[] = await this.getProcessIdsByName(name);
|
|
129
|
-
if (
|
|
129
|
+
if (pids.length === 0) {
|
|
130
130
|
log.info(`No '${name}' process has been found`);
|
|
131
131
|
} else {
|
|
132
132
|
await Promise.all(pids.map((p: number) => this.killProcessByPID(p, signal)));
|
|
133
133
|
}
|
|
134
134
|
} catch (e: unknown) {
|
|
135
135
|
const err: Error = e as Error;
|
|
136
|
-
throw new Error(`Unable to kill ${name} processes. Original error: ${err.message}
|
|
136
|
+
throw new Error(`Unable to kill ${name} processes. Original error: ${err.message}`, {
|
|
137
|
+
cause: e,
|
|
138
|
+
});
|
|
137
139
|
}
|
|
138
140
|
}
|
|
139
141
|
|
|
@@ -158,10 +160,10 @@ export async function killProcessByPID(
|
|
|
158
160
|
await this.shell(['kill', `-${signal}`, `${pid}`]);
|
|
159
161
|
} catch (e: unknown) {
|
|
160
162
|
const err: ExecError = e as ExecError;
|
|
161
|
-
if (
|
|
163
|
+
if ((err.stderr ?? '').includes(noProcessFlag)) {
|
|
162
164
|
return;
|
|
163
165
|
}
|
|
164
|
-
if (!
|
|
166
|
+
if (!(err.stderr ?? '').includes('Operation not permitted')) {
|
|
165
167
|
throw err;
|
|
166
168
|
}
|
|
167
169
|
log.info(`Cannot kill PID ${pid} due to insufficient permissions. Retrying as root`);
|
|
@@ -171,7 +173,7 @@ export async function killProcessByPID(
|
|
|
171
173
|
});
|
|
172
174
|
} catch (e1: unknown) {
|
|
173
175
|
const err1: ExecError = e1 as ExecError;
|
|
174
|
-
if (
|
|
176
|
+
if ((err1.stderr ?? '').includes(noProcessFlag)) {
|
|
175
177
|
return;
|
|
176
178
|
}
|
|
177
179
|
throw err1;
|
|
@@ -188,5 +190,5 @@ export async function killProcessByPID(
|
|
|
188
190
|
* @throws {Error} If the given process name is not a valid class name.
|
|
189
191
|
*/
|
|
190
192
|
export async function processExists(this: ADB, processName: string): Promise<boolean> {
|
|
191
|
-
return
|
|
193
|
+
return (await this.getProcessIdsByName(processName)).length > 0;
|
|
192
194
|
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import {log} from '../logger';
|
|
3
3
|
import {system, fs, util, tempDir, timing} from '@appium/support';
|
|
4
|
-
import {DEFAULT_ADB_EXEC_TIMEOUT, getSdkRootFromEnv} from '../helpers';
|
|
5
4
|
import {exec, SubProcess} from 'teen_process';
|
|
6
5
|
import type {ExecError, TeenProcessExecResult} from 'teen_process';
|
|
7
6
|
import {asyncmap, retry, retryInterval, sleep, waitForCondition} from 'asyncbox';
|
|
8
|
-
import _ from 'lodash';
|
|
9
7
|
import * as semver from 'semver';
|
|
10
8
|
import type {ADB} from '../adb';
|
|
9
|
+
import {DEFAULT_ADB_EXEC_TIMEOUT, cloneDeep, getSdkRootFromEnv, memoize, zip} from '../utils';
|
|
11
10
|
import type {
|
|
12
11
|
ConnectedDevicesOptions,
|
|
13
12
|
Device,
|
|
@@ -81,7 +80,7 @@ function _getBinaryNameForOS(binaryName: string): string {
|
|
|
81
80
|
*/
|
|
82
81
|
function getSdkBinaryLocationCandidates(sdkRoot: string, fullBinaryName: string): string[] {
|
|
83
82
|
return SDK_BINARY_ROOTS.map((x) =>
|
|
84
|
-
path.resolve(sdkRoot, ...(
|
|
83
|
+
path.resolve(sdkRoot, ...(Array.isArray(x) ? x : [x]), fullBinaryName),
|
|
85
84
|
);
|
|
86
85
|
}
|
|
87
86
|
|
|
@@ -100,7 +99,7 @@ async function getOpenSslForOs(): Promise<string> {
|
|
|
100
99
|
}
|
|
101
100
|
}
|
|
102
101
|
|
|
103
|
-
export const getBinaryNameForOS =
|
|
102
|
+
export const getBinaryNameForOS = memoize(_getBinaryNameForOS);
|
|
104
103
|
|
|
105
104
|
/**
|
|
106
105
|
* Retrieve full path to the given binary and caches it into `binaries`
|
|
@@ -124,19 +123,16 @@ export async function getBinaryFromSdkRoot(this: ADB, binaryName: string): Promi
|
|
|
124
123
|
let buildToolsDirs = await getBuildToolsDirs(this.sdkRoot);
|
|
125
124
|
if (this.buildToolsVersion) {
|
|
126
125
|
buildToolsDirs = buildToolsDirs.filter((x) => path.basename(x) === this.buildToolsVersion);
|
|
127
|
-
if (
|
|
126
|
+
if (util.isEmpty(buildToolsDirs)) {
|
|
128
127
|
log.info(`Found no build tools whose version matches to '${this.buildToolsVersion}'`);
|
|
129
128
|
} else {
|
|
130
129
|
log.info(`Using build tools at '${buildToolsDirs}'`);
|
|
131
130
|
}
|
|
132
131
|
}
|
|
133
132
|
binaryLocs.push(
|
|
134
|
-
...
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
path.resolve(dir, 'lib', fullBinaryName),
|
|
138
|
-
]),
|
|
139
|
-
),
|
|
133
|
+
...buildToolsDirs
|
|
134
|
+
.map((dir) => [path.resolve(dir, fullBinaryName), path.resolve(dir, 'lib', fullBinaryName)])
|
|
135
|
+
.flat(),
|
|
140
136
|
);
|
|
141
137
|
|
|
142
138
|
let binaryLoc: string | null = null;
|
|
@@ -146,7 +142,7 @@ export async function getBinaryFromSdkRoot(this: ADB, binaryName: string): Promi
|
|
|
146
142
|
break;
|
|
147
143
|
}
|
|
148
144
|
}
|
|
149
|
-
if (
|
|
145
|
+
if (binaryLoc === null) {
|
|
150
146
|
throw new Error(
|
|
151
147
|
`Could not find '${fullBinaryName}' in ${JSON.stringify(binaryLocs)}. ` +
|
|
152
148
|
`Do you have Android Build Tools ${this.buildToolsVersion ? `v ${this.buildToolsVersion} ` : ''}` +
|
|
@@ -235,7 +231,9 @@ export async function getConnectedDevices(
|
|
|
235
231
|
({stdout} = await exec(this.executable.path, args));
|
|
236
232
|
} catch (e: unknown) {
|
|
237
233
|
const error = e as Error;
|
|
238
|
-
throw new Error(`Error while getting connected devices. Original error: ${error.message}
|
|
234
|
+
throw new Error(`Error while getting connected devices. Original error: ${error.message}`, {
|
|
235
|
+
cause: e,
|
|
236
|
+
});
|
|
239
237
|
}
|
|
240
238
|
const listHeader = 'List of devices';
|
|
241
239
|
// expecting adb devices to return output as
|
|
@@ -253,7 +251,7 @@ export async function getConnectedDevices(
|
|
|
253
251
|
}
|
|
254
252
|
const devices = stdout
|
|
255
253
|
.split('\n')
|
|
256
|
-
.map(
|
|
254
|
+
.map((x) => x.trim())
|
|
257
255
|
.filter((line) => line && !excludedLines.some((x) => line.includes(x)))
|
|
258
256
|
.map((line) => {
|
|
259
257
|
// state is "device", afaic
|
|
@@ -271,7 +269,7 @@ export async function getConnectedDevices(
|
|
|
271
269
|
}
|
|
272
270
|
return device;
|
|
273
271
|
});
|
|
274
|
-
if (
|
|
272
|
+
if (util.isEmpty(devices)) {
|
|
275
273
|
log.debug('No connected devices have been detected');
|
|
276
274
|
} else {
|
|
277
275
|
log.debug(`Connected devices: ${JSON.stringify(devices)}`);
|
|
@@ -322,7 +320,9 @@ export async function getDevicesWithRetry(this: ADB, timeoutMs: number = 20000):
|
|
|
322
320
|
} catch (e: unknown) {
|
|
323
321
|
const error = e as Error;
|
|
324
322
|
if (/Condition unmet/.test(error.message)) {
|
|
325
|
-
throw new Error(`Could not find a connected Android device in ${timeoutMs}ms
|
|
323
|
+
throw new Error(`Could not find a connected Android device in ${timeoutMs}ms`, {
|
|
324
|
+
cause: e,
|
|
325
|
+
});
|
|
326
326
|
} else {
|
|
327
327
|
throw e;
|
|
328
328
|
}
|
|
@@ -346,7 +346,9 @@ export async function reconnect(this: ADB, target: string | null = 'offline'): P
|
|
|
346
346
|
await this.adbExec(args);
|
|
347
347
|
} catch (e: unknown) {
|
|
348
348
|
const error = e as ExecError;
|
|
349
|
-
throw new Error(`Cannot reconnect adb. Original error: ${error.stderr || error.message}
|
|
349
|
+
throw new Error(`Cannot reconnect adb. Original error: ${error.stderr || error.message}`, {
|
|
350
|
+
cause: e,
|
|
351
|
+
});
|
|
350
352
|
}
|
|
351
353
|
}
|
|
352
354
|
|
|
@@ -384,7 +386,7 @@ export async function killServer(this: ADB): Promise<void> {
|
|
|
384
386
|
*
|
|
385
387
|
* @returns True if token was reset successfully, false otherwise
|
|
386
388
|
*/
|
|
387
|
-
export const resetTelnetAuthToken =
|
|
389
|
+
export const resetTelnetAuthToken = memoize(
|
|
388
390
|
async function resetTelnetAuthToken(): Promise<boolean> {
|
|
389
391
|
// The methods is used to remove telnet auth token
|
|
390
392
|
//
|
|
@@ -451,14 +453,14 @@ export async function adbExec<
|
|
|
451
453
|
throw new Error('You need to pass in a command to adbExec()');
|
|
452
454
|
}
|
|
453
455
|
|
|
454
|
-
const optsCopy =
|
|
456
|
+
const optsCopy = cloneDeep(opts ?? {}) as TExecOpts;
|
|
455
457
|
// setting default timeout for each command to prevent infinite wait.
|
|
456
458
|
optsCopy.timeout = optsCopy.timeout || this.adbExecTimeout || DEFAULT_ADB_EXEC_TIMEOUT;
|
|
457
459
|
optsCopy.timeoutCapName = optsCopy.timeoutCapName || 'adbExecTimeout'; // For error message
|
|
458
460
|
|
|
459
461
|
const {outputFormat = this.EXEC_OUTPUT_FORMAT.STDOUT} = optsCopy;
|
|
460
462
|
|
|
461
|
-
cmd =
|
|
463
|
+
cmd = Array.isArray(cmd) ? cmd : [cmd];
|
|
462
464
|
let adbRetried = false;
|
|
463
465
|
const execFunc = async (): Promise<string | ExecResult> => {
|
|
464
466
|
try {
|
|
@@ -495,7 +497,7 @@ export async function adbExec<
|
|
|
495
497
|
return error.stdout.replace(LINKER_WARNING_REGEXP, '').trim();
|
|
496
498
|
}
|
|
497
499
|
|
|
498
|
-
if (
|
|
500
|
+
if (error.code === null) {
|
|
499
501
|
error.message =
|
|
500
502
|
`Error executing adbExec. Original error: '${error.message}'. ` +
|
|
501
503
|
`Try to increase the ${optsCopy.timeout}ms adb execution timeout ` +
|
|
@@ -544,7 +546,7 @@ export async function shell<TShellExecOpts extends ShellExecOptions = ShellExecO
|
|
|
544
546
|
): Promise<TShellExecOpts extends TFullOutputOption ? ExecResult : string> {
|
|
545
547
|
const {privileged} = opts ?? ({} as TShellExecOpts);
|
|
546
548
|
|
|
547
|
-
const cmdArr =
|
|
549
|
+
const cmdArr = Array.isArray(cmd) ? cmd : [cmd];
|
|
548
550
|
const fullCmd: string[] = ['shell'];
|
|
549
551
|
if (privileged) {
|
|
550
552
|
log.info(`'adb shell ${util.quote(cmdArr)}' requires root access`);
|
|
@@ -592,7 +594,7 @@ export function getAdbServerPort(this: ADB): number {
|
|
|
592
594
|
*/
|
|
593
595
|
export async function getEmulatorPort(this: ADB): Promise<number> {
|
|
594
596
|
log.debug('Getting running emulator port');
|
|
595
|
-
if (
|
|
597
|
+
if (this.emulatorPort != null) {
|
|
596
598
|
return this.emulatorPort;
|
|
597
599
|
}
|
|
598
600
|
try {
|
|
@@ -605,7 +607,7 @@ export async function getEmulatorPort(this: ADB): Promise<number> {
|
|
|
605
607
|
}
|
|
606
608
|
} catch (e: unknown) {
|
|
607
609
|
const error = e as Error;
|
|
608
|
-
throw new Error(`No devices connected. Original error: ${error.message}
|
|
610
|
+
throw new Error(`No devices connected. Original error: ${error.message}`, {cause: e});
|
|
609
611
|
}
|
|
610
612
|
}
|
|
611
613
|
|
|
@@ -647,7 +649,7 @@ export async function getConnectedEmulators(
|
|
|
647
649
|
return emulators;
|
|
648
650
|
} catch (e: unknown) {
|
|
649
651
|
const error = e as Error;
|
|
650
|
-
throw new Error(`Error getting emulators. Original error: ${error.message}
|
|
652
|
+
throw new Error(`Error getting emulators. Original error: ${error.message}`, {cause: e});
|
|
651
653
|
}
|
|
652
654
|
}
|
|
653
655
|
|
|
@@ -684,7 +686,7 @@ export function setDeviceId(this: ADB, deviceId: string): void {
|
|
|
684
686
|
export function setDevice(this: ADB, deviceObj: Device): void {
|
|
685
687
|
const deviceId = deviceObj.udid;
|
|
686
688
|
const emPort = this.getPortFromEmulatorString(deviceId);
|
|
687
|
-
if (
|
|
689
|
+
if (typeof emPort === 'number' && !Number.isNaN(emPort)) {
|
|
688
690
|
this.setEmulatorPort(emPort);
|
|
689
691
|
}
|
|
690
692
|
this.setDeviceId(deviceId);
|
|
@@ -705,7 +707,7 @@ export async function getRunningAVD(this: ADB, avdName: string): Promise<Device
|
|
|
705
707
|
try {
|
|
706
708
|
const emulators = await this.getConnectedEmulators();
|
|
707
709
|
for (const emulator of emulators) {
|
|
708
|
-
if (
|
|
710
|
+
if (typeof emulator.port === 'number' && !Number.isNaN(emulator.port)) {
|
|
709
711
|
this.setEmulatorPort(emulator.port);
|
|
710
712
|
}
|
|
711
713
|
const runningAVDName = await this.execEmuConsoleCommand(['avd', 'name'], {
|
|
@@ -713,7 +715,7 @@ export async function getRunningAVD(this: ADB, avdName: string): Promise<Device
|
|
|
713
715
|
execTimeout: 5000,
|
|
714
716
|
connTimeout: 1000,
|
|
715
717
|
});
|
|
716
|
-
if (
|
|
718
|
+
if (avdName.toLowerCase() === runningAVDName.trim().toLowerCase()) {
|
|
717
719
|
log.debug(`Found emulator '${avdName}' on port ${emulator.port}`);
|
|
718
720
|
this.setDeviceId(emulator.udid);
|
|
719
721
|
return emulator;
|
|
@@ -723,7 +725,7 @@ export async function getRunningAVD(this: ADB, avdName: string): Promise<Device
|
|
|
723
725
|
return null;
|
|
724
726
|
} catch (e: unknown) {
|
|
725
727
|
const error = e as Error;
|
|
726
|
-
throw new Error(`Error getting AVD. Original error: ${error.message}
|
|
728
|
+
throw new Error(`Error getting AVD. Original error: ${error.message}`, {cause: e});
|
|
727
729
|
}
|
|
728
730
|
}
|
|
729
731
|
|
|
@@ -758,7 +760,9 @@ export async function getRunningAVDWithRetry(
|
|
|
758
760
|
)) as Device | null;
|
|
759
761
|
} catch (e: unknown) {
|
|
760
762
|
const error = e as Error;
|
|
761
|
-
throw new Error(`Error getting AVD with retry. Original error: ${error.message}
|
|
763
|
+
throw new Error(`Error getting AVD with retry. Original error: ${error.message}`, {
|
|
764
|
+
cause: e,
|
|
765
|
+
});
|
|
762
766
|
}
|
|
763
767
|
}
|
|
764
768
|
|
|
@@ -781,7 +785,7 @@ export async function killAllEmulators(this: ADB): Promise<void> {
|
|
|
781
785
|
await exec(cmd, args);
|
|
782
786
|
} catch (e: unknown) {
|
|
783
787
|
const error = e as Error;
|
|
784
|
-
throw new Error(`Error killing emulators. Original error: ${error.message}
|
|
788
|
+
throw new Error(`Error killing emulators. Original error: ${error.message}`, {cause: e});
|
|
785
789
|
}
|
|
786
790
|
}
|
|
787
791
|
|
|
@@ -906,12 +910,12 @@ export async function launchAVD(
|
|
|
906
910
|
log.info('The -delay-adb emulator startup detection feature has been explicitly disabled');
|
|
907
911
|
}
|
|
908
912
|
|
|
909
|
-
if (!
|
|
910
|
-
launchArgs.push(...(
|
|
913
|
+
if (!util.isEmpty(args)) {
|
|
914
|
+
launchArgs.push(...(Array.isArray(args) ? args : util.shellParse(`${args}`)));
|
|
911
915
|
}
|
|
912
916
|
|
|
913
917
|
log.debug(`Running '${emulatorBinaryPath}' with args: ${util.quote(launchArgs)}`);
|
|
914
|
-
if (!
|
|
918
|
+
if (!util.isEmpty(env)) {
|
|
915
919
|
log.debug(`Customized emulator environment: ${JSON.stringify(env)}`);
|
|
916
920
|
}
|
|
917
921
|
const proc = new SubProcess(emulatorBinaryPath, launchArgs, {
|
|
@@ -939,6 +943,7 @@ export async function launchAVD(
|
|
|
939
943
|
const error = e as ExecError;
|
|
940
944
|
throw new Error(
|
|
941
945
|
`'${processedAvdName}' Emulator has failed to boot: ${error.stderr || error.message}`,
|
|
946
|
+
{cause: e},
|
|
942
947
|
);
|
|
943
948
|
}
|
|
944
949
|
}
|
|
@@ -952,13 +957,13 @@ export async function launchAVD(
|
|
|
952
957
|
* @returns Version information object
|
|
953
958
|
* @throws {Error} If error occurs while getting adb version
|
|
954
959
|
*/
|
|
955
|
-
export const getVersion =
|
|
960
|
+
export const getVersion = memoize(async function getVersion(this: ADB): Promise<Version> {
|
|
956
961
|
let stdout: string;
|
|
957
962
|
try {
|
|
958
963
|
stdout = await this.adbExec('version');
|
|
959
964
|
} catch (e: unknown) {
|
|
960
965
|
const error = e as ExecError;
|
|
961
|
-
throw new Error(`Error getting adb version: ${error.stderr || error.message}
|
|
966
|
+
throw new Error(`Error getting adb version: ${error.stderr || error.message}`, {cause: e});
|
|
962
967
|
}
|
|
963
968
|
|
|
964
969
|
const result: Partial<Version> = {};
|
|
@@ -1044,7 +1049,7 @@ export async function waitForEmulatorReady(this: ADB, timeoutMs: number = 20000)
|
|
|
1044
1049
|
let suffix = '';
|
|
1045
1050
|
const servicesValue = services;
|
|
1046
1051
|
if (servicesValue) {
|
|
1047
|
-
const missingServices =
|
|
1052
|
+
const missingServices = zip(REQUIRED_SERVICES, requiredServicesRe)
|
|
1048
1053
|
.filter(([, pattern]) => !(pattern as RegExp).test(servicesValue))
|
|
1049
1054
|
.map(([name]) => name);
|
|
1050
1055
|
suffix = ` (${missingServices} service${missingServices.length === 1 ? ' is' : 's are'} not running)`;
|
|
@@ -1133,6 +1138,7 @@ export async function reboot(
|
|
|
1133
1138
|
throw new Error(
|
|
1134
1139
|
`Could not reboot device. Rebooting requires root access and ` +
|
|
1135
1140
|
`attempt to get root access on device failed with error: '${message}'`,
|
|
1141
|
+
{cause: e},
|
|
1136
1142
|
);
|
|
1137
1143
|
}
|
|
1138
1144
|
throw error;
|
|
@@ -1293,6 +1299,7 @@ export async function installMitmCertificate(this: ADB, cert: Buffer | string):
|
|
|
1293
1299
|
`Is the certificate properly encoded into base64-string? ` +
|
|
1294
1300
|
`Do you have root permissions on the device? ` +
|
|
1295
1301
|
`Original error: ${error.message}`,
|
|
1302
|
+
{cause: err},
|
|
1296
1303
|
);
|
|
1297
1304
|
} finally {
|
|
1298
1305
|
await fs.rimraf(tmpRoot);
|
|
@@ -1325,6 +1332,7 @@ export async function isMitmCertificateInstalled(
|
|
|
1325
1332
|
`Cannot retrieve the certificate hash. ` +
|
|
1326
1333
|
`Is the certificate properly encoded into base64-string? ` +
|
|
1327
1334
|
`Original error: ${error.message}`,
|
|
1335
|
+
{cause: err},
|
|
1328
1336
|
);
|
|
1329
1337
|
} finally {
|
|
1330
1338
|
await fs.rimraf(tmpRoot);
|
|
@@ -1353,10 +1361,10 @@ export async function shellChunks(
|
|
|
1353
1361
|
let cmdChunk: string[] = [];
|
|
1354
1362
|
for (const arg of args) {
|
|
1355
1363
|
const nextCmd = argTransformer(arg);
|
|
1356
|
-
if (!
|
|
1364
|
+
if (!Array.isArray(nextCmd)) {
|
|
1357
1365
|
throw new Error('Argument transformer must result in an array');
|
|
1358
1366
|
}
|
|
1359
|
-
if (
|
|
1367
|
+
if (nextCmd.at(-1) !== ';') {
|
|
1360
1368
|
nextCmd.push(';');
|
|
1361
1369
|
}
|
|
1362
1370
|
if (nextCmd.join(' ').length + cmdChunk.join(' ').length >= MAX_SHELL_BUFFER_LENGTH) {
|
|
@@ -1365,7 +1373,7 @@ export async function shellChunks(
|
|
|
1365
1373
|
}
|
|
1366
1374
|
cmdChunk = [...cmdChunk, ...nextCmd];
|
|
1367
1375
|
}
|
|
1368
|
-
if (!
|
|
1376
|
+
if (!util.isEmpty(cmdChunk)) {
|
|
1369
1377
|
commands.push(cmdChunk);
|
|
1370
1378
|
}
|
|
1371
1379
|
log.debug(`Got the following command chunks to execute: ${JSON.stringify(commands)}`);
|
|
@@ -1392,18 +1400,18 @@ export async function shellChunks(
|
|
|
1392
1400
|
*/
|
|
1393
1401
|
export function toAvdLocaleArgs(language: string | null, country: string | null): string[] {
|
|
1394
1402
|
const result: string[] = [];
|
|
1395
|
-
if (language &&
|
|
1403
|
+
if (language && typeof language === 'string') {
|
|
1396
1404
|
result.push('-prop', `persist.sys.language=${language.toLowerCase()}`);
|
|
1397
1405
|
}
|
|
1398
|
-
if (country &&
|
|
1406
|
+
if (country && typeof country === 'string') {
|
|
1399
1407
|
result.push('-prop', `persist.sys.country=${country.toUpperCase()}`);
|
|
1400
1408
|
}
|
|
1401
1409
|
let locale: string | undefined;
|
|
1402
|
-
if (
|
|
1410
|
+
if (typeof language === 'string' && typeof country === 'string' && language && country) {
|
|
1403
1411
|
locale = language.toLowerCase() + '-' + country.toUpperCase();
|
|
1404
|
-
} else if (language &&
|
|
1412
|
+
} else if (language && typeof language === 'string') {
|
|
1405
1413
|
locale = language.toLowerCase();
|
|
1406
|
-
} else if (country &&
|
|
1414
|
+
} else if (country && typeof country === 'string') {
|
|
1407
1415
|
locale = country;
|
|
1408
1416
|
}
|
|
1409
1417
|
if (locale) {
|
|
@@ -1419,7 +1427,7 @@ export function toAvdLocaleArgs(language: string | null, country: string | null)
|
|
|
1419
1427
|
* @param sdkRoot - The Android SDK root directory path
|
|
1420
1428
|
* @returns Array of build-tools directory paths (newest first)
|
|
1421
1429
|
*/
|
|
1422
|
-
export const getBuildToolsDirs =
|
|
1430
|
+
export const getBuildToolsDirs = memoize(async function getBuildToolsDirs(
|
|
1423
1431
|
sdkRoot: string,
|
|
1424
1432
|
): Promise<string[]> {
|
|
1425
1433
|
let buildToolsDirs = await fs.glob('*/', {
|
package/lib/tools/types.ts
CHANGED