appium-adb 14.1.0 → 14.1.2
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.map +1 -1
- package/build/lib/adb.js.map +1 -1
- package/build/lib/helpers.d.ts.map +1 -1
- package/build/lib/helpers.js +3 -3
- package/build/lib/helpers.js.map +1 -1
- package/build/lib/logcat.d.ts.map +1 -1
- package/build/lib/logcat.js +16 -6
- package/build/lib/logcat.js.map +1 -1
- package/build/lib/logger.js.map +1 -1
- package/build/lib/tools/aab-utils.d.ts.map +1 -1
- package/build/lib/tools/aab-utils.js +25 -18
- 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 +36 -24
- 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 +58 -37
- 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 +46 -38
- 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 +21 -14
- 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 +44 -45
- 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 +38 -32
- package/build/lib/tools/device-settings.js.map +1 -1
- package/build/lib/tools/emu-constants.d.ts.map +1 -1
- package/build/lib/tools/emu-constants.js +4 -4
- package/build/lib/tools/emu-constants.js.map +1 -1
- package/build/lib/tools/emulator-commands.d.ts.map +1 -1
- package/build/lib/tools/emulator-commands.js +20 -17
- 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 +2 -1
- 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 +11 -15
- 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.map +1 -1
- package/build/lib/tools/lockmgmt.d.ts.map +1 -1
- package/build/lib/tools/lockmgmt.js +24 -23
- 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.map +1 -1
- package/build/lib/tools/network-commands.d.ts.map +1 -1
- package/build/lib/tools/network-commands.js +0 -1
- 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 +1 -1
- package/build/lib/tools/process-commands.js.map +1 -1
- package/build/lib/tools/system-calls.d.ts.map +1 -1
- package/build/lib/tools/system-calls.js +30 -36
- package/build/lib/tools/system-calls.js.map +1 -1
- package/build/lib/tools/types.d.ts +6 -6
- package/build/lib/tools/types.d.ts.map +1 -1
- package/lib/adb.ts +20 -25
- package/lib/helpers.ts +48 -36
- package/lib/logcat.ts +32 -22
- package/lib/logger.ts +1 -2
- package/lib/tools/aab-utils.js +42 -33
- package/lib/tools/android-manifest.js +78 -54
- package/lib/tools/apk-signing.ts +123 -89
- package/lib/tools/apk-utils.ts +206 -109
- package/lib/tools/apks-utils.js +50 -40
- package/lib/tools/app-commands.ts +220 -135
- package/lib/tools/device-settings.ts +154 -124
- package/lib/tools/emu-constants.ts +4 -5
- package/lib/tools/emulator-commands.ts +113 -65
- package/lib/tools/fs-commands.js +9 -8
- package/lib/tools/general-commands.js +48 -51
- package/lib/tools/keyboard-commands.js +22 -20
- package/lib/tools/lockmgmt.js +80 -51
- package/lib/tools/logcat-commands.js +6 -6
- package/lib/tools/network-commands.js +11 -12
- package/lib/tools/process-commands.ts +28 -15
- package/lib/tools/system-calls.ts +319 -221
- package/lib/tools/types.ts +19 -10
- package/package.json +4 -3
package/lib/tools/apk-utils.ts
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
|
-
APKS_EXTENSION,
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
APKS_EXTENSION,
|
|
3
|
+
buildInstallArgs,
|
|
4
|
+
APK_INSTALL_TIMEOUT,
|
|
5
|
+
DEFAULT_ADB_EXEC_TIMEOUT,
|
|
6
|
+
readPackageManifest,
|
|
5
7
|
} from '../helpers.js';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
+
import {exec, type ExecError} from 'teen_process';
|
|
9
|
+
import {log} from '../logger.js';
|
|
8
10
|
import path from 'path';
|
|
9
11
|
import _ from 'lodash';
|
|
10
|
-
import {
|
|
12
|
+
import {fs, util, mkdirp, timing} from '@appium/support';
|
|
11
13
|
import * as semver from 'semver';
|
|
12
14
|
import os from 'os';
|
|
13
|
-
import {
|
|
14
|
-
import type {
|
|
15
|
+
import {LRUCache} from 'lru-cache';
|
|
16
|
+
import type {ADB} from '../adb.js';
|
|
15
17
|
import type {
|
|
16
18
|
UninstallOptions,
|
|
17
19
|
ShellExecOptions,
|
|
@@ -22,7 +24,7 @@ import type {
|
|
|
22
24
|
ApkStrings,
|
|
23
25
|
AppInfo,
|
|
24
26
|
InstallState,
|
|
25
|
-
StringRecord
|
|
27
|
+
StringRecord,
|
|
26
28
|
} from './types.js';
|
|
27
29
|
|
|
28
30
|
export const REMOTE_CACHE_ROOT = '/data/local/tmp/appium_cache';
|
|
@@ -35,9 +37,13 @@ export const REMOTE_CACHE_ROOT = '/data/local/tmp/appium_cache';
|
|
|
35
37
|
* @returns True if the package was found on the device and
|
|
36
38
|
* successfully uninstalled.
|
|
37
39
|
*/
|
|
38
|
-
export async function uninstallApk
|
|
40
|
+
export async function uninstallApk(
|
|
41
|
+
this: ADB,
|
|
42
|
+
pkg: string,
|
|
43
|
+
options: UninstallOptions = {},
|
|
44
|
+
): Promise<boolean> {
|
|
39
45
|
log.debug(`Uninstalling ${pkg}`);
|
|
40
|
-
if (!options.skipInstallCheck && !await this.isAppInstalled(pkg)) {
|
|
46
|
+
if (!options.skipInstallCheck && !(await this.isAppInstalled(pkg))) {
|
|
41
47
|
log.info(`${pkg} was not uninstalled, because it was not present on the device`);
|
|
42
48
|
return false;
|
|
43
49
|
}
|
|
@@ -72,7 +78,11 @@ export async function uninstallApk (this: ADB, pkg: string, options: UninstallOp
|
|
|
72
78
|
* @param opts - Additional exec options.
|
|
73
79
|
* @throws If there was a failure during application install.
|
|
74
80
|
*/
|
|
75
|
-
export async function installFromDevicePath
|
|
81
|
+
export async function installFromDevicePath(
|
|
82
|
+
this: ADB,
|
|
83
|
+
apkPathOnDevice: string,
|
|
84
|
+
opts: ShellExecOptions = {},
|
|
85
|
+
): Promise<void> {
|
|
76
86
|
const stdout = await this.shell(['pm', 'install', '-r', apkPathOnDevice], opts);
|
|
77
87
|
if (stdout.includes('Failure')) {
|
|
78
88
|
throw new Error(`Remote install failed: ${stdout}`);
|
|
@@ -87,7 +97,11 @@ export async function installFromDevicePath (this: ADB, apkPathOnDevice: string,
|
|
|
87
97
|
* @returns Full path to the cached apk on the remote file system
|
|
88
98
|
* @throws if there was a failure while caching the app
|
|
89
99
|
*/
|
|
90
|
-
export async function cacheApk
|
|
100
|
+
export async function cacheApk(
|
|
101
|
+
this: ADB,
|
|
102
|
+
apkPath: string,
|
|
103
|
+
options: CachingOptions = {},
|
|
104
|
+
): Promise<string> {
|
|
91
105
|
const appHash = await fs.hash(apkPath);
|
|
92
106
|
const remotePath = path.posix.join(REMOTE_CACHE_ROOT, `${appHash}.apk`);
|
|
93
107
|
const remoteCachedFiles: string[] = [];
|
|
@@ -95,13 +109,21 @@ export async function cacheApk (this: ADB, apkPath: string, options: CachingOpti
|
|
|
95
109
|
try {
|
|
96
110
|
const errorMarker = '_ERROR_';
|
|
97
111
|
let lsOutput: string | null = null;
|
|
98
|
-
if (
|
|
112
|
+
if (
|
|
113
|
+
this._areExtendedLsOptionsSupported === true ||
|
|
114
|
+
!_.isBoolean(this._areExtendedLsOptionsSupported)
|
|
115
|
+
) {
|
|
99
116
|
lsOutput = await this.shell([`ls -t -1 ${REMOTE_CACHE_ROOT} 2>&1 || echo ${errorMarker}`]);
|
|
100
117
|
}
|
|
101
|
-
if (
|
|
118
|
+
if (
|
|
119
|
+
!_.isString(lsOutput) ||
|
|
120
|
+
(lsOutput.includes(errorMarker) && !lsOutput.includes(REMOTE_CACHE_ROOT))
|
|
121
|
+
) {
|
|
102
122
|
if (!_.isBoolean(this._areExtendedLsOptionsSupported)) {
|
|
103
|
-
log.debug(
|
|
104
|
-
'
|
|
123
|
+
log.debug(
|
|
124
|
+
'The current Android API does not support extended ls options. ' +
|
|
125
|
+
'Defaulting to no-options call',
|
|
126
|
+
);
|
|
105
127
|
}
|
|
106
128
|
lsOutput = await this.shell([`ls ${REMOTE_CACHE_ROOT} 2>&1 || echo ${errorMarker}`]);
|
|
107
129
|
this._areExtendedLsOptionsSupported = false;
|
|
@@ -111,15 +133,18 @@ export async function cacheApk (this: ADB, apkPath: string, options: CachingOpti
|
|
|
111
133
|
if (lsOutput.includes(errorMarker)) {
|
|
112
134
|
throw new Error(lsOutput.substring(0, lsOutput.indexOf(errorMarker)));
|
|
113
135
|
}
|
|
114
|
-
remoteCachedFiles.push(
|
|
115
|
-
lsOutput
|
|
136
|
+
remoteCachedFiles.push(
|
|
137
|
+
...lsOutput
|
|
138
|
+
.split('\n')
|
|
116
139
|
.map((x) => x.trim())
|
|
117
|
-
.filter(Boolean)
|
|
118
|
-
)
|
|
140
|
+
.filter(Boolean),
|
|
141
|
+
);
|
|
119
142
|
} catch (e) {
|
|
120
143
|
const err = e as Error;
|
|
121
|
-
log.debug(
|
|
122
|
-
`
|
|
144
|
+
log.debug(
|
|
145
|
+
`Got an error '${err.message.trim()}' while getting the list of files in the cache. ` +
|
|
146
|
+
`Assuming the cache does not exist yet`,
|
|
147
|
+
);
|
|
123
148
|
await this.shell(['mkdir', '-p', REMOTE_CACHE_ROOT]);
|
|
124
149
|
}
|
|
125
150
|
log.debug(`The count of applications in the cache: ${remoteCachedFiles.length}`);
|
|
@@ -136,8 +161,10 @@ export async function cacheApk (this: ADB, apkPath: string, options: CachingOpti
|
|
|
136
161
|
const timer = new timing.Timer().start();
|
|
137
162
|
await this.push(apkPath, remotePath, {timeout: options.timeout});
|
|
138
163
|
const {size} = await fs.stat(apkPath);
|
|
139
|
-
log.info(
|
|
140
|
-
`
|
|
164
|
+
log.info(
|
|
165
|
+
`The upload of '${path.basename(apkPath)}' (${util.toReadableSizeString(size)}) ` +
|
|
166
|
+
`took ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`,
|
|
167
|
+
);
|
|
141
168
|
}
|
|
142
169
|
if (!this.remoteAppsCache) {
|
|
143
170
|
this.remoteAppsCache = new LRUCache({
|
|
@@ -145,8 +172,9 @@ export async function cacheApk (this: ADB, apkPath: string, options: CachingOpti
|
|
|
145
172
|
});
|
|
146
173
|
}
|
|
147
174
|
// Cleanup the invalid entries from the cache
|
|
148
|
-
_.difference([...this.remoteAppsCache.keys()], remoteCachedFiles.map(toHash))
|
|
149
|
-
|
|
175
|
+
_.difference([...this.remoteAppsCache.keys()], remoteCachedFiles.map(toHash)).forEach((hash) =>
|
|
176
|
+
(this.remoteAppsCache as LRUCache<string, string>).delete(hash),
|
|
177
|
+
);
|
|
150
178
|
// Bump the cache record for the recently cached item
|
|
151
179
|
this.remoteAppsCache.set(appHash, remotePath);
|
|
152
180
|
// If the remote cache exceeds this.remoteAppsCacheLimit, remove the least recently used entries
|
|
@@ -160,8 +188,10 @@ export async function cacheApk (this: ADB, apkPath: string, options: CachingOpti
|
|
|
160
188
|
log.debug(`Deleted ${entriesToCleanup.length} expired application cache entries`);
|
|
161
189
|
} catch (e) {
|
|
162
190
|
const err = e as Error;
|
|
163
|
-
log.warn(
|
|
164
|
-
`
|
|
191
|
+
log.warn(
|
|
192
|
+
`Cannot delete ${entriesToCleanup.length} expired application cache entries. ` +
|
|
193
|
+
`Original error: ${err.message}`,
|
|
194
|
+
);
|
|
165
195
|
}
|
|
166
196
|
}
|
|
167
197
|
return remotePath;
|
|
@@ -174,7 +204,11 @@ export async function cacheApk (this: ADB, apkPath: string, options: CachingOpti
|
|
|
174
204
|
* @param options - The set of installation options.
|
|
175
205
|
* @throws If an unexpected error happens during install.
|
|
176
206
|
*/
|
|
177
|
-
export async function install
|
|
207
|
+
export async function install(
|
|
208
|
+
this: ADB,
|
|
209
|
+
appPath: string,
|
|
210
|
+
options: InstallOptions = {},
|
|
211
|
+
): Promise<void> {
|
|
178
212
|
if (appPath.endsWith(APKS_EXTENSION)) {
|
|
179
213
|
return await this.installApks(appPath, options);
|
|
180
214
|
}
|
|
@@ -182,12 +216,13 @@ export async function install (this: ADB, appPath: string, options: InstallOptio
|
|
|
182
216
|
options = _.cloneDeep(options);
|
|
183
217
|
_.defaults(options, {
|
|
184
218
|
replace: true,
|
|
185
|
-
timeout:
|
|
219
|
+
timeout:
|
|
220
|
+
this.adbExecTimeout === DEFAULT_ADB_EXEC_TIMEOUT ? APK_INSTALL_TIMEOUT : this.adbExecTimeout,
|
|
186
221
|
timeoutCapName: 'androidInstallTimeout',
|
|
187
222
|
});
|
|
188
223
|
|
|
189
224
|
const installArgs = buildInstallArgs(await this.getApiLevel(), options);
|
|
190
|
-
if (options.noIncremental && await this.isIncrementalInstallSupported()) {
|
|
225
|
+
if (options.noIncremental && (await this.isIncrementalInstallSupported())) {
|
|
191
226
|
// Adb throws an error if it does not know about an arg,
|
|
192
227
|
// which is the case here for older adb versions.
|
|
193
228
|
installArgs.push('--no-incremental');
|
|
@@ -196,19 +231,17 @@ export async function install (this: ADB, appPath: string, options: InstallOptio
|
|
|
196
231
|
timeout: options.timeout,
|
|
197
232
|
timeoutCapName: options.timeoutCapName,
|
|
198
233
|
};
|
|
199
|
-
const installCmd = [
|
|
200
|
-
'install',
|
|
201
|
-
...installArgs,
|
|
202
|
-
appPath,
|
|
203
|
-
];
|
|
234
|
+
const installCmd = ['install', ...installArgs, appPath];
|
|
204
235
|
let performAppInstall = async () => await this.adbExec(installCmd, installOpts);
|
|
205
236
|
// this.remoteAppsCacheLimit <= 0 means no caching should be applied
|
|
206
237
|
let shouldCacheApp = (this.remoteAppsCacheLimit as number) > 0;
|
|
207
238
|
if (shouldCacheApp) {
|
|
208
239
|
shouldCacheApp = !(await this.isStreamedInstallSupported());
|
|
209
240
|
if (!shouldCacheApp) {
|
|
210
|
-
log.info(
|
|
211
|
-
`
|
|
241
|
+
log.info(
|
|
242
|
+
`The application at '${appPath}' will not be cached, because the device under test has ` +
|
|
243
|
+
`confirmed the support of streamed installs`,
|
|
244
|
+
);
|
|
212
245
|
}
|
|
213
246
|
}
|
|
214
247
|
if (shouldCacheApp) {
|
|
@@ -216,25 +249,31 @@ export async function install (this: ADB, appPath: string, options: InstallOptio
|
|
|
216
249
|
log.info(`Clearing the cache at '${REMOTE_CACHE_ROOT}'`);
|
|
217
250
|
await this.shell(['rm', '-rf', `${REMOTE_CACHE_ROOT}/*`]);
|
|
218
251
|
};
|
|
219
|
-
const cacheApp = async () =>
|
|
220
|
-
|
|
221
|
-
|
|
252
|
+
const cacheApp = async () =>
|
|
253
|
+
await this.cacheApk(appPath, {
|
|
254
|
+
timeout: options.timeout,
|
|
255
|
+
});
|
|
222
256
|
try {
|
|
223
257
|
const cachedAppPath = await cacheApp();
|
|
224
258
|
performAppInstall = async () => {
|
|
225
259
|
const pmInstallCmdByRemotePath = (remotePath: string) => [
|
|
226
|
-
'pm',
|
|
260
|
+
'pm',
|
|
261
|
+
'install',
|
|
227
262
|
...installArgs,
|
|
228
263
|
remotePath,
|
|
229
264
|
];
|
|
230
265
|
const output = await this.shell(pmInstallCmdByRemotePath(cachedAppPath), installOpts);
|
|
231
266
|
// https://github.com/appium/appium/issues/13970
|
|
232
267
|
if (/\bINSTALL_FAILED_INSUFFICIENT_STORAGE\b/.test(output)) {
|
|
233
|
-
log.warn(
|
|
234
|
-
`
|
|
268
|
+
log.warn(
|
|
269
|
+
`There was a failure while installing '${appPath}' ` +
|
|
270
|
+
`because of the insufficient device storage space`,
|
|
271
|
+
);
|
|
235
272
|
await clearCache();
|
|
236
|
-
log.info(
|
|
237
|
-
`
|
|
273
|
+
log.info(
|
|
274
|
+
`Consider decreasing the maximum amount of cached apps ` +
|
|
275
|
+
`(currently ${this.remoteAppsCacheLimit}) to avoid such issues in the future`,
|
|
276
|
+
);
|
|
238
277
|
const newCachedAppPath = await cacheApp();
|
|
239
278
|
return await this.shell(pmInstallCmdByRemotePath(newCachedAppPath), installOpts);
|
|
240
279
|
}
|
|
@@ -251,9 +290,13 @@ export async function install (this: ADB, appPath: string, options: InstallOptio
|
|
|
251
290
|
try {
|
|
252
291
|
const timer = new timing.Timer().start();
|
|
253
292
|
const output = await performAppInstall();
|
|
254
|
-
log.info(
|
|
255
|
-
|
|
256
|
-
|
|
293
|
+
log.info(
|
|
294
|
+
`The installation of '${path.basename(appPath)}' took ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`,
|
|
295
|
+
);
|
|
296
|
+
const truncatedOutput =
|
|
297
|
+
!_.isString(output) || output.length <= 300
|
|
298
|
+
? output
|
|
299
|
+
: `${output.substring(0, 150)}...${output.substring(output.length - 150)}`;
|
|
257
300
|
log.debug(`Install command stdout: ${truncatedOutput}`);
|
|
258
301
|
if (/\[INSTALL[A-Z_]+FAILED[A-Z_]+\]/.test(output)) {
|
|
259
302
|
if (this.isTestPackageOnlyError(output)) {
|
|
@@ -282,7 +325,11 @@ export async function install (this: ADB, appPath: string, options: InstallOptio
|
|
|
282
325
|
* try to extract it on its own
|
|
283
326
|
* @returns One of `APP_INSTALL_STATE` constants
|
|
284
327
|
*/
|
|
285
|
-
export async function getApplicationInstallState
|
|
328
|
+
export async function getApplicationInstallState(
|
|
329
|
+
this: ADB,
|
|
330
|
+
appPath: string,
|
|
331
|
+
pkg: string | null = null,
|
|
332
|
+
): Promise<InstallState> {
|
|
286
333
|
let apkInfo: AppInfo | null = null;
|
|
287
334
|
if (!pkg) {
|
|
288
335
|
apkInfo = (await this.getApkInfo(appPath)) as AppInfo;
|
|
@@ -319,30 +366,48 @@ export async function getApplicationInstallState (this: ADB, appPath: string, pk
|
|
|
319
366
|
}
|
|
320
367
|
if (_.isInteger(apkVersionCode) && _.isInteger(pkgVersionCode)) {
|
|
321
368
|
if ((pkgVersionCode as number) > (apkVersionCode as number)) {
|
|
322
|
-
log.debug(
|
|
369
|
+
log.debug(
|
|
370
|
+
`The version code of the installed '${pkg}' is greater than the application version code (${pkgVersionCode} > ${apkVersionCode})`,
|
|
371
|
+
);
|
|
323
372
|
return this.APP_INSTALL_STATE.NEWER_VERSION_INSTALLED;
|
|
324
373
|
}
|
|
325
374
|
// Version codes might not be maintained. Check version names.
|
|
326
375
|
if (pkgVersionCode === apkVersionCode) {
|
|
327
|
-
if (
|
|
328
|
-
|
|
376
|
+
if (
|
|
377
|
+
_.isString(apkVersionName) &&
|
|
378
|
+
_.isString(pkgVersionName) &&
|
|
379
|
+
semver.satisfies(pkgVersionName, `>=${apkVersionName}`)
|
|
380
|
+
) {
|
|
381
|
+
log.debug(
|
|
382
|
+
`The version name of the installed '${pkg}' is greater or equal to the application version name ('${pkgVersionName}' >= '${apkVersionName}')`,
|
|
383
|
+
);
|
|
329
384
|
return semver.satisfies(pkgVersionName, `>${apkVersionName}`)
|
|
330
385
|
? this.APP_INSTALL_STATE.NEWER_VERSION_INSTALLED
|
|
331
386
|
: this.APP_INSTALL_STATE.SAME_VERSION_INSTALLED;
|
|
332
387
|
}
|
|
333
388
|
if (!_.isString(apkVersionName) || !_.isString(pkgVersionName)) {
|
|
334
|
-
log.debug(
|
|
389
|
+
log.debug(
|
|
390
|
+
`The version name of the installed '${pkg}' is equal to application version name (${pkgVersionCode} === ${apkVersionCode})`,
|
|
391
|
+
);
|
|
335
392
|
return this.APP_INSTALL_STATE.SAME_VERSION_INSTALLED;
|
|
336
393
|
}
|
|
337
394
|
}
|
|
338
|
-
} else if (
|
|
339
|
-
|
|
395
|
+
} else if (
|
|
396
|
+
_.isString(apkVersionName) &&
|
|
397
|
+
_.isString(pkgVersionName) &&
|
|
398
|
+
semver.satisfies(pkgVersionName, `>=${apkVersionName}`)
|
|
399
|
+
) {
|
|
400
|
+
log.debug(
|
|
401
|
+
`The version name of the installed '${pkg}' is greater or equal to the application version name ('${pkgVersionName}' >= '${apkVersionName}')`,
|
|
402
|
+
);
|
|
340
403
|
return semver.satisfies(pkgVersionName, `>${apkVersionName}`)
|
|
341
404
|
? this.APP_INSTALL_STATE.NEWER_VERSION_INSTALLED
|
|
342
405
|
: this.APP_INSTALL_STATE.SAME_VERSION_INSTALLED;
|
|
343
406
|
}
|
|
344
407
|
|
|
345
|
-
log.debug(
|
|
408
|
+
log.debug(
|
|
409
|
+
`The installed '${pkg}' package is older than '${appPath}' (${pkgVersionCode} < ${apkVersionCode} or '${pkgVersionName}' < '${apkVersionName}')'`,
|
|
410
|
+
);
|
|
346
411
|
return this.APP_INSTALL_STATE.OLDER_VERSION_INSTALLED;
|
|
347
412
|
}
|
|
348
413
|
|
|
@@ -356,7 +421,12 @@ export async function getApplicationInstallState (this: ADB, appPath: string, pk
|
|
|
356
421
|
* @param options - Set of install options.
|
|
357
422
|
* @throws If an unexpected error happens during install.
|
|
358
423
|
*/
|
|
359
|
-
export async function installOrUpgrade
|
|
424
|
+
export async function installOrUpgrade(
|
|
425
|
+
this: ADB,
|
|
426
|
+
appPath: string,
|
|
427
|
+
pkg: string | null = null,
|
|
428
|
+
options: InstallOrUpgradeOptions = {},
|
|
429
|
+
): Promise<InstallOrUpgradeResult> {
|
|
360
430
|
if (!pkg) {
|
|
361
431
|
const apkInfo = await this.getApkInfo(appPath);
|
|
362
432
|
if ('name' in apkInfo) {
|
|
@@ -364,18 +434,16 @@ export async function installOrUpgrade (this: ADB, appPath: string, pkg: string
|
|
|
364
434
|
} else {
|
|
365
435
|
log.warn(
|
|
366
436
|
`Cannot determine the package name of '${appPath}'. ` +
|
|
367
|
-
|
|
437
|
+
`Continuing with the install anyway`,
|
|
368
438
|
);
|
|
369
439
|
}
|
|
370
440
|
}
|
|
371
441
|
|
|
372
|
-
const {
|
|
373
|
-
enforceCurrentBuild,
|
|
374
|
-
} = options;
|
|
442
|
+
const {enforceCurrentBuild} = options;
|
|
375
443
|
const appState = await this.getApplicationInstallState(appPath, pkg);
|
|
376
444
|
let wasUninstalled = false;
|
|
377
445
|
const uninstallPackage = async () => {
|
|
378
|
-
if (!await this.uninstallApk(pkg as string, {skipInstallCheck: true})) {
|
|
446
|
+
if (!(await this.uninstallApk(pkg as string, {skipInstallCheck: true}))) {
|
|
379
447
|
throw new Error(`'${pkg}' package cannot be uninstalled`);
|
|
380
448
|
}
|
|
381
449
|
wasUninstalled = true;
|
|
@@ -420,7 +488,9 @@ export async function installOrUpgrade (this: ADB, appPath: string, pkg: string
|
|
|
420
488
|
await this.install(appPath, {...options, replace: true});
|
|
421
489
|
} catch (err) {
|
|
422
490
|
const error = err as Error;
|
|
423
|
-
log.warn(
|
|
491
|
+
log.warn(
|
|
492
|
+
`Cannot install/upgrade '${pkg}' because of '${error.message}'. Trying full reinstall`,
|
|
493
|
+
);
|
|
424
494
|
await uninstallPackage();
|
|
425
495
|
await this.install(appPath, {...options, replace: false});
|
|
426
496
|
}
|
|
@@ -440,11 +510,11 @@ export async function installOrUpgrade (this: ADB, appPath: string, pkg: string
|
|
|
440
510
|
* store the extracted file to. If not provided then the `localPath` property in the returned object
|
|
441
511
|
* will be undefined.
|
|
442
512
|
*/
|
|
443
|
-
export async function extractStringsFromApk
|
|
513
|
+
export async function extractStringsFromApk(
|
|
444
514
|
this: ADB,
|
|
445
515
|
appPath: string,
|
|
446
516
|
language: string | null = null,
|
|
447
|
-
outRoot: string | null = null
|
|
517
|
+
outRoot: string | null = null,
|
|
448
518
|
): Promise<ApkStrings> {
|
|
449
519
|
log.debug(`Extracting strings from for language: ${language || 'default'}`);
|
|
450
520
|
const originalAppPath = appPath;
|
|
@@ -457,49 +527,73 @@ export async function extractStringsFromApk (
|
|
|
457
527
|
try {
|
|
458
528
|
await this.initAapt();
|
|
459
529
|
|
|
460
|
-
configMarker = await formatConfigMarker(
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
530
|
+
configMarker = await formatConfigMarker(
|
|
531
|
+
async () => {
|
|
532
|
+
const {stdout} = await exec((this.binaries as StringRecord).aapt as string, [
|
|
533
|
+
'd',
|
|
534
|
+
'configurations',
|
|
535
|
+
appPath,
|
|
536
|
+
]);
|
|
537
|
+
return _.uniq(stdout.split(os.EOL));
|
|
538
|
+
},
|
|
539
|
+
language,
|
|
540
|
+
'(default)',
|
|
541
|
+
);
|
|
466
542
|
|
|
467
543
|
const {stdout} = await exec((this.binaries as StringRecord).aapt as string, [
|
|
468
|
-
'd',
|
|
544
|
+
'd',
|
|
545
|
+
'--values',
|
|
546
|
+
'resources',
|
|
547
|
+
appPath,
|
|
469
548
|
]);
|
|
470
549
|
apkStrings = parseAaptStrings(stdout, configMarker);
|
|
471
550
|
} catch (e) {
|
|
472
551
|
const err = e as ExecError;
|
|
473
|
-
log.debug(
|
|
474
|
-
|
|
552
|
+
log.debug(
|
|
553
|
+
'Cannot extract resources using aapt. Trying aapt2. ' +
|
|
554
|
+
`Original error: ${err.stderr || err.message}`,
|
|
555
|
+
);
|
|
475
556
|
|
|
476
557
|
await this.initAapt2();
|
|
477
558
|
|
|
478
|
-
configMarker = await formatConfigMarker(
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
559
|
+
configMarker = await formatConfigMarker(
|
|
560
|
+
async () => {
|
|
561
|
+
const {stdout} = await exec((this.binaries as StringRecord).aapt2 as string, [
|
|
562
|
+
'd',
|
|
563
|
+
'configurations',
|
|
564
|
+
appPath,
|
|
565
|
+
]);
|
|
566
|
+
return _.uniq(stdout.split(os.EOL));
|
|
567
|
+
},
|
|
568
|
+
language,
|
|
569
|
+
'',
|
|
570
|
+
);
|
|
484
571
|
|
|
485
572
|
try {
|
|
486
573
|
const {stdout} = await exec((this.binaries as StringRecord).aapt2 as string, [
|
|
487
|
-
'd',
|
|
574
|
+
'd',
|
|
575
|
+
'resources',
|
|
576
|
+
appPath,
|
|
488
577
|
]);
|
|
489
578
|
apkStrings = parseAapt2Strings(stdout, configMarker);
|
|
490
579
|
} catch (e) {
|
|
491
580
|
const error = e as Error;
|
|
492
|
-
throw new Error(
|
|
493
|
-
`Original error: ${error.message}
|
|
581
|
+
throw new Error(
|
|
582
|
+
`Cannot extract resources from '${originalAppPath}'. ` + `Original error: ${error.message}`,
|
|
583
|
+
);
|
|
494
584
|
}
|
|
495
585
|
}
|
|
496
586
|
|
|
497
587
|
if (_.isEmpty(apkStrings)) {
|
|
498
|
-
log.warn(
|
|
499
|
-
`
|
|
588
|
+
log.warn(
|
|
589
|
+
`No strings have been found in '${originalAppPath}' resources ` +
|
|
590
|
+
`for '${configMarker || 'default'}' configuration`,
|
|
591
|
+
);
|
|
500
592
|
} else {
|
|
501
|
-
log.info(
|
|
502
|
-
`
|
|
593
|
+
log.info(
|
|
594
|
+
`Successfully extracted ${_.keys(apkStrings).length} strings from ` +
|
|
595
|
+
`'${originalAppPath}' resources for '${configMarker || 'default'}' configuration`,
|
|
596
|
+
);
|
|
503
597
|
}
|
|
504
598
|
|
|
505
599
|
if (!outRoot) {
|
|
@@ -519,8 +613,8 @@ export async function extractStringsFromApk (
|
|
|
519
613
|
* file system.
|
|
520
614
|
* @returns The parsed application information.
|
|
521
615
|
*/
|
|
522
|
-
export async function getApkInfo
|
|
523
|
-
if (!await fs.exists(appPath)) {
|
|
616
|
+
export async function getApkInfo(this: ADB, appPath: string): Promise<AppInfo | {}> {
|
|
617
|
+
if (!(await fs.exists(appPath))) {
|
|
524
618
|
throw new Error(`The file at path ${appPath} does not exist or is not accessible`);
|
|
525
619
|
}
|
|
526
620
|
|
|
@@ -554,7 +648,11 @@ export async function getApkInfo (this: ADB, appPath: string): Promise<AppInfo |
|
|
|
554
648
|
* @param defaultMarker The default config marker value
|
|
555
649
|
* @returns The formatted config marker
|
|
556
650
|
*/
|
|
557
|
-
async function formatConfigMarker
|
|
651
|
+
async function formatConfigMarker(
|
|
652
|
+
configsGetter: () => Promise<string[]>,
|
|
653
|
+
desiredMarker: string | null,
|
|
654
|
+
defaultMarker: string,
|
|
655
|
+
): Promise<string> {
|
|
558
656
|
let configMarker = desiredMarker || defaultMarker;
|
|
559
657
|
if (configMarker.includes('-') && !configMarker.includes('-r')) {
|
|
560
658
|
configMarker = configMarker.replace('-', '-r');
|
|
@@ -562,10 +660,14 @@ async function formatConfigMarker (configsGetter: () => Promise<string[]>, desir
|
|
|
562
660
|
const configs = await configsGetter();
|
|
563
661
|
log.debug(`Resource configurations: ${JSON.stringify(configs)}`);
|
|
564
662
|
// Assume the 'en' configuration is the default one
|
|
565
|
-
if (
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
663
|
+
if (
|
|
664
|
+
configMarker.toLowerCase().startsWith('en') &&
|
|
665
|
+
!configs.some((x) => x.trim() === configMarker)
|
|
666
|
+
) {
|
|
667
|
+
log.debug(
|
|
668
|
+
`Resource configuration name '${configMarker}' is unknown. ` +
|
|
669
|
+
`Replacing it with '${defaultMarker}'`,
|
|
670
|
+
);
|
|
569
671
|
configMarker = defaultMarker;
|
|
570
672
|
} else {
|
|
571
673
|
log.debug(`Selected configuration: '${configMarker}'`);
|
|
@@ -583,9 +685,9 @@ async function formatConfigMarker (configsGetter: () => Promise<string[]>, desir
|
|
|
583
685
|
* values are represented as arrays. If no config found for the
|
|
584
686
|
* given marker then an empty mapping is returned.
|
|
585
687
|
*/
|
|
586
|
-
export function parseAapt2Strings
|
|
688
|
+
export function parseAapt2Strings(rawOutput: string, configMarker: string): StringRecord {
|
|
587
689
|
const allLines = rawOutput.split(os.EOL);
|
|
588
|
-
function extractContent
|
|
690
|
+
function extractContent(startIdx: number): [string | null, number] {
|
|
589
691
|
let idx = startIdx;
|
|
590
692
|
const startCharPos = allLines[startIdx].indexOf('"');
|
|
591
693
|
if (startCharPos < 0) {
|
|
@@ -597,15 +699,9 @@ export function parseAapt2Strings (rawOutput: string, configMarker: string): Str
|
|
|
597
699
|
if (terminationCharMatch) {
|
|
598
700
|
const terminationCharPos = terminationCharMatch.index;
|
|
599
701
|
if (startIdx === idx) {
|
|
600
|
-
return [
|
|
601
|
-
allLines[idx].substring(startCharPos + 1, terminationCharPos),
|
|
602
|
-
idx
|
|
603
|
-
];
|
|
702
|
+
return [allLines[idx].substring(startCharPos + 1, terminationCharPos), idx];
|
|
604
703
|
}
|
|
605
|
-
return [
|
|
606
|
-
`${result}\\n${_.trimStart(allLines[idx].substring(0, terminationCharPos))}`,
|
|
607
|
-
idx,
|
|
608
|
-
];
|
|
704
|
+
return [`${result}\\n${_.trimStart(allLines[idx].substring(0, terminationCharPos))}`, idx];
|
|
609
705
|
}
|
|
610
706
|
if (idx > startIdx) {
|
|
611
707
|
result += `\\n${_.trimStart(allLines[idx])}`;
|
|
@@ -670,7 +766,9 @@ export function parseAapt2Strings (rawOutput: string, configMarker: string): Str
|
|
|
670
766
|
lineIndex = idx;
|
|
671
767
|
if (_.isString(content)) {
|
|
672
768
|
apkStrings[currentResourceId] = [
|
|
673
|
-
...(Array.isArray(apkStrings[currentResourceId])
|
|
769
|
+
...(Array.isArray(apkStrings[currentResourceId])
|
|
770
|
+
? apkStrings[currentResourceId]
|
|
771
|
+
: []),
|
|
674
772
|
content,
|
|
675
773
|
];
|
|
676
774
|
}
|
|
@@ -699,7 +797,7 @@ export function parseAapt2Strings (rawOutput: string, configMarker: string): Str
|
|
|
699
797
|
* values are represented as arrays. If no config found for the
|
|
700
798
|
* given marker then an empty mapping is returned.
|
|
701
799
|
*/
|
|
702
|
-
export function parseAaptStrings
|
|
800
|
+
export function parseAaptStrings(rawOutput: string, configMarker: string): StringRecord {
|
|
703
801
|
const normalizeStringMatch = function (s: string) {
|
|
704
802
|
return s.replace(/"$/, '').replace(/^"/, '').replace(/\\"/g, '"');
|
|
705
803
|
};
|
|
@@ -770,4 +868,3 @@ export function parseAaptStrings (rawOutput: string, configMarker: string): Stri
|
|
|
770
868
|
}
|
|
771
869
|
|
|
772
870
|
// #endregion
|
|
773
|
-
|