@react-native-harness/platform-android 1.1.0-rc.1 → 1.1.0-rc.3

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.
Files changed (81) hide show
  1. package/README.md +9 -2
  2. package/dist/__tests__/adb.test.js +283 -10
  3. package/dist/__tests__/avd-config.test.d.ts +2 -0
  4. package/dist/__tests__/avd-config.test.d.ts.map +1 -0
  5. package/dist/__tests__/avd-config.test.js +174 -0
  6. package/dist/__tests__/ci-action.test.d.ts +2 -0
  7. package/dist/__tests__/ci-action.test.d.ts.map +1 -0
  8. package/dist/__tests__/ci-action.test.js +46 -0
  9. package/dist/__tests__/emulator-startup.test.d.ts +2 -0
  10. package/dist/__tests__/emulator-startup.test.d.ts.map +1 -0
  11. package/dist/__tests__/emulator-startup.test.js +19 -0
  12. package/dist/__tests__/environment.test.d.ts +2 -0
  13. package/dist/__tests__/environment.test.d.ts.map +1 -0
  14. package/dist/__tests__/environment.test.js +51 -0
  15. package/dist/__tests__/instance.test.d.ts +2 -0
  16. package/dist/__tests__/instance.test.d.ts.map +1 -0
  17. package/dist/__tests__/instance.test.js +423 -0
  18. package/dist/__tests__/shared-prefs.test.d.ts +2 -0
  19. package/dist/__tests__/shared-prefs.test.d.ts.map +1 -0
  20. package/dist/__tests__/shared-prefs.test.js +87 -0
  21. package/dist/adb.d.ts +23 -0
  22. package/dist/adb.d.ts.map +1 -1
  23. package/dist/adb.js +265 -16
  24. package/dist/app-monitor.d.ts.map +1 -1
  25. package/dist/app-monitor.js +29 -8
  26. package/dist/avd-config.d.ts +41 -0
  27. package/dist/avd-config.d.ts.map +1 -0
  28. package/dist/avd-config.js +173 -0
  29. package/dist/config.d.ts +77 -0
  30. package/dist/config.d.ts.map +1 -1
  31. package/dist/config.js +5 -0
  32. package/dist/emulator-startup.d.ts +3 -0
  33. package/dist/emulator-startup.d.ts.map +1 -0
  34. package/dist/emulator-startup.js +17 -0
  35. package/dist/environment.d.ts +28 -0
  36. package/dist/environment.d.ts.map +1 -0
  37. package/dist/environment.js +295 -0
  38. package/dist/errors.d.ts +4 -12
  39. package/dist/errors.d.ts.map +1 -1
  40. package/dist/errors.js +10 -24
  41. package/dist/factory.d.ts.map +1 -1
  42. package/dist/factory.js +3 -0
  43. package/dist/index.d.ts +3 -0
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +3 -0
  46. package/dist/instance.d.ts +6 -0
  47. package/dist/instance.d.ts.map +1 -0
  48. package/dist/instance.js +234 -0
  49. package/dist/runner.d.ts +3 -3
  50. package/dist/runner.d.ts.map +1 -1
  51. package/dist/runner.js +12 -48
  52. package/dist/shared-prefs.d.ts +3 -0
  53. package/dist/shared-prefs.d.ts.map +1 -0
  54. package/dist/shared-prefs.js +92 -0
  55. package/dist/targets.d.ts +1 -1
  56. package/dist/targets.d.ts.map +1 -1
  57. package/dist/targets.js +2 -0
  58. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  59. package/package.json +4 -4
  60. package/src/__tests__/adb.test.ts +419 -15
  61. package/src/__tests__/avd-config.test.ts +206 -0
  62. package/src/__tests__/ci-action.test.ts +81 -0
  63. package/src/__tests__/emulator-startup.test.ts +32 -0
  64. package/src/__tests__/environment.test.ts +87 -0
  65. package/src/__tests__/instance.test.ts +610 -0
  66. package/src/__tests__/shared-prefs.test.ts +144 -0
  67. package/src/adb.ts +423 -16
  68. package/src/app-monitor.ts +58 -18
  69. package/src/avd-config.ts +290 -0
  70. package/src/config.ts +8 -0
  71. package/src/emulator-startup.ts +28 -0
  72. package/src/environment.ts +510 -0
  73. package/src/errors.ts +19 -0
  74. package/src/factory.ts +4 -0
  75. package/src/index.ts +7 -1
  76. package/src/instance.ts +380 -0
  77. package/src/runner.ts +25 -63
  78. package/src/shared-prefs.ts +205 -0
  79. package/src/targets.ts +11 -8
  80. package/tsconfig.json +2 -2
  81. package/tsconfig.lib.json +2 -2
@@ -0,0 +1,510 @@
1
+ import { spawn } from '@react-native-harness/tools';
2
+ import { logger } from '@react-native-harness/tools';
3
+ import { createWriteStream } from 'node:fs';
4
+ import { access, cp, mkdir, mkdtemp, rm } from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import { pipeline } from 'node:stream/promises';
8
+ import https from 'node:https';
9
+
10
+ const CMDLINE_TOOLS_PATH_SEGMENTS = ['cmdline-tools', 'latest'];
11
+ const ANDROID_REPOSITORY_INDEX_URL =
12
+ 'https://dl.google.com/android/repository/repository2-1.xml';
13
+ const androidEnvironmentLogger = logger.child('android-environment');
14
+
15
+ export type AndroidSystemImageArch = 'x86_64' | 'arm64-v8a' | 'armeabi-v7a';
16
+
17
+ type AndroidSdkRootOptions = {
18
+ env?: NodeJS.ProcessEnv;
19
+ platform?: NodeJS.Platform;
20
+ homeDirectory?: string;
21
+ };
22
+
23
+ const getConfiguredAndroidSdkRoot = (
24
+ env: NodeJS.ProcessEnv = process.env
25
+ ): string | null => {
26
+ return env.ANDROID_HOME ?? env.ANDROID_SDK_ROOT ?? null;
27
+ };
28
+
29
+ export const getDefaultUnixAndroidSdkRoot = ({
30
+ platform = process.platform,
31
+ homeDirectory = os.homedir(),
32
+ }: Omit<AndroidSdkRootOptions, 'env'> = {}): string | null => {
33
+ if (platform === 'darwin') {
34
+ return path.join(homeDirectory, 'Library', 'Android', 'sdk');
35
+ }
36
+
37
+ if (platform === 'linux') {
38
+ return path.join(homeDirectory, 'Android', 'Sdk');
39
+ }
40
+
41
+ return null;
42
+ };
43
+
44
+ const canBootstrapAndroidSdk = (
45
+ platform: NodeJS.Platform = process.platform
46
+ ) => {
47
+ return platform === 'darwin' || platform === 'linux';
48
+ };
49
+
50
+ const pathExists = async (filePath: string): Promise<boolean> => {
51
+ try {
52
+ await access(filePath);
53
+ return true;
54
+ } catch {
55
+ return false;
56
+ }
57
+ };
58
+
59
+ const quoteShell = (value: string): string => {
60
+ return `'${value.replace(/'/g, `'\\''`)}'`;
61
+ };
62
+
63
+ const downloadText = async (url: string): Promise<string> => {
64
+ return new Promise((resolve, reject) => {
65
+ const request = https.get(url, (response) => {
66
+ const { statusCode = 0, headers } = response;
67
+
68
+ if (
69
+ statusCode >= 300 &&
70
+ statusCode < 400 &&
71
+ typeof headers.location === 'string'
72
+ ) {
73
+ response.resume();
74
+ resolve(downloadText(headers.location));
75
+ return;
76
+ }
77
+
78
+ if (statusCode !== 200) {
79
+ response.resume();
80
+ reject(
81
+ new Error(
82
+ `Failed to download Android repository index from ${url} (status ${statusCode}).`
83
+ )
84
+ );
85
+ return;
86
+ }
87
+
88
+ response.setEncoding('utf8');
89
+
90
+ let body = '';
91
+ response.on('data', (chunk: string) => {
92
+ body += chunk;
93
+ });
94
+ response.once('end', () => {
95
+ resolve(body);
96
+ });
97
+ });
98
+
99
+ request.once('error', reject);
100
+ });
101
+ };
102
+
103
+ const downloadFile = async (
104
+ url: string,
105
+ destinationPath: string
106
+ ): Promise<void> => {
107
+ await new Promise<void>((resolve, reject) => {
108
+ const request = https.get(url, (response) => {
109
+ const { statusCode = 0, headers } = response;
110
+
111
+ if (
112
+ statusCode >= 300 &&
113
+ statusCode < 400 &&
114
+ typeof headers.location === 'string'
115
+ ) {
116
+ response.resume();
117
+ resolve(downloadFile(headers.location, destinationPath));
118
+ return;
119
+ }
120
+
121
+ if (statusCode !== 200) {
122
+ response.resume();
123
+ reject(
124
+ new Error(
125
+ `Failed to download Android command-line tools from ${url} (status ${statusCode}).`
126
+ )
127
+ );
128
+ return;
129
+ }
130
+
131
+ const output = createWriteStream(destinationPath);
132
+ pipeline(response, output).then(resolve).catch(reject);
133
+ });
134
+
135
+ request.once('error', reject);
136
+ });
137
+ };
138
+
139
+ const getCommandLineToolsArchiveUrl = async (
140
+ platform: NodeJS.Platform = process.platform
141
+ ): Promise<string> => {
142
+ const archivePlatform =
143
+ platform === 'darwin' ? 'mac' : platform === 'linux' ? 'linux' : null;
144
+
145
+ if (!archivePlatform) {
146
+ throw new Error(
147
+ 'Automatic Android SDK bootstrap is only supported on macOS and Linux.'
148
+ );
149
+ }
150
+
151
+ const repositoryIndex = await downloadText(ANDROID_REPOSITORY_INDEX_URL);
152
+ const archivePattern = new RegExp(
153
+ `commandlinetools-${archivePlatform}-(\\d+)_latest\\.zip`,
154
+ 'g'
155
+ );
156
+ const matches = [...repositoryIndex.matchAll(archivePattern)];
157
+
158
+ if (matches.length === 0) {
159
+ throw new Error(
160
+ `Failed to resolve Android command-line tools archive for ${archivePlatform}.`
161
+ );
162
+ }
163
+
164
+ const newestArchive = matches
165
+ .map((match) => ({
166
+ fileName: match[0],
167
+ revision: Number(match[1]),
168
+ }))
169
+ .sort((left, right) => right.revision - left.revision)[0];
170
+
171
+ return `https://dl.google.com/android/repository/${newestArchive.fileName}`;
172
+ };
173
+
174
+ const ensureAndroidCommandLineTools = async (
175
+ sdkRoot: string,
176
+ platform: NodeJS.Platform = process.platform
177
+ ): Promise<void> => {
178
+ if (
179
+ (await pathExists(getSdkManagerBinaryPath(sdkRoot))) &&
180
+ (await pathExists(getAvdManagerBinaryPath(sdkRoot)))
181
+ ) {
182
+ return;
183
+ }
184
+
185
+ if (!canBootstrapAndroidSdk(platform)) {
186
+ throw new Error(
187
+ 'Android command-line tools are missing. Set ANDROID_HOME or ANDROID_SDK_ROOT to an initialized SDK.'
188
+ );
189
+ }
190
+
191
+ androidEnvironmentLogger.info(
192
+ 'Bootstrapping Android command-line tools in %s',
193
+ sdkRoot
194
+ );
195
+
196
+ await mkdir(sdkRoot, { recursive: true });
197
+
198
+ const temporaryDirectory = await mkdtemp(
199
+ path.join(os.tmpdir(), 'android-cmdline-tools-')
200
+ );
201
+ const archivePath = path.join(temporaryDirectory, 'cmdline-tools.zip');
202
+ const extractedPath = path.join(temporaryDirectory, 'extracted');
203
+ const sourceDirectory = path.join(extractedPath, 'cmdline-tools');
204
+ const targetDirectory = path.join(sdkRoot, ...CMDLINE_TOOLS_PATH_SEGMENTS);
205
+
206
+ try {
207
+ await downloadFile(
208
+ await getCommandLineToolsArchiveUrl(platform),
209
+ archivePath
210
+ );
211
+ await spawn('unzip', ['-q', archivePath, '-d', extractedPath]);
212
+ await rm(targetDirectory, { force: true, recursive: true });
213
+ await mkdir(path.dirname(targetDirectory), { recursive: true });
214
+ await cp(sourceDirectory, targetDirectory, { recursive: true });
215
+ } finally {
216
+ await rm(temporaryDirectory, { force: true, recursive: true });
217
+ }
218
+ };
219
+
220
+ const acceptAndroidLicenses = async (sdkRoot: string): Promise<void> => {
221
+ const sdkManagerBinaryPath = getSdkManagerBinaryPath(sdkRoot);
222
+
223
+ await spawn(
224
+ 'bash',
225
+ [
226
+ '-lc',
227
+ `yes | ${quoteShell(sdkManagerBinaryPath)} --sdk_root=${quoteShell(
228
+ sdkRoot
229
+ )} --licenses >/dev/null`,
230
+ ],
231
+ {
232
+ env: getAndroidProcessEnv({
233
+ ...process.env,
234
+ ANDROID_HOME: sdkRoot,
235
+ ANDROID_SDK_ROOT: sdkRoot,
236
+ }),
237
+ }
238
+ );
239
+ };
240
+
241
+ const getPackageVerificationPath = (
242
+ sdkRoot: string,
243
+ packageName: string
244
+ ): string | null => {
245
+ if (packageName === 'platform-tools') {
246
+ return getAdbBinaryPath(sdkRoot);
247
+ }
248
+
249
+ if (packageName === 'emulator') {
250
+ return getEmulatorBinaryPath(sdkRoot);
251
+ }
252
+
253
+ if (packageName.startsWith('platforms;android-')) {
254
+ return path.join(sdkRoot, packageName.replace(';', '/'));
255
+ }
256
+
257
+ if (packageName.startsWith('system-images;android-')) {
258
+ return path.join(sdkRoot, packageName.replaceAll(';', path.sep));
259
+ }
260
+
261
+ return null;
262
+ };
263
+
264
+ const getMissingAndroidSdkPackages = async (
265
+ sdkRoot: string,
266
+ packages: readonly string[]
267
+ ): Promise<string[]> => {
268
+ const missingPackages: string[] = [];
269
+
270
+ for (const packageName of packages) {
271
+ const verificationPath = getPackageVerificationPath(sdkRoot, packageName);
272
+
273
+ if (!verificationPath) {
274
+ continue;
275
+ }
276
+
277
+ if (!(await pathExists(verificationPath))) {
278
+ missingPackages.push(packageName);
279
+ }
280
+ }
281
+
282
+ return missingPackages;
283
+ };
284
+
285
+ const installAndroidSdkPackages = async (
286
+ sdkRoot: string,
287
+ packages: readonly string[]
288
+ ): Promise<void> => {
289
+ if (packages.length === 0) {
290
+ return;
291
+ }
292
+
293
+ const sdkManagerBinaryPath = getSdkManagerBinaryPath(sdkRoot);
294
+ const packageArgs = packages
295
+ .map((packageName) => quoteShell(packageName))
296
+ .join(' ');
297
+
298
+ androidEnvironmentLogger.info(
299
+ 'Installing missing Android SDK packages: %s',
300
+ packages.join(', ')
301
+ );
302
+
303
+ await acceptAndroidLicenses(sdkRoot);
304
+ await spawn(
305
+ 'bash',
306
+ [
307
+ '-lc',
308
+ `yes | ${quoteShell(sdkManagerBinaryPath)} --sdk_root=${quoteShell(
309
+ sdkRoot
310
+ )} ${packageArgs}`,
311
+ ],
312
+ {
313
+ env: getAndroidProcessEnv({
314
+ ...process.env,
315
+ ANDROID_HOME: sdkRoot,
316
+ ANDROID_SDK_ROOT: sdkRoot,
317
+ }),
318
+ }
319
+ );
320
+ };
321
+
322
+ export const getAndroidSdkRoot = (
323
+ env: NodeJS.ProcessEnv = process.env,
324
+ options: Omit<AndroidSdkRootOptions, 'env'> = {}
325
+ ): string | null => {
326
+ return (
327
+ getConfiguredAndroidSdkRoot(env) ?? getDefaultUnixAndroidSdkRoot(options)
328
+ );
329
+ };
330
+
331
+ const getRequiredAndroidSdkRoot = (
332
+ env: NodeJS.ProcessEnv = process.env,
333
+ options: Omit<AndroidSdkRootOptions, 'env'> = {}
334
+ ): string => {
335
+ const sdkRoot = getAndroidSdkRoot(env, options);
336
+
337
+ if (!sdkRoot) {
338
+ throw new Error(
339
+ 'Android SDK root is not configured. Set ANDROID_HOME or ANDROID_SDK_ROOT.'
340
+ );
341
+ }
342
+
343
+ return sdkRoot;
344
+ };
345
+
346
+ export const getHostAndroidSystemImageArch = (
347
+ architecture: string = process.arch
348
+ ): AndroidSystemImageArch => {
349
+ switch (architecture) {
350
+ case 'arm64':
351
+ return 'arm64-v8a';
352
+ case 'arm':
353
+ return 'armeabi-v7a';
354
+ case 'x64':
355
+ default:
356
+ return 'x86_64';
357
+ }
358
+ };
359
+
360
+ export const getAndroidPlatformPackage = (apiLevel: number): string => {
361
+ return `platforms;android-${apiLevel}`;
362
+ };
363
+
364
+ export const getAndroidSystemImagePackage = (
365
+ apiLevel: number,
366
+ architecture: AndroidSystemImageArch = getHostAndroidSystemImageArch()
367
+ ): string => {
368
+ return `system-images;android-${apiLevel};default;${architecture}`;
369
+ };
370
+
371
+ export const getRequiredAndroidSdkPackages = ({
372
+ apiLevel,
373
+ includeEmulator = false,
374
+ architecture = getHostAndroidSystemImageArch(),
375
+ }: {
376
+ apiLevel?: number;
377
+ includeEmulator?: boolean;
378
+ architecture?: AndroidSystemImageArch;
379
+ } = {}): string[] => {
380
+ const packages = ['platform-tools'];
381
+
382
+ if (!includeEmulator) {
383
+ return packages;
384
+ }
385
+
386
+ packages.push('emulator');
387
+
388
+ if (typeof apiLevel === 'number') {
389
+ packages.push(getAndroidPlatformPackage(apiLevel));
390
+ packages.push(getAndroidSystemImagePackage(apiLevel, architecture));
391
+ }
392
+
393
+ return packages;
394
+ };
395
+
396
+ export const ensureAndroidSdkPackages = async (
397
+ packages: readonly string[],
398
+ {
399
+ env = process.env,
400
+ platform = process.platform,
401
+ homeDirectory = os.homedir(),
402
+ }: AndroidSdkRootOptions = {}
403
+ ): Promise<string> => {
404
+ const sdkRoot = getRequiredAndroidSdkRoot(env, { platform, homeDirectory });
405
+
406
+ await mkdir(sdkRoot, { recursive: true });
407
+ await ensureAndroidCommandLineTools(sdkRoot, platform);
408
+
409
+ const missingPackages = await getMissingAndroidSdkPackages(sdkRoot, packages);
410
+
411
+ if (missingPackages.length > 0) {
412
+ await installAndroidSdkPackages(sdkRoot, missingPackages);
413
+ }
414
+
415
+ const unresolvedPackages = await getMissingAndroidSdkPackages(
416
+ sdkRoot,
417
+ packages
418
+ );
419
+
420
+ if (unresolvedPackages.length > 0) {
421
+ throw new Error(
422
+ `Android SDK packages are still missing after installation: ${unresolvedPackages.join(
423
+ ', '
424
+ )}`
425
+ );
426
+ }
427
+
428
+ return sdkRoot;
429
+ };
430
+
431
+ export const ensureAndroidDiscoveryEnvironment = async (): Promise<string> => {
432
+ initializeAndroidProcessEnv();
433
+
434
+ return ensureAndroidSdkPackages(
435
+ getRequiredAndroidSdkPackages({ includeEmulator: true })
436
+ );
437
+ };
438
+
439
+ export const ensureAndroidPhysicalDeviceEnvironment =
440
+ async (): Promise<string> => {
441
+ initializeAndroidProcessEnv();
442
+
443
+ return ensureAndroidSdkPackages(getRequiredAndroidSdkPackages());
444
+ };
445
+
446
+ export const ensureAndroidEmulatorEnvironment = async (
447
+ apiLevel: number
448
+ ): Promise<string> => {
449
+ initializeAndroidProcessEnv();
450
+
451
+ return ensureAndroidSdkPackages(
452
+ getRequiredAndroidSdkPackages({
453
+ apiLevel,
454
+ includeEmulator: true,
455
+ })
456
+ );
457
+ };
458
+
459
+ export const getAndroidProcessEnv = (
460
+ env: NodeJS.ProcessEnv = process.env
461
+ ): NodeJS.ProcessEnv => {
462
+ const sdkRoot = getAndroidSdkRoot(env);
463
+
464
+ if (!sdkRoot) {
465
+ return env;
466
+ }
467
+
468
+ const platformToolsPath = path.join(sdkRoot, 'platform-tools');
469
+ const emulatorPath = path.join(sdkRoot, 'emulator');
470
+ const cmdlineToolsPath = path.join(sdkRoot, ...CMDLINE_TOOLS_PATH_SEGMENTS);
471
+ const cmdlineToolsBinPath = path.join(cmdlineToolsPath, 'bin');
472
+ const currentPath = env.PATH ?? '';
473
+ const pathEntries = [
474
+ platformToolsPath,
475
+ emulatorPath,
476
+ cmdlineToolsPath,
477
+ cmdlineToolsBinPath,
478
+ currentPath,
479
+ ].filter((entry) => entry !== '');
480
+
481
+ return {
482
+ ...env,
483
+ ANDROID_HOME: sdkRoot,
484
+ ANDROID_SDK_ROOT: sdkRoot,
485
+ ANDROID_AVD_HOME: path.join(os.homedir(), '.android', 'avd'),
486
+ PATH: pathEntries.join(path.delimiter),
487
+ };
488
+ };
489
+
490
+ export const initializeAndroidProcessEnv = (): void => {
491
+ Object.assign(process.env, getAndroidProcessEnv());
492
+ };
493
+
494
+ export const getAdbBinaryPath = (
495
+ sdkRoot: string = getRequiredAndroidSdkRoot()
496
+ ): string => path.join(sdkRoot, 'platform-tools', 'adb');
497
+
498
+ export const getEmulatorBinaryPath = (
499
+ sdkRoot: string = getRequiredAndroidSdkRoot()
500
+ ): string => path.join(sdkRoot, 'emulator', 'emulator');
501
+
502
+ export const getSdkManagerBinaryPath = (
503
+ sdkRoot: string = getRequiredAndroidSdkRoot()
504
+ ): string =>
505
+ path.join(sdkRoot, ...CMDLINE_TOOLS_PATH_SEGMENTS, 'bin', 'sdkmanager');
506
+
507
+ export const getAvdManagerBinaryPath = (
508
+ sdkRoot: string = getRequiredAndroidSdkRoot()
509
+ ): string =>
510
+ path.join(sdkRoot, ...CMDLINE_TOOLS_PATH_SEGMENTS, 'bin', 'avdmanager');
package/src/errors.ts ADDED
@@ -0,0 +1,19 @@
1
+ export class HarnessAppPathError extends Error {
2
+ constructor(reason: 'missing' | 'invalid', appPath?: string) {
3
+ super(
4
+ reason === 'missing'
5
+ ? 'App is not installed on the emulator and HARNESS_APP_PATH is not set.'
6
+ : `HARNESS_APP_PATH points to a missing APK: ${appPath ?? '<unknown>'}`
7
+ );
8
+ this.name = 'HarnessAppPathError';
9
+ }
10
+ }
11
+
12
+ export class HarnessEmulatorConfigError extends Error {
13
+ constructor(deviceName: string) {
14
+ super(
15
+ `Android emulator "${deviceName}" is not running and no AVD config was provided. Add the "avd" property to this runner config so Harness can create and boot the emulator.`
16
+ );
17
+ this.name = 'HarnessEmulatorConfigError';
18
+ }
19
+ }
package/src/factory.ts CHANGED
@@ -31,4 +31,8 @@ export const androidPlatform = (
31
31
  config,
32
32
  runner: import.meta.resolve('./runner.js'),
33
33
  platformId: 'android',
34
+ getResourceLockKey: () =>
35
+ config.device.type === 'emulator'
36
+ ? `android:emulator:${config.device.name}`
37
+ : `android:physical:${config.device.manufacturer}:${config.device.model}`,
34
38
  });
package/src/index.ts CHANGED
@@ -4,4 +4,10 @@ export {
4
4
  androidPlatform,
5
5
  } from './factory.js';
6
6
  export type { AndroidPlatformConfig } from './config.js';
7
- export { getRunTargets } from './targets.js';
7
+ export {
8
+ getNormalizedAvdCacheConfig,
9
+ resolveAvdCachingEnabled,
10
+ } from './avd-config.js';
11
+ export { getHostAndroidSystemImageArch } from './environment.js';
12
+ export { HarnessAppPathError, HarnessEmulatorConfigError } from './errors.js';
13
+ export { getRunTargets } from './targets.js';