appium-adb 14.0.4 → 14.0.6
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/tools/apk-utils.d.ts +44 -67
- package/build/lib/tools/apk-utils.d.ts.map +1 -1
- package/build/lib/tools/apk-utils.js +74 -76
- package/build/lib/tools/apk-utils.js.map +1 -1
- package/build/lib/tools/app-commands.d.ts +125 -150
- package/build/lib/tools/app-commands.d.ts.map +1 -1
- package/build/lib/tools/app-commands.js +194 -234
- package/build/lib/tools/app-commands.js.map +1 -1
- package/build/lib/tools/device-settings.d.ts +103 -142
- package/build/lib/tools/device-settings.d.ts.map +1 -1
- package/build/lib/tools/device-settings.js +84 -124
- package/build/lib/tools/device-settings.js.map +1 -1
- package/lib/tools/{apk-utils.js → apk-utils.ts} +118 -105
- package/lib/tools/{app-commands.js → app-commands.ts} +279 -294
- package/lib/tools/{device-settings.js → device-settings.ts} +136 -172
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
APK_INSTALL_TIMEOUT, DEFAULT_ADB_EXEC_TIMEOUT,
|
|
4
4
|
readPackageManifest
|
|
5
5
|
} from '../helpers.js';
|
|
6
|
-
import { exec } from 'teen_process';
|
|
6
|
+
import { exec, type ExecError } from 'teen_process';
|
|
7
7
|
import { log } from '../logger.js';
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import _ from 'lodash';
|
|
@@ -11,19 +11,31 @@ import { fs, util, mkdirp, timing } from '@appium/support';
|
|
|
11
11
|
import * as semver from 'semver';
|
|
12
12
|
import os from 'os';
|
|
13
13
|
import { LRUCache } from 'lru-cache';
|
|
14
|
+
import type { ADB } from '../adb.js';
|
|
15
|
+
import type {
|
|
16
|
+
UninstallOptions,
|
|
17
|
+
ShellExecOptions,
|
|
18
|
+
CachingOptions,
|
|
19
|
+
InstallOptions,
|
|
20
|
+
InstallOrUpgradeOptions,
|
|
21
|
+
InstallOrUpgradeResult,
|
|
22
|
+
ApkStrings,
|
|
23
|
+
AppInfo,
|
|
24
|
+
InstallState,
|
|
25
|
+
StringRecord
|
|
26
|
+
} from './types.js';
|
|
14
27
|
|
|
15
28
|
export const REMOTE_CACHE_ROOT = '/data/local/tmp/appium_cache';
|
|
16
29
|
|
|
17
30
|
/**
|
|
18
31
|
* Uninstall the given package from the device under test.
|
|
19
32
|
*
|
|
20
|
-
* @
|
|
21
|
-
* @param
|
|
22
|
-
* @
|
|
23
|
-
* @return {Promise<boolean>} True if the package was found on the device and
|
|
33
|
+
* @param pkg - The name of the package to be uninstalled.
|
|
34
|
+
* @param options - The set of uninstall options.
|
|
35
|
+
* @returns True if the package was found on the device and
|
|
24
36
|
* successfully uninstalled.
|
|
25
37
|
*/
|
|
26
|
-
export async function uninstallApk (pkg, options = {}) {
|
|
38
|
+
export async function uninstallApk (this: ADB, pkg: string, options: UninstallOptions = {}): Promise<boolean> {
|
|
27
39
|
log.debug(`Uninstalling ${pkg}`);
|
|
28
40
|
if (!options.skipInstallCheck && !await this.isAppInstalled(pkg)) {
|
|
29
41
|
log.info(`${pkg} was not uninstalled, because it was not present on the device`);
|
|
@@ -36,12 +48,13 @@ export async function uninstallApk (pkg, options = {}) {
|
|
|
36
48
|
}
|
|
37
49
|
cmd.push(pkg);
|
|
38
50
|
|
|
39
|
-
let stdout;
|
|
51
|
+
let stdout: string;
|
|
40
52
|
try {
|
|
41
53
|
await this.forceStop(pkg);
|
|
42
54
|
stdout = (await this.adbExec(cmd, {timeout: options.timeout})).trim();
|
|
43
55
|
} catch (e) {
|
|
44
|
-
|
|
56
|
+
const err = e as Error;
|
|
57
|
+
throw new Error(`Unable to uninstall APK. Original error: ${err.message}`);
|
|
45
58
|
}
|
|
46
59
|
log.debug(`'adb ${cmd.join(' ')}' command output: ${stdout}`);
|
|
47
60
|
if (stdout.includes('Success')) {
|
|
@@ -55,13 +68,12 @@ export async function uninstallApk (pkg, options = {}) {
|
|
|
55
68
|
/**
|
|
56
69
|
* Install the package after it was pushed to the device under test.
|
|
57
70
|
*
|
|
58
|
-
* @
|
|
59
|
-
* @param
|
|
60
|
-
* @
|
|
61
|
-
* @throws {error} If there was a failure during application install.
|
|
71
|
+
* @param apkPathOnDevice - The full path to the package on the device file system.
|
|
72
|
+
* @param opts - Additional exec options.
|
|
73
|
+
* @throws If there was a failure during application install.
|
|
62
74
|
*/
|
|
63
|
-
export async function installFromDevicePath (apkPathOnDevice, opts = {}) {
|
|
64
|
-
const stdout =
|
|
75
|
+
export async function installFromDevicePath (this: ADB, apkPathOnDevice: string, opts: ShellExecOptions = {}): Promise<void> {
|
|
76
|
+
const stdout = await this.shell(['pm', 'install', '-r', apkPathOnDevice], opts);
|
|
65
77
|
if (stdout.includes('Failure')) {
|
|
66
78
|
throw new Error(`Remote install failed: ${stdout}`);
|
|
67
79
|
}
|
|
@@ -70,20 +82,19 @@ export async function installFromDevicePath (apkPathOnDevice, opts = {}) {
|
|
|
70
82
|
/**
|
|
71
83
|
* Caches the given APK at a remote location to speed up further APK deployments.
|
|
72
84
|
*
|
|
73
|
-
* @
|
|
74
|
-
* @param
|
|
75
|
-
* @
|
|
76
|
-
* @
|
|
77
|
-
* @throws {Error} if there was a failure while caching the app
|
|
85
|
+
* @param apkPath - Full path to the apk on the local FS
|
|
86
|
+
* @param options - Caching options
|
|
87
|
+
* @returns Full path to the cached apk on the remote file system
|
|
88
|
+
* @throws if there was a failure while caching the app
|
|
78
89
|
*/
|
|
79
|
-
export async function cacheApk (apkPath, options = {}) {
|
|
90
|
+
export async function cacheApk (this: ADB, apkPath: string, options: CachingOptions = {}): Promise<string> {
|
|
80
91
|
const appHash = await fs.hash(apkPath);
|
|
81
92
|
const remotePath = path.posix.join(REMOTE_CACHE_ROOT, `${appHash}.apk`);
|
|
82
|
-
const remoteCachedFiles = [];
|
|
93
|
+
const remoteCachedFiles: string[] = [];
|
|
83
94
|
// Get current contents of the remote cache or create it for the first time
|
|
84
95
|
try {
|
|
85
96
|
const errorMarker = '_ERROR_';
|
|
86
|
-
let lsOutput = null;
|
|
97
|
+
let lsOutput: string | null = null;
|
|
87
98
|
if (this._areExtendedLsOptionsSupported === true || !_.isBoolean(this._areExtendedLsOptionsSupported)) {
|
|
88
99
|
lsOutput = await this.shell([`ls -t -1 ${REMOTE_CACHE_ROOT} 2>&1 || echo ${errorMarker}`]);
|
|
89
100
|
}
|
|
@@ -106,12 +117,13 @@ export async function cacheApk (apkPath, options = {}) {
|
|
|
106
117
|
.filter(Boolean)
|
|
107
118
|
));
|
|
108
119
|
} catch (e) {
|
|
109
|
-
|
|
120
|
+
const err = e as Error;
|
|
121
|
+
log.debug(`Got an error '${err.message.trim()}' while getting the list of files in the cache. ` +
|
|
110
122
|
`Assuming the cache does not exist yet`);
|
|
111
123
|
await this.shell(['mkdir', '-p', REMOTE_CACHE_ROOT]);
|
|
112
124
|
}
|
|
113
125
|
log.debug(`The count of applications in the cache: ${remoteCachedFiles.length}`);
|
|
114
|
-
const toHash = (remotePath) => path.posix.parse(remotePath).name;
|
|
126
|
+
const toHash = (remotePath: string) => path.posix.parse(remotePath).name;
|
|
115
127
|
// Push the apk to the remote cache if needed
|
|
116
128
|
if (remoteCachedFiles.some((x) => toHash(x) === appHash)) {
|
|
117
129
|
log.info(`The application at '${apkPath}' is already cached to '${remotePath}'`);
|
|
@@ -129,26 +141,27 @@ export async function cacheApk (apkPath, options = {}) {
|
|
|
129
141
|
}
|
|
130
142
|
if (!this.remoteAppsCache) {
|
|
131
143
|
this.remoteAppsCache = new LRUCache({
|
|
132
|
-
max:
|
|
144
|
+
max: this.remoteAppsCacheLimit as number,
|
|
133
145
|
});
|
|
134
146
|
}
|
|
135
147
|
// Cleanup the invalid entries from the cache
|
|
136
148
|
_.difference([...this.remoteAppsCache.keys()], remoteCachedFiles.map(toHash))
|
|
137
|
-
.forEach((hash) => (
|
|
149
|
+
.forEach((hash) => (this.remoteAppsCache as LRUCache<string, string>).delete(hash));
|
|
138
150
|
// Bump the cache record for the recently cached item
|
|
139
151
|
this.remoteAppsCache.set(appHash, remotePath);
|
|
140
152
|
// If the remote cache exceeds this.remoteAppsCacheLimit, remove the least recently used entries
|
|
141
153
|
const entriesToCleanup = remoteCachedFiles
|
|
142
154
|
.map((x) => path.posix.join(REMOTE_CACHE_ROOT, x))
|
|
143
|
-
.filter((x) => !(
|
|
144
|
-
.slice((
|
|
155
|
+
.filter((x) => !(this.remoteAppsCache as LRUCache<string, string>).has(toHash(x)))
|
|
156
|
+
.slice((this.remoteAppsCacheLimit as number) - [...this.remoteAppsCache.keys()].length);
|
|
145
157
|
if (!_.isEmpty(entriesToCleanup)) {
|
|
146
158
|
try {
|
|
147
159
|
await this.shell(['rm', '-f', ...entriesToCleanup]);
|
|
148
160
|
log.debug(`Deleted ${entriesToCleanup.length} expired application cache entries`);
|
|
149
161
|
} catch (e) {
|
|
162
|
+
const err = e as Error;
|
|
150
163
|
log.warn(`Cannot delete ${entriesToCleanup.length} expired application cache entries. ` +
|
|
151
|
-
`Original error: ${
|
|
164
|
+
`Original error: ${err.message}`);
|
|
152
165
|
}
|
|
153
166
|
}
|
|
154
167
|
return remotePath;
|
|
@@ -157,12 +170,11 @@ export async function cacheApk (apkPath, options = {}) {
|
|
|
157
170
|
/**
|
|
158
171
|
* Install the package from the local file system.
|
|
159
172
|
*
|
|
160
|
-
* @
|
|
161
|
-
* @param
|
|
162
|
-
* @
|
|
163
|
-
* @throws {Error} If an unexpected error happens during install.
|
|
173
|
+
* @param appPath - The full path to the local package.
|
|
174
|
+
* @param options - The set of installation options.
|
|
175
|
+
* @throws If an unexpected error happens during install.
|
|
164
176
|
*/
|
|
165
|
-
export async function install (appPath, options = {}) {
|
|
177
|
+
export async function install (this: ADB, appPath: string, options: InstallOptions = {}): Promise<void> {
|
|
166
178
|
if (appPath.endsWith(APKS_EXTENSION)) {
|
|
167
179
|
return await this.installApks(appPath, options);
|
|
168
180
|
}
|
|
@@ -191,7 +203,7 @@ export async function install (appPath, options = {}) {
|
|
|
191
203
|
];
|
|
192
204
|
let performAppInstall = async () => await this.adbExec(installCmd, installOpts);
|
|
193
205
|
// this.remoteAppsCacheLimit <= 0 means no caching should be applied
|
|
194
|
-
let shouldCacheApp = (
|
|
206
|
+
let shouldCacheApp = (this.remoteAppsCacheLimit as number) > 0;
|
|
195
207
|
if (shouldCacheApp) {
|
|
196
208
|
shouldCacheApp = !(await this.isStreamedInstallSupported());
|
|
197
209
|
if (!shouldCacheApp) {
|
|
@@ -210,7 +222,7 @@ export async function install (appPath, options = {}) {
|
|
|
210
222
|
try {
|
|
211
223
|
const cachedAppPath = await cacheApp();
|
|
212
224
|
performAppInstall = async () => {
|
|
213
|
-
const pmInstallCmdByRemotePath = (remotePath) => [
|
|
225
|
+
const pmInstallCmdByRemotePath = (remotePath: string) => [
|
|
214
226
|
'pm', 'install',
|
|
215
227
|
...installArgs,
|
|
216
228
|
remotePath,
|
|
@@ -229,15 +241,16 @@ export async function install (appPath, options = {}) {
|
|
|
229
241
|
return output;
|
|
230
242
|
};
|
|
231
243
|
} catch (e) {
|
|
232
|
-
|
|
233
|
-
log.
|
|
244
|
+
const err = e as Error;
|
|
245
|
+
log.debug(err);
|
|
246
|
+
log.warn(`There was a failure while caching '${appPath}': ${err.message}`);
|
|
234
247
|
log.warn('Falling back to the default installation procedure');
|
|
235
248
|
await clearCache();
|
|
236
249
|
}
|
|
237
250
|
}
|
|
238
251
|
try {
|
|
239
252
|
const timer = new timing.Timer().start();
|
|
240
|
-
const output =
|
|
253
|
+
const output = await performAppInstall();
|
|
241
254
|
log.info(`The installation of '${path.basename(appPath)}' took ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`);
|
|
242
255
|
const truncatedOutput = (!_.isString(output) || output.length <= 300) ?
|
|
243
256
|
output : `${output.substring(0, 150)}...${output.substring(output.length - 150)}`;
|
|
@@ -251,10 +264,11 @@ export async function install (appPath, options = {}) {
|
|
|
251
264
|
throw new Error(output);
|
|
252
265
|
}
|
|
253
266
|
} catch (err) {
|
|
267
|
+
const error = err as Error;
|
|
254
268
|
// on some systems this will throw an error if the app already
|
|
255
269
|
// exists
|
|
256
|
-
if (!
|
|
257
|
-
throw
|
|
270
|
+
if (!error.message.includes('INSTALL_FAILED_ALREADY_EXISTS')) {
|
|
271
|
+
throw error;
|
|
258
272
|
}
|
|
259
273
|
log.debug(`Application '${appPath}' already installed. Continuing.`);
|
|
260
274
|
}
|
|
@@ -263,18 +277,16 @@ export async function install (appPath, options = {}) {
|
|
|
263
277
|
/**
|
|
264
278
|
* Retrieves the current installation state of the particular application
|
|
265
279
|
*
|
|
266
|
-
* @
|
|
267
|
-
* @param
|
|
268
|
-
* @param {string?} [pkg=null] - Package identifier. If omitted then the script will
|
|
280
|
+
* @param appPath - Full path to the application
|
|
281
|
+
* @param pkg - Package identifier. If omitted then the script will
|
|
269
282
|
* try to extract it on its own
|
|
270
|
-
* @returns
|
|
283
|
+
* @returns One of `APP_INSTALL_STATE` constants
|
|
271
284
|
*/
|
|
272
|
-
export async function getApplicationInstallState (appPath, pkg = null) {
|
|
273
|
-
let apkInfo = null;
|
|
285
|
+
export async function getApplicationInstallState (this: ADB, appPath: string, pkg: string | null = null): Promise<InstallState> {
|
|
286
|
+
let apkInfo: AppInfo | null = null;
|
|
274
287
|
if (!pkg) {
|
|
275
|
-
apkInfo = await this.getApkInfo(appPath);
|
|
276
|
-
|
|
277
|
-
pkg = apkInfo.name;
|
|
288
|
+
apkInfo = (await this.getApkInfo(appPath)) as AppInfo;
|
|
289
|
+
pkg = apkInfo?.name;
|
|
278
290
|
}
|
|
279
291
|
if (!pkg) {
|
|
280
292
|
log.warn(`Cannot read the package name of '${appPath}'`);
|
|
@@ -292,10 +304,10 @@ export async function getApplicationInstallState (appPath, pkg = null) {
|
|
|
292
304
|
}
|
|
293
305
|
const pkgVersionName = semver.valid(semver.coerce(pkgVersionNameStr));
|
|
294
306
|
if (!apkInfo) {
|
|
295
|
-
apkInfo = await this.getApkInfo(appPath);
|
|
307
|
+
apkInfo = (await this.getApkInfo(appPath)) as AppInfo;
|
|
296
308
|
}
|
|
297
309
|
// @ts-ignore We validate the values below
|
|
298
|
-
const {versionCode: apkVersionCode, versionName: apkVersionNameStr} = apkInfo;
|
|
310
|
+
const {versionCode: apkVersionCode, versionName: apkVersionNameStr} = apkInfo || {};
|
|
299
311
|
const apkVersionName = semver.valid(semver.coerce(apkVersionNameStr));
|
|
300
312
|
|
|
301
313
|
if (!_.isInteger(apkVersionCode) || !_.isInteger(pkgVersionCode)) {
|
|
@@ -306,7 +318,7 @@ export async function getApplicationInstallState (appPath, pkg = null) {
|
|
|
306
318
|
}
|
|
307
319
|
}
|
|
308
320
|
if (_.isInteger(apkVersionCode) && _.isInteger(pkgVersionCode)) {
|
|
309
|
-
if ((
|
|
321
|
+
if ((pkgVersionCode as number) > (apkVersionCode as number)) {
|
|
310
322
|
log.debug(`The version code of the installed '${pkg}' is greater than the application version code (${pkgVersionCode} > ${apkVersionCode})`);
|
|
311
323
|
return this.APP_INSTALL_STATE.NEWER_VERSION_INSTALLED;
|
|
312
324
|
}
|
|
@@ -338,15 +350,13 @@ export async function getApplicationInstallState (appPath, pkg = null) {
|
|
|
338
350
|
* Install the package from the local file system or upgrade it if an older
|
|
339
351
|
* version of the same package is already installed.
|
|
340
352
|
*
|
|
341
|
-
* @
|
|
342
|
-
* @param
|
|
343
|
-
* @param {string?} [pkg=null] - The name of the installed package. The method will
|
|
353
|
+
* @param appPath - The full path to the local package.
|
|
354
|
+
* @param pkg - The name of the installed package. The method will
|
|
344
355
|
* perform faster if it is set.
|
|
345
|
-
* @param
|
|
346
|
-
* @throws
|
|
347
|
-
* @returns {Promise<import('./types').InstallOrUpgradeResult>}
|
|
356
|
+
* @param options - Set of install options.
|
|
357
|
+
* @throws If an unexpected error happens during install.
|
|
348
358
|
*/
|
|
349
|
-
export async function installOrUpgrade (appPath, pkg = null, options = {}) {
|
|
359
|
+
export async function installOrUpgrade (this: ADB, appPath: string, pkg: string | null = null, options: InstallOrUpgradeOptions = {}): Promise<InstallOrUpgradeResult> {
|
|
350
360
|
if (!pkg) {
|
|
351
361
|
const apkInfo = await this.getApkInfo(appPath);
|
|
352
362
|
if ('name' in apkInfo) {
|
|
@@ -365,7 +375,7 @@ export async function installOrUpgrade (appPath, pkg = null, options = {}) {
|
|
|
365
375
|
const appState = await this.getApplicationInstallState(appPath, pkg);
|
|
366
376
|
let wasUninstalled = false;
|
|
367
377
|
const uninstallPackage = async () => {
|
|
368
|
-
if (!await this.uninstallApk(
|
|
378
|
+
if (!await this.uninstallApk(pkg as string, {skipInstallCheck: true})) {
|
|
369
379
|
throw new Error(`'${pkg}' package cannot be uninstalled`);
|
|
370
380
|
}
|
|
371
381
|
wasUninstalled = true;
|
|
@@ -409,7 +419,8 @@ export async function installOrUpgrade (appPath, pkg = null, options = {}) {
|
|
|
409
419
|
try {
|
|
410
420
|
await this.install(appPath, {...options, replace: true});
|
|
411
421
|
} catch (err) {
|
|
412
|
-
|
|
422
|
+
const error = err as Error;
|
|
423
|
+
log.warn(`Cannot install/upgrade '${pkg}' because of '${error.message}'. Trying full reinstall`);
|
|
413
424
|
await uninstallPackage();
|
|
414
425
|
await this.install(appPath, {...options, replace: false});
|
|
415
426
|
}
|
|
@@ -422,63 +433,64 @@ export async function installOrUpgrade (appPath, pkg = null, options = {}) {
|
|
|
422
433
|
/**
|
|
423
434
|
* Extract string resources from the given package on local file system.
|
|
424
435
|
*
|
|
425
|
-
* @
|
|
426
|
-
* @param
|
|
427
|
-
* @param {string?} [language=null] - The name of the language to extract the resources for.
|
|
436
|
+
* @param appPath - The full path to the .apk(s) package.
|
|
437
|
+
* @param language - The name of the language to extract the resources for.
|
|
428
438
|
* The default language is used if this equals to `null`
|
|
429
|
-
* @param
|
|
439
|
+
* @param outRoot - The name of the destination folder on the local file system to
|
|
430
440
|
* store the extracted file to. If not provided then the `localPath` property in the returned object
|
|
431
441
|
* will be undefined.
|
|
432
|
-
* @return {Promise<import('./types').ApkStrings>}
|
|
433
442
|
*/
|
|
434
443
|
export async function extractStringsFromApk (
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
444
|
+
this: ADB,
|
|
445
|
+
appPath: string,
|
|
446
|
+
language: string | null = null,
|
|
447
|
+
outRoot: string | null = null
|
|
448
|
+
): Promise<ApkStrings> {
|
|
439
449
|
log.debug(`Extracting strings from for language: ${language || 'default'}`);
|
|
440
450
|
const originalAppPath = appPath;
|
|
441
451
|
if (appPath.endsWith(APKS_EXTENSION)) {
|
|
442
452
|
appPath = await this.extractLanguageApk(appPath, language);
|
|
443
453
|
}
|
|
444
454
|
|
|
445
|
-
let apkStrings = {};
|
|
446
|
-
let configMarker;
|
|
455
|
+
let apkStrings: StringRecord = {};
|
|
456
|
+
let configMarker: string;
|
|
447
457
|
try {
|
|
448
458
|
await this.initAapt();
|
|
449
459
|
|
|
450
460
|
configMarker = await formatConfigMarker(async () => {
|
|
451
|
-
const {stdout} = await exec((
|
|
461
|
+
const {stdout} = await exec((this.binaries as StringRecord).aapt as string, [
|
|
452
462
|
'd', 'configurations', appPath,
|
|
453
463
|
]);
|
|
454
464
|
return _.uniq(stdout.split(os.EOL));
|
|
455
465
|
}, language, '(default)');
|
|
456
466
|
|
|
457
|
-
const {stdout} = await exec((
|
|
467
|
+
const {stdout} = await exec((this.binaries as StringRecord).aapt as string, [
|
|
458
468
|
'd', '--values', 'resources', appPath,
|
|
459
469
|
]);
|
|
460
470
|
apkStrings = parseAaptStrings(stdout, configMarker);
|
|
461
471
|
} catch (e) {
|
|
472
|
+
const err = e as ExecError;
|
|
462
473
|
log.debug('Cannot extract resources using aapt. Trying aapt2. ' +
|
|
463
|
-
`Original error: ${
|
|
474
|
+
`Original error: ${err.stderr || err.message}`);
|
|
464
475
|
|
|
465
476
|
await this.initAapt2();
|
|
466
477
|
|
|
467
478
|
configMarker = await formatConfigMarker(async () => {
|
|
468
|
-
const {stdout} = await exec((
|
|
479
|
+
const {stdout} = await exec((this.binaries as StringRecord).aapt2 as string, [
|
|
469
480
|
'd', 'configurations', appPath,
|
|
470
481
|
]);
|
|
471
482
|
return _.uniq(stdout.split(os.EOL));
|
|
472
483
|
}, language, '');
|
|
473
484
|
|
|
474
485
|
try {
|
|
475
|
-
const {stdout} = await exec((
|
|
486
|
+
const {stdout} = await exec((this.binaries as StringRecord).aapt2 as string, [
|
|
476
487
|
'd', 'resources', appPath,
|
|
477
488
|
]);
|
|
478
489
|
apkStrings = parseAapt2Strings(stdout, configMarker);
|
|
479
490
|
} catch (e) {
|
|
491
|
+
const error = e as Error;
|
|
480
492
|
throw new Error(`Cannot extract resources from '${originalAppPath}'. ` +
|
|
481
|
-
`Original error: ${
|
|
493
|
+
`Original error: ${error.message}`);
|
|
482
494
|
}
|
|
483
495
|
}
|
|
484
496
|
|
|
@@ -503,12 +515,11 @@ export async function extractStringsFromApk (
|
|
|
503
515
|
/**
|
|
504
516
|
* Get the package info from local apk file.
|
|
505
517
|
*
|
|
506
|
-
* @
|
|
507
|
-
* @param {string} appPath - The full path to existing .apk(s) package on the local
|
|
518
|
+
* @param appPath - The full path to existing .apk(s) package on the local
|
|
508
519
|
* file system.
|
|
509
|
-
* @
|
|
520
|
+
* @returns The parsed application information.
|
|
510
521
|
*/
|
|
511
|
-
export async function getApkInfo (appPath) {
|
|
522
|
+
export async function getApkInfo (this: ADB, appPath: string): Promise<AppInfo | {}> {
|
|
512
523
|
if (!await fs.exists(appPath)) {
|
|
513
524
|
throw new Error(`The file at path ${appPath} does not exist or is not accessible`);
|
|
514
525
|
}
|
|
@@ -525,7 +536,8 @@ export async function getApkInfo (appPath) {
|
|
|
525
536
|
versionName,
|
|
526
537
|
};
|
|
527
538
|
} catch (e) {
|
|
528
|
-
|
|
539
|
+
const err = e as Error;
|
|
540
|
+
log.warn(`Error '${err.message}' while getting badging info`);
|
|
529
541
|
}
|
|
530
542
|
return {};
|
|
531
543
|
}
|
|
@@ -536,13 +548,13 @@ export async function getApkInfo (appPath) {
|
|
|
536
548
|
* Formats the config marker, which is then passed to parse.. methods
|
|
537
549
|
* to make it compatible with resource formats generated by aapt(2) tool
|
|
538
550
|
*
|
|
539
|
-
* @param
|
|
551
|
+
* @param configsGetter The function whose result is a list
|
|
540
552
|
* of apk configs
|
|
541
|
-
* @param
|
|
542
|
-
* @param
|
|
543
|
-
* @
|
|
553
|
+
* @param desiredMarker The desired config marker value
|
|
554
|
+
* @param defaultMarker The default config marker value
|
|
555
|
+
* @returns The formatted config marker
|
|
544
556
|
*/
|
|
545
|
-
async function formatConfigMarker (configsGetter
|
|
557
|
+
async function formatConfigMarker (configsGetter: () => Promise<string[]>, desiredMarker: string | null, defaultMarker: string): Promise<string> {
|
|
546
558
|
let configMarker = desiredMarker || defaultMarker;
|
|
547
559
|
if (configMarker.includes('-') && !configMarker.includes('-r')) {
|
|
548
560
|
configMarker = configMarker.replace('-', '-r');
|
|
@@ -564,16 +576,16 @@ async function formatConfigMarker (configsGetter, desiredMarker, defaultMarker)
|
|
|
564
576
|
/**
|
|
565
577
|
* Parses apk strings from aapt2 tool output
|
|
566
578
|
*
|
|
567
|
-
* @param
|
|
568
|
-
* @param
|
|
579
|
+
* @param rawOutput The actual tool output
|
|
580
|
+
* @param configMarker The config marker. Usually
|
|
569
581
|
* a language abbreviation or an empty string for the default one
|
|
570
|
-
* @returns
|
|
582
|
+
* @returns Strings ids to values mapping. Plural
|
|
571
583
|
* values are represented as arrays. If no config found for the
|
|
572
584
|
* given marker then an empty mapping is returned.
|
|
573
585
|
*/
|
|
574
|
-
export function parseAapt2Strings (rawOutput, configMarker) {
|
|
586
|
+
export function parseAapt2Strings (rawOutput: string, configMarker: string): StringRecord {
|
|
575
587
|
const allLines = rawOutput.split(os.EOL);
|
|
576
|
-
function extractContent (startIdx) {
|
|
588
|
+
function extractContent (startIdx: number): [string | null, number] {
|
|
577
589
|
let idx = startIdx;
|
|
578
590
|
const startCharPos = allLines[startIdx].indexOf('"');
|
|
579
591
|
if (startCharPos < 0) {
|
|
@@ -605,8 +617,8 @@ export function parseAapt2Strings (rawOutput, configMarker) {
|
|
|
605
617
|
return [result, idx];
|
|
606
618
|
}
|
|
607
619
|
|
|
608
|
-
const apkStrings = {};
|
|
609
|
-
let currentResourceId = null;
|
|
620
|
+
const apkStrings: StringRecord = {};
|
|
621
|
+
let currentResourceId: string | null = null;
|
|
610
622
|
let isInPluralGroup = false;
|
|
611
623
|
let isInCurrentConfig = false;
|
|
612
624
|
let lineIndex = 0;
|
|
@@ -658,7 +670,7 @@ export function parseAapt2Strings (rawOutput, configMarker) {
|
|
|
658
670
|
lineIndex = idx;
|
|
659
671
|
if (_.isString(content)) {
|
|
660
672
|
apkStrings[currentResourceId] = [
|
|
661
|
-
...(apkStrings[currentResourceId]
|
|
673
|
+
...(Array.isArray(apkStrings[currentResourceId]) ? apkStrings[currentResourceId] : []),
|
|
662
674
|
content,
|
|
663
675
|
];
|
|
664
676
|
}
|
|
@@ -680,21 +692,21 @@ export function parseAapt2Strings (rawOutput, configMarker) {
|
|
|
680
692
|
/**
|
|
681
693
|
* Parses apk strings from aapt tool output
|
|
682
694
|
*
|
|
683
|
-
* @param
|
|
684
|
-
* @param
|
|
695
|
+
* @param rawOutput The actual tool output
|
|
696
|
+
* @param configMarker The config marker. Usually
|
|
685
697
|
* a language abbreviation or `(default)`
|
|
686
|
-
* @returns
|
|
698
|
+
* @returns Strings ids to values mapping. Plural
|
|
687
699
|
* values are represented as arrays. If no config found for the
|
|
688
700
|
* given marker then an empty mapping is returned.
|
|
689
701
|
*/
|
|
690
|
-
export function parseAaptStrings (rawOutput, configMarker) {
|
|
691
|
-
const normalizeStringMatch = function (s) {
|
|
702
|
+
export function parseAaptStrings (rawOutput: string, configMarker: string): StringRecord {
|
|
703
|
+
const normalizeStringMatch = function (s: string) {
|
|
692
704
|
return s.replace(/"$/, '').replace(/^"/, '').replace(/\\"/g, '"');
|
|
693
705
|
};
|
|
694
706
|
|
|
695
|
-
const apkStrings = {};
|
|
707
|
+
const apkStrings: StringRecord = {};
|
|
696
708
|
let isInConfig = false;
|
|
697
|
-
let currentResourceId = null;
|
|
709
|
+
let currentResourceId: string | null = null;
|
|
698
710
|
let isInPluralGroup = false;
|
|
699
711
|
// The pattern matches any quoted content including escaped quotes
|
|
700
712
|
const quotedStringPattern = /"[^"\\]*(?:\\.[^"\\]*)*"/;
|
|
@@ -747,7 +759,7 @@ export function parseAaptStrings (rawOutput, configMarker) {
|
|
|
747
759
|
const match = quotedStringPattern.exec(trimmedLine);
|
|
748
760
|
if (match) {
|
|
749
761
|
apkStrings[currentResourceId] = [
|
|
750
|
-
...(apkStrings[currentResourceId]
|
|
762
|
+
...(Array.isArray(apkStrings[currentResourceId]) ? apkStrings[currentResourceId] : []),
|
|
751
763
|
normalizeStringMatch(match[0]),
|
|
752
764
|
];
|
|
753
765
|
}
|
|
@@ -758,3 +770,4 @@ export function parseAaptStrings (rawOutput, configMarker) {
|
|
|
758
770
|
}
|
|
759
771
|
|
|
760
772
|
// #endregion
|
|
773
|
+
|