@react-native-harness/platform-android 1.6.0 → 1.7.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/dist/adb.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"adb.d.ts","sourceRoot":"","sources":["../src/adb.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAC/E,OAAO,EAOL,KAAK,YAAY,EAClB,MAAM,6BAA6B,CAAC;AAErC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAgB5C,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,uBAAuB,CAAC;AAgC/B,eAAO,MAAM,eAAe;iCAElB,MAAM,QACN,SAAS,MAAM,EAAE,KACtB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC;CAKjD,CAAC;AAqEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,eAAO,MAAM,2BAA2B,GAAI,UAAU,MAAM,KAAG,MAAM,EAMpE,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,UAAU,MAAM,KACf,OAAO,CAAC,IAAI,CAEd,CAAC;AAgEF,eAAO,MAAM,eAAe,GAC1B,UAAU,MAAM,EAChB,cAAc,MAAM,EACpB,UAAU,uBAAuB,KAChC,MAAM,EAoCR,CAAC;AAEF,eAAO,MAAM,cAAc,GACzB,OAAO,MAAM,EACb,UAAU,MAAM,KACf,OAAO,CAAC,OAAO,CAWjB,CAAC;AAEF,eAAO,MAAM,WAAW,GACtB,OAAO,MAAM,EACb,MAAM,MAAM,EACZ,WAAU,MAAa,KACtB,OAAO,CAAC,IAAI,CAQd,CAAC;AAEF,eAAO,MAAM,OAAO,GAClB,OAAO,MAAM,EACb,UAAU,MAAM,KACf,OAAO,CAAC,IAAI,CASd,CAAC;AAEF,eAAO,MAAM,QAAQ,GACnB,OAAO,MAAM,EACb,UAAU,MAAM,EAChB,cAAc,MAAM,EACpB,UAAU,uBAAuB,KAChC,OAAO,CAAC,IAAI,CAMd,CAAC;AAEF,eAAO,MAAM,YAAY,QAAa,OAAO,CAAC,MAAM,EAAE,CAOrD,CAAC;AAEF,eAAO,MAAM,eAAe,GAC1B,OAAO,MAAM,KACZ,OAAO,CAAC,MAAM,GAAG,IAAI,CASvB,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAC3B,OAAO,MAAM,EACb,UAAU,MAAM,KACf,OAAO,CAAC,MAAM,GAAG,IAAI,CASvB,CAAC;AAMF,MAAM,MAAM,UAAU,GAAG;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AAEF,eAAO,MAAM,aAAa,GACxB,OAAO,MAAM,KACZ,OAAO,CAAC,UAAU,GAAG,IAAI,CAI3B,CAAC;AA2EF,eAAO,MAAM,eAAe,GAAU,OAAO,MAAM,KAAG,OAAO,CAAC,OAAO,CAWpE,CAAC;AAEF,eAAO,MAAM,YAAY,GAAU,OAAO,MAAM,KAAG,OAAO,CAAC,IAAI,CAE9D,CAAC;AAEF,eAAO,MAAM,UAAU,GACrB,OAAO,MAAM,EACb,SAAS,MAAM,KACd,OAAO,CAAC,IAAI,CAEd,CAAC;AAEF,eAAO,MAAM,YAAY,GACvB,OAAO,MAAM,EACb,UAAU,MAAM,KACf,OAAO,CAAC,IAAI,CAEd,CAAC;AAEF,eAAO,MAAM,MAAM,GAAU,MAAM,MAAM,KAAG,OAAO,CAAC,OAAO,CAG1D,CAAC;AAEF,eAAO,MAAM,SAAS,GAAU,kDAM7B,gBAAgB,KAAG,OAAO,CAAC,IAAI,CAyBjC,CAAC;AAEF,eAAO,MAAM,SAAS,GAAU,MAAM,MAAM,KAAG,OAAO,CAAC,IAAI,CAkB1D,CAAC;AAEF,eAAO,MAAM,aAAa,GACxB,MAAM,MAAM,EACZ,OAAM,gBAAiC,KACtC,OAAO,CAAC,IAAI,CAsFd,CAAC;AAEF,eAAO,MAAM,eAAe,GAC1B,MAAM,MAAM,EACZ,QAAQ,WAAW,KAClB,OAAO,CAAC,MAAM,CAoBhB,CAAC;AAEF,eAAO,MAAM,yBAAyB,GACpC,OAAO,MAAM,EACb,QAAQ,WAAW,KAClB,OAAO,CAAC,IAAI,CAYd,CAAC;AAEF,eAAO,MAAM,WAAW,GACtB,MAAM,MAAM,EACZ,QAAQ,WAAW,KAClB,OAAO,CAAC,MAAM,CAwBhB,CAAC;AAEF,eAAO,MAAM,YAAY,GACvB,OAAO,MAAM,EACb,UAAU,MAAM,KACf,OAAO,CAAC,OAAO,CAEjB,CAAC;AAEF,eAAO,MAAM,SAAS,GACpB,OAAO,MAAM,EACb,UAAU,MAAM,KACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAsBvB,CAAC;AAEF,eAAO,MAAM,SAAS,GACpB,OAAO,MAAM,EACb,UAAU,MAAM,KACf,OAAO,CAAC,MAAM,CAoBhB,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC9B,OAAO,MAAM,EACb,MAAM,OAAO,KACZ,OAAO,CAAC,IAAI,CAWd,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAU,OAAO,MAAM,KAAG,OAAO,CAAC,MAAM,CAUtE,CAAC;AAEF,eAAO,MAAM,WAAW,GACtB,OAAO,MAAM,EACb,MAAM,SAAS,MAAM,EAAE,KACtB,YAIC,CAAC;AAEL,eAAO,MAAM,kBAAkB,sDAGrB,CAAC;AAEX,eAAO,MAAM,eAAe,GAC1B,OAAO,MAAM,EACb,OAAM,SAAS,MAAM,EAAuB,KAC3C,OAAO,CAAC,MAAM,CAoBhB,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC9B,OAAO,MAAM,EACb,UAAU,MAAM,KACf,OAAO,CAAC,MAAM,CAYhB,CAAC;AAEF,eAAO,MAAM,OAAO,QAAa,OAAO,CAAC,MAAM,EAAE,CAWhD,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,eAAO,MAAM,mBAAmB,QAAa,OAAO,CAAC,SAAS,EAAE,CA8B/D,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAC3B,OAAO,MAAM,EACb,UAAU,MAAM,KACf,OAAO,CAAC,IAAI,CAqEd,CAAC"}
1
+ {"version":3,"file":"adb.d.ts","sourceRoot":"","sources":["../src/adb.ts"],"names":[],"mappings":"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"}
package/dist/adb.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { delay, logger, spawn, spawnOwnedProcess, SubprocessError, waitForAbort, } from '@react-native-harness/tools';
2
2
  import { spawn as nodeSpawn } from 'node:child_process';
3
- import { access, rm } from 'node:fs/promises';
4
- import { getAvdConfigPath, getAvdDirectory, readAvdConfig, } from './avd-config.js';
3
+ import { access, appendFile, rm, writeFile } from 'node:fs/promises';
4
+ import { getAvdConfigPath, getAvdDirectory, getAvdIniPath, readAvdConfig, } from './avd-config.js';
5
5
  import { ensureAndroidEmulatorAvailable, ensureAndroidSdkPackages, getAdbBinaryPath, getAndroidSystemImagePackage, getAvdManagerBinaryPath, getEmulatorBinaryPath, getHostAndroidSystemImageArch, getRequiredAndroidSdkPackages, } from './environment.js';
6
6
  import { getEmulatorCpuCores, getEmulatorStartupArgs, } from './emulator-startup.js';
7
7
  import { AdbAppNotInstalledError, AdbPermissionGrantError, } from './adb-errors.js';
@@ -102,17 +102,16 @@ const ensureAvdConfigExists = async (name) => {
102
102
  }
103
103
  return configPath;
104
104
  };
105
- const getAvdIniPath = (name) => {
106
- const avdHome = process.env.ANDROID_AVD_HOME ?? `${process.env.HOME}/.android/avd`;
107
- return `${avdHome}/${name}.ini`;
108
- };
109
105
  const ensureAvdIniExists = async ({ name, apiLevel, }) => {
110
106
  const iniPath = getAvdIniPath(name);
111
107
  const avdDirectory = getAvdDirectory(name);
112
- await spawn('bash', [
113
- '-lc',
114
- `printf '%s\n%s\n%s\n%s\n' 'avd.ini.encoding=UTF-8' 'path=${avdDirectory}' 'path.rel=avd/${name}.avd' 'target=android-${apiLevel}' > "${iniPath}"`,
115
- ]);
108
+ await writeFile(iniPath, [
109
+ 'avd.ini.encoding=UTF-8',
110
+ `path=${avdDirectory}`,
111
+ `path.rel=avd/${name}.avd`,
112
+ `target=android-${apiLevel}`,
113
+ '',
114
+ ].join('\n'));
116
115
  return iniPath;
117
116
  };
118
117
  export const getStartAppArgs = (bundleId, activityName, options) => {
@@ -302,10 +301,20 @@ export const createAvd = async ({ name, apiLevel, profile, diskSize, heapSize, }
302
301
  const systemImagePackage = getAndroidSystemImagePackage(apiLevel, getHostAndroidSystemImageArch());
303
302
  await verifyAndroidEmulatorSdk(apiLevel);
304
303
  await ensureAvdProfileAvailable(profile);
305
- await spawn('bash', [
306
- '-lc',
307
- `printf 'no\n' | "${getAvdManagerBinaryPath()}" create avd --force --name "${name}" --package "${systemImagePackage}" --device "${profile}" -p "${getAvdDirectory(name)}"`,
308
- ]);
304
+ // avdmanager asks whether to create a custom hardware profile; answer "no".
305
+ await spawn(getAvdManagerBinaryPath(), [
306
+ 'create',
307
+ 'avd',
308
+ '--force',
309
+ '--name',
310
+ name,
311
+ '--package',
312
+ systemImagePackage,
313
+ '--device',
314
+ profile,
315
+ '-p',
316
+ getAvdDirectory(name),
317
+ ], { stdin: { string: 'no\n' } });
309
318
  await ensureAvdIniExists({ name, apiLevel });
310
319
  const configPath = await ensureAvdConfigExists(name);
311
320
  // hw.cpu.ncore is baked into config.ini (rather than passed as a `-cores`
@@ -313,17 +322,19 @@ export const createAvd = async ({ name, apiLevel, profile, diskSize, heapSize, }
313
322
  // snapshots require an identical hardware config to load, so the vCPU
314
323
  // count must be fixed at AVD-creation time and stay consistent across
315
324
  // boots.
316
- await spawn('bash', [
317
- '-lc',
318
- `printf '%s\n%s\n%s\n' 'disk.dataPartition.size=${diskSize}' 'vm.heapSize=${heapSize}' 'hw.cpu.ncore=${getEmulatorCpuCores()}' >> "${configPath}"`,
319
- ]);
325
+ await appendFile(configPath, [
326
+ `disk.dataPartition.size=${diskSize}`,
327
+ `vm.heapSize=${heapSize}`,
328
+ `hw.cpu.ncore=${getEmulatorCpuCores()}`,
329
+ '',
330
+ ].join('\n'));
320
331
  };
321
332
  export const deleteAvd = async (name) => {
322
- await rm(`${process.env.ANDROID_AVD_HOME ?? `${process.env.HOME}/.android/avd`}/${name}.avd`, {
333
+ await rm(getAvdDirectory(name), {
323
334
  force: true,
324
335
  recursive: true,
325
336
  });
326
- await rm(`${process.env.ANDROID_AVD_HOME ?? `${process.env.HOME}/.android/avd`}/${name}.ini`, {
337
+ await rm(getAvdIniPath(name), {
327
338
  force: true,
328
339
  });
329
340
  };
@@ -14,7 +14,9 @@ export type AvdCompatibilityResult = {
14
14
  compatible: false;
15
15
  reason: string;
16
16
  };
17
+ export declare const getAvdHome: () => string;
17
18
  export declare const getAvdDirectory: (name: string) => string;
19
+ export declare const getAvdIniPath: (name: string) => string;
18
20
  export declare const getAvdConfigPath: (name: string) => string;
19
21
  export declare const parseAvdConfig: (contents: string) => AvdConfig;
20
22
  export declare const readAvdConfig: (name: string) => Promise<AvdConfig | null>;
@@ -1 +1 @@
1
- {"version":3,"file":"avd-config.d.ts","sourceRoot":"","sources":["../src/avd-config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAG7E,MAAM,MAAM,SAAS,GAAG;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAC9B;IAAE,UAAU,EAAE,IAAI,CAAA;CAAE,GACpB;IAAE,UAAU,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1C,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,KAAG,MAI9C,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,KAAG,MAE/C,CAAC;AAkEF,eAAO,MAAM,cAAc,GAAI,UAAU,MAAM,KAAG,SA4CjD,CAAC;AAEF,eAAO,MAAM,aAAa,GACxB,MAAM,MAAM,KACX,OAAO,CAAC,SAAS,GAAG,IAAI,CAU1B,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,oCAI7B;IACD,QAAQ,EAAE,eAAe,CAAC;IAC1B,SAAS,EAAE,SAAS,CAAC;IACrB,QAAQ,EAAE,sBAAsB,CAAC;CAClC,KAAG,sBA+FH,CAAC;AAEF,eAAO,MAAM,2BAA2B,GAAI,yBAGzC;IACD,QAAQ,EAAE,eAAe,CAAC;IAC1B,QAAQ,EAAE,sBAAsB,CAAC;CAClC,KAAG;IACF,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,sBAAsB,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,IAeH,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAI,8BAItC;IACD,GAAG,CAAC,EAAE,wBAAwB,CAAC;IAC/B,aAAa,EAAE,OAAO,CAAC;IACvB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB,KAAG,OAWH,CAAC"}
1
+ {"version":3,"file":"avd-config.d.ts","sourceRoot":"","sources":["../src/avd-config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAG7E,MAAM,MAAM,SAAS,GAAG;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAC9B;IAAE,UAAU,EAAE,IAAI,CAAA;CAAE,GACpB;IAAE,UAAU,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1C,eAAO,MAAM,UAAU,QAAO,MAI7B,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,KAAG,MAE9C,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,MAE5C,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,KAAG,MAE/C,CAAC;AAkEF,eAAO,MAAM,cAAc,GAAI,UAAU,MAAM,KAAG,SA4CjD,CAAC;AAEF,eAAO,MAAM,aAAa,GACxB,MAAM,MAAM,KACX,OAAO,CAAC,SAAS,GAAG,IAAI,CAU1B,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,oCAI7B;IACD,QAAQ,EAAE,eAAe,CAAC;IAC1B,SAAS,EAAE,SAAS,CAAC;IACrB,QAAQ,EAAE,sBAAsB,CAAC;CAClC,KAAG,sBA+FH,CAAC;AAEF,eAAO,MAAM,2BAA2B,GAAI,yBAGzC;IACD,QAAQ,EAAE,eAAe,CAAC;IAC1B,QAAQ,EAAE,sBAAsB,CAAC;CAClC,KAAG;IACF,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,sBAAsB,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,IAeH,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAI,8BAItC;IACD,GAAG,CAAC,EAAE,wBAAwB,CAAC;IAC/B,aAAa,EAAE,OAAO,CAAC;IACvB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB,KAAG,OAWH,CAAC"}
@@ -1,10 +1,18 @@
1
1
  import { access, readFile } from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
2
4
  import { getEmulatorCpuCores } from './emulator-startup.js';
5
+ export const getAvdHome = () => {
6
+ return (process.env.ANDROID_AVD_HOME ?? path.join(os.homedir(), '.android', 'avd'));
7
+ };
3
8
  export const getAvdDirectory = (name) => {
4
- return `${process.env.ANDROID_AVD_HOME ?? `${process.env.HOME}/.android/avd`}/${name}.avd`;
9
+ return path.join(getAvdHome(), `${name}.avd`);
10
+ };
11
+ export const getAvdIniPath = (name) => {
12
+ return path.join(getAvdHome(), `${name}.ini`);
5
13
  };
6
14
  export const getAvdConfigPath = (name) => {
7
- return `${getAvdDirectory(name)}/config.ini`;
15
+ return path.join(getAvdDirectory(name), 'config.ini');
8
16
  };
9
17
  const normalizeAvdValue = (value) => {
10
18
  if (!value) {
package/dist/config.d.ts CHANGED
@@ -68,8 +68,8 @@ export declare const AndroidEmulatorSchema: z.ZodObject<{
68
68
  } | undefined;
69
69
  }>>;
70
70
  }, "strip", z.ZodTypeAny, {
71
- name: string;
72
71
  type: "emulator";
72
+ name: string;
73
73
  avd?: {
74
74
  apiLevel: number;
75
75
  profile: string;
@@ -80,8 +80,8 @@ export declare const AndroidEmulatorSchema: z.ZodObject<{
80
80
  } | undefined;
81
81
  } | undefined;
82
82
  }, {
83
- name: string;
84
83
  type: "emulator";
84
+ name: string;
85
85
  avd?: {
86
86
  apiLevel: number;
87
87
  profile: string;
@@ -138,8 +138,8 @@ export declare const AndroidDeviceSchema: z.ZodDiscriminatedUnion<"type", [z.Zod
138
138
  } | undefined;
139
139
  }>>;
140
140
  }, "strip", z.ZodTypeAny, {
141
- name: string;
142
141
  type: "emulator";
142
+ name: string;
143
143
  avd?: {
144
144
  apiLevel: number;
145
145
  profile: string;
@@ -150,8 +150,8 @@ export declare const AndroidDeviceSchema: z.ZodDiscriminatedUnion<"type", [z.Zod
150
150
  } | undefined;
151
151
  } | undefined;
152
152
  }, {
153
- name: string;
154
153
  type: "emulator";
154
+ name: string;
155
155
  avd?: {
156
156
  apiLevel: number;
157
157
  profile: string;
@@ -209,8 +209,8 @@ export declare const AndroidPlatformConfigSchema: z.ZodObject<{
209
209
  } | undefined;
210
210
  }>>;
211
211
  }, "strip", z.ZodTypeAny, {
212
- name: string;
213
212
  type: "emulator";
213
+ name: string;
214
214
  avd?: {
215
215
  apiLevel: number;
216
216
  profile: string;
@@ -221,8 +221,8 @@ export declare const AndroidPlatformConfigSchema: z.ZodObject<{
221
221
  } | undefined;
222
222
  } | undefined;
223
223
  }, {
224
- name: string;
225
224
  type: "emulator";
225
+ name: string;
226
226
  avd?: {
227
227
  apiLevel: number;
228
228
  profile: string;
@@ -255,11 +255,10 @@ export declare const AndroidPlatformConfigSchema: z.ZodObject<{
255
255
  extras?: Record<string, string | number | boolean> | undefined;
256
256
  }>>;
257
257
  }, "strip", z.ZodTypeAny, {
258
- bundleId: string;
259
258
  name: string;
260
259
  device: {
261
- name: string;
262
260
  type: "emulator";
261
+ name: string;
263
262
  avd?: {
264
263
  apiLevel: number;
265
264
  profile: string;
@@ -274,16 +273,16 @@ export declare const AndroidPlatformConfigSchema: z.ZodObject<{
274
273
  manufacturer: string;
275
274
  model: string;
276
275
  };
276
+ bundleId: string;
277
277
  activityName: string;
278
278
  appLaunchOptions?: {
279
279
  extras?: Record<string, string | number | boolean> | undefined;
280
280
  } | undefined;
281
281
  }, {
282
- bundleId: string;
283
282
  name: string;
284
283
  device: {
285
- name: string;
286
284
  type: "emulator";
285
+ name: string;
287
286
  avd?: {
288
287
  apiLevel: number;
289
288
  profile: string;
@@ -298,6 +297,7 @@ export declare const AndroidPlatformConfigSchema: z.ZodObject<{
298
297
  manufacturer: string;
299
298
  model: string;
300
299
  };
300
+ bundleId: string;
301
301
  activityName?: string | undefined;
302
302
  appLaunchOptions?: {
303
303
  extras?: Record<string, string | number | boolean> | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"environment.d.ts","sourceRoot":"","sources":["../src/environment.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,sBAAsB,GAAG,QAAQ,GAAG,WAAW,GAAG,aAAa,CAAC;AAE5E,KAAK,qBAAqB,GAAG;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAQF,eAAO,MAAM,4BAA4B,GAAI,+BAG1C,IAAI,CAAC,qBAAqB,EAAE,KAAK,CAAM,KAAG,MAAM,GAAG,IAUrD,CAAC;AAwRF,eAAO,MAAM,iBAAiB,GAC5B,MAAK,MAAM,CAAC,UAAwB,EACpC,UAAS,IAAI,CAAC,qBAAqB,EAAE,KAAK,CAAM,KAC/C,MAAM,GAAG,IAIX,CAAC;AAiBF,eAAO,MAAM,6BAA6B,GACxC,eAAc,MAAqB,KAClC,sBAUF,CAAC;AAEF,eAAO,MAAM,yBAAyB,GAAI,UAAU,MAAM,KAAG,MAE5D,CAAC;AAEF,eAAO,MAAM,4BAA4B,GACvC,UAAU,MAAM,EAChB,eAAc,sBAAwD,KACrE,MAEF,CAAC;AAEF,eAAO,MAAM,6BAA6B,GAAI,+CAI3C;IACD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,YAAY,CAAC,EAAE,sBAAsB,CAAC;CAClC,KAAG,MAAM,EAed,CAAC;AAoBF,eAAO,MAAM,wBAAwB,GACnC,UAAU,SAAS,MAAM,EAAE,EAC3B,oCAIG,qBAA0B,KAC5B,OAAO,CAAC,MAAM,CA8BhB,CAAC;AAEF,eAAO,MAAM,yBAAyB,GACpC,UAAS,qBAA0B,KAClC,OAAO,CAAC,MAAM,CAEhB,CAAC;AAEF,eAAO,MAAM,8BAA8B,GACzC,UAAS,qBAA0B,KAClC,OAAO,CAAC,MAAM,CAEhB,CAAC;AAEF,eAAO,MAAM,qCAAqC,GAChD,UAAU,MAAM,EAChB,eAAc,sBAAwD,EACtE,UAAS,qBAA0B,KAClC,OAAO,CAAC,MAAM,CAQhB,CAAC;AAEF,eAAO,MAAM,iCAAiC,QAAa,OAAO,CAAC,MAAM,CAIxE,CAAC;AAEF,eAAO,MAAM,sCAAsC,QACvC,OAAO,CAAC,MAAM,CAIvB,CAAC;AAEJ,eAAO,MAAM,gCAAgC,GAC3C,UAAU,MAAM,KACf,OAAO,CAAC,MAAM,CAOhB,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,MAAK,MAAM,CAAC,UAAwB,KACnC,MAAM,CAAC,UA2BT,CAAC;AAEF,eAAO,MAAM,2BAA2B,QAAO,IAE9C,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAC3B,UAAS,MAAoC,KAC5C,MAAqD,CAAC;AAEzD,eAAO,MAAM,qBAAqB,GAChC,UAAS,MAAoC,KAC5C,MAAoD,CAAC;AAExD,eAAO,MAAM,uBAAuB,GAClC,UAAS,MAAoC,KAC5C,MACsE,CAAC;AAE1E,eAAO,MAAM,uBAAuB,GAClC,UAAS,MAAoC,KAC5C,MACsE,CAAC"}
1
+ {"version":3,"file":"environment.d.ts","sourceRoot":"","sources":["../src/environment.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,sBAAsB,GAAG,QAAQ,GAAG,WAAW,GAAG,aAAa,CAAC;AAE5E,KAAK,qBAAqB,GAAG;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAQF,eAAO,MAAM,4BAA4B,GAAI,+BAG1C,IAAI,CAAC,qBAAqB,EAAE,KAAK,CAAM,KAAG,MAAM,GAAG,IAkBrD,CAAC;AAqQF,eAAO,MAAM,iBAAiB,GAC5B,MAAK,MAAM,CAAC,UAAwB,EACpC,UAAS,IAAI,CAAC,qBAAqB,EAAE,KAAK,CAAM,KAC/C,MAAM,GAAG,IAIX,CAAC;AAiBF,eAAO,MAAM,6BAA6B,GACxC,eAAc,MAAqB,KAClC,sBAUF,CAAC;AAEF,eAAO,MAAM,yBAAyB,GAAI,UAAU,MAAM,KAAG,MAE5D,CAAC;AAEF,eAAO,MAAM,4BAA4B,GACvC,UAAU,MAAM,EAChB,eAAc,sBAAwD,KACrE,MAEF,CAAC;AAEF,eAAO,MAAM,6BAA6B,GAAI,+CAI3C;IACD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,YAAY,CAAC,EAAE,sBAAsB,CAAC;CAClC,KAAG,MAAM,EAed,CAAC;AAoBF,eAAO,MAAM,wBAAwB,GACnC,UAAU,SAAS,MAAM,EAAE,EAC3B,oCAIG,qBAA0B,KAC5B,OAAO,CAAC,MAAM,CA8BhB,CAAC;AAEF,eAAO,MAAM,yBAAyB,GACpC,UAAS,qBAA0B,KAClC,OAAO,CAAC,MAAM,CAEhB,CAAC;AAEF,eAAO,MAAM,8BAA8B,GACzC,UAAS,qBAA0B,KAClC,OAAO,CAAC,MAAM,CAEhB,CAAC;AAEF,eAAO,MAAM,qCAAqC,GAChD,UAAU,MAAM,EAChB,eAAc,sBAAwD,EACtE,UAAS,qBAA0B,KAClC,OAAO,CAAC,MAAM,CAQhB,CAAC;AAEF,eAAO,MAAM,iCAAiC,QAAa,OAAO,CAAC,MAAM,CAIxE,CAAC;AAEF,eAAO,MAAM,sCAAsC,QACvC,OAAO,CAAC,MAAM,CAIvB,CAAC;AAEJ,eAAO,MAAM,gCAAgC,GAC3C,UAAU,MAAM,KACf,OAAO,CAAC,MAAM,CAOhB,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,MAAK,MAAM,CAAC,UAAwB,KACnC,MAAM,CAAC,UA4BT,CAAC;AAEF,eAAO,MAAM,2BAA2B,QAAO,IAE9C,CAAC;AAUF,eAAO,MAAM,gBAAgB,GAC3B,UAAS,MAAoC,KAC5C,MACwE,CAAC;AAE5E,eAAO,MAAM,qBAAqB,GAChC,UAAS,MAAoC,KAC5C,MACuE,CAAC;AAE3E,eAAO,MAAM,uBAAuB,GAClC,UAAS,MAAoC,KAC5C,MAMA,CAAC;AAEJ,eAAO,MAAM,uBAAuB,GAClC,UAAS,MAAoC,KAC5C,MAMA,CAAC"}
@@ -19,6 +19,9 @@ export const getDefaultUnixAndroidSdkRoot = ({ platform = process.platform, home
19
19
  if (platform === 'linux') {
20
20
  return path.join(homeDirectory, 'Android', 'Sdk');
21
21
  }
22
+ if (platform === 'win32') {
23
+ return path.join(process.env.LOCALAPPDATA ?? path.join(homeDirectory, 'AppData', 'Local'), 'Android', 'Sdk');
24
+ }
22
25
  return null;
23
26
  };
24
27
  const canBootstrapAndroidSdk = (platform = process.platform) => {
@@ -33,9 +36,8 @@ const pathExists = async (filePath) => {
33
36
  return false;
34
37
  }
35
38
  };
36
- const quoteShell = (value) => {
37
- return `'${value.replace(/'/g, `'\\''`)}'`;
38
- };
39
+ // Enough "y" answers for every license prompt sdkmanager may show.
40
+ const SDK_MANAGER_CONSENT_ANSWERS = 100;
39
41
  const downloadText = async (url) => {
40
42
  return new Promise((resolve, reject) => {
41
43
  const request = https.get(url, (response) => {
@@ -131,12 +133,10 @@ const ensureAndroidCommandLineTools = async (sdkRoot, platform = process.platfor
131
133
  await rm(temporaryDirectory, { force: true, recursive: true });
132
134
  }
133
135
  };
134
- const acceptAndroidLicenses = async (sdkRoot) => {
135
- const sdkManagerBinaryPath = getSdkManagerBinaryPath(sdkRoot);
136
- await spawn('bash', [
137
- '-lc',
138
- `yes | ${quoteShell(sdkManagerBinaryPath)} --sdk_root=${quoteShell(sdkRoot)} --licenses >/dev/null`,
139
- ], {
136
+ // Answers "y" to every prompt, like piping `yes` into sdkmanager.
137
+ const runSdkManagerWithConsent = async (sdkRoot, args) => {
138
+ await spawn(getSdkManagerBinaryPath(sdkRoot), [`--sdk_root=${sdkRoot}`, ...args], {
139
+ stdin: { string: 'y\n'.repeat(SDK_MANAGER_CONSENT_ANSWERS) },
140
140
  env: getAndroidProcessEnv({
141
141
  ...process.env,
142
142
  ANDROID_HOME: sdkRoot,
@@ -144,6 +144,9 @@ const acceptAndroidLicenses = async (sdkRoot) => {
144
144
  }),
145
145
  });
146
146
  };
147
+ const acceptAndroidLicenses = async (sdkRoot) => {
148
+ await runSdkManagerWithConsent(sdkRoot, ['--licenses']);
149
+ };
147
150
  const getPackageVerificationPath = (sdkRoot, packageName) => {
148
151
  if (packageName === 'platform-tools') {
149
152
  return getAdbBinaryPath(sdkRoot);
@@ -176,22 +179,9 @@ const installAndroidSdkPackages = async (sdkRoot, packages) => {
176
179
  if (packages.length === 0) {
177
180
  return;
178
181
  }
179
- const sdkManagerBinaryPath = getSdkManagerBinaryPath(sdkRoot);
180
- const packageArgs = packages
181
- .map((packageName) => quoteShell(packageName))
182
- .join(' ');
183
182
  androidEnvironmentLogger.info('Installing missing Android SDK packages: %s', packages.join(', '));
184
183
  await acceptAndroidLicenses(sdkRoot);
185
- await spawn('bash', [
186
- '-lc',
187
- `yes | ${quoteShell(sdkManagerBinaryPath)} --sdk_root=${quoteShell(sdkRoot)} ${packageArgs}`,
188
- ], {
189
- env: getAndroidProcessEnv({
190
- ...process.env,
191
- ANDROID_HOME: sdkRoot,
192
- ANDROID_SDK_ROOT: sdkRoot,
193
- }),
194
- });
184
+ await runSdkManagerWithConsent(sdkRoot, packages);
195
185
  };
196
186
  export const getAndroidSdkRoot = (env = process.env, options = {}) => {
197
187
  return (getConfiguredAndroidSdkRoot(env) ?? getDefaultUnixAndroidSdkRoot(options));
@@ -304,14 +294,17 @@ export const getAndroidProcessEnv = (env = process.env) => {
304
294
  ...env,
305
295
  ANDROID_HOME: sdkRoot,
306
296
  ANDROID_SDK_ROOT: sdkRoot,
307
- ANDROID_AVD_HOME: path.join(os.homedir(), '.android', 'avd'),
297
+ ANDROID_AVD_HOME: env.ANDROID_AVD_HOME || path.join(os.homedir(), '.android', 'avd'),
308
298
  PATH: pathEntries.join(path.delimiter),
309
299
  };
310
300
  };
311
301
  export const initializeAndroidProcessEnv = () => {
312
302
  Object.assign(process.env, getAndroidProcessEnv());
313
303
  };
314
- export const getAdbBinaryPath = (sdkRoot = getRequiredAndroidSdkRoot()) => path.join(sdkRoot, 'platform-tools', 'adb');
315
- export const getEmulatorBinaryPath = (sdkRoot = getRequiredAndroidSdkRoot()) => path.join(sdkRoot, 'emulator', 'emulator');
316
- export const getSdkManagerBinaryPath = (sdkRoot = getRequiredAndroidSdkRoot()) => path.join(sdkRoot, ...CMDLINE_TOOLS_PATH_SEGMENTS, 'bin', 'sdkmanager');
317
- export const getAvdManagerBinaryPath = (sdkRoot = getRequiredAndroidSdkRoot()) => path.join(sdkRoot, ...CMDLINE_TOOLS_PATH_SEGMENTS, 'bin', 'avdmanager');
304
+ // On Windows, SDK binaries ship as `.exe` and command-line tools as `.bat`.
305
+ // Spawning handles either form, but existence checks need the real file name.
306
+ const getSdkExecutableName = (name, windowsExtension, platform = process.platform) => (platform === 'win32' ? `${name}${windowsExtension}` : name);
307
+ export const getAdbBinaryPath = (sdkRoot = getRequiredAndroidSdkRoot()) => path.join(sdkRoot, 'platform-tools', getSdkExecutableName('adb', '.exe'));
308
+ export const getEmulatorBinaryPath = (sdkRoot = getRequiredAndroidSdkRoot()) => path.join(sdkRoot, 'emulator', getSdkExecutableName('emulator', '.exe'));
309
+ export const getSdkManagerBinaryPath = (sdkRoot = getRequiredAndroidSdkRoot()) => path.join(sdkRoot, ...CMDLINE_TOOLS_PATH_SEGMENTS, 'bin', getSdkExecutableName('sdkmanager', '.bat'));
310
+ export const getAvdManagerBinaryPath = (sdkRoot = getRequiredAndroidSdkRoot()) => path.join(sdkRoot, ...CMDLINE_TOOLS_PATH_SEGMENTS, 'bin', getSdkExecutableName('avdmanager', '.bat'));
@@ -1 +1 @@
1
- {"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../src/instance.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,0BAA0B,EAC/B,qBAAqB,EACtB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAE5E,OAAO,EACL,qBAAqB,EAGtB,MAAM,aAAa,CAAC;AAyKrB,eAAO,MAAM,kCAAkC,GAC7C,QAAQ,qBAAqB,EAC7B,eAAe,aAAa,EAC5B,MAAM,0BAA0B,KAC/B,OAAO,CAAC,qBAAqB,CAmI/B,CAAC;AAEF,eAAO,MAAM,wCAAwC,GACnD,QAAQ,qBAAqB,EAC7B,eAAe,aAAa,EAC5B,OAAO,0BAA0B,KAChC,OAAO,CAAC,qBAAqB,CA8D/B,CAAC"}
1
+ {"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../src/instance.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,0BAA0B,EAC/B,qBAAqB,EACtB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAE5E,OAAO,EACL,qBAAqB,EAGtB,MAAM,aAAa,CAAC;AAmLrB,eAAO,MAAM,kCAAkC,GAC7C,QAAQ,qBAAqB,EAC7B,eAAe,aAAa,EAC5B,MAAM,0BAA0B,KAC/B,OAAO,CAAC,qBAAqB,CAoI/B,CAAC;AAEF,eAAO,MAAM,wCAAwC,GACnD,QAAQ,qBAAqB,EAC7B,eAAe,aAAa,EAC5B,OAAO,0BAA0B,KAChC,OAAO,CAAC,qBAAqB,CA+D/B,CAAC"}
package/dist/instance.js CHANGED
@@ -33,11 +33,17 @@ const getOptionalHarnessAppPath = () => {
33
33
  }
34
34
  return appPath;
35
35
  };
36
- const configureAndroidRuntime = async (adbId, config, harnessConfig) => {
37
- const metroPort = harnessConfig.metroPort;
36
+ // ADB reconnects can remove device-side tunnels between app launches.
37
+ const restorePortForwarding = async (adbId, metroPort) => {
38
38
  await Promise.all([
39
39
  adbModule.reversePort(adbId, metroPort),
40
40
  adbModule.reversePort(adbId, 8080),
41
+ ]);
42
+ };
43
+ const configureAndroidRuntime = async (adbId, config, harnessConfig) => {
44
+ const metroPort = harnessConfig.metroPort;
45
+ await Promise.all([
46
+ restorePortForwarding(adbId, metroPort),
41
47
  adbModule.setHideErrorDialogs(adbId, true),
42
48
  applyHarnessDebugHttpHost(adbId, config.bundleId, `localhost:${metroPort}`),
43
49
  ]);
@@ -163,6 +169,7 @@ export const getAndroidEmulatorPlatformInstance = async (config, harnessConfig,
163
169
  return {
164
170
  createAppSession: async (options) => {
165
171
  await adb.stopApp(adbId, config.bundleId);
172
+ await restorePortForwarding(adbId, harnessConfig.metroPort);
166
173
  const launchOptions = options ??
167
174
  config.appLaunchOptions;
168
175
  return await createAndroidAppSession({
@@ -208,6 +215,7 @@ export const getAndroidPhysicalDevicePlatformInstance = async (config, harnessCo
208
215
  return {
209
216
  createAppSession: async (options) => {
210
217
  await adb.stopApp(adbId, config.bundleId);
218
+ await restorePortForwarding(adbId, harnessConfig.metroPort);
211
219
  const launchOptions = options ??
212
220
  config.appLaunchOptions;
213
221
  return await createAndroidAppSession({
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@react-native-harness/platform-android",
3
3
  "description": "Android platform for React Native Harness",
4
- "version": "1.6.0",
4
+ "version": "1.7.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",
@@ -19,9 +19,9 @@
19
19
  "vite": "^7.2.2",
20
20
  "zod": "^3.25.67",
21
21
  "tslib": "^2.3.0",
22
- "@react-native-harness/tools": "1.6.0",
23
- "@react-native-harness/platforms": "1.6.0",
24
- "@react-native-harness/config": "1.6.0"
22
+ "@react-native-harness/config": "1.7.0",
23
+ "@react-native-harness/platforms": "1.7.0",
24
+ "@react-native-harness/tools": "1.7.0"
25
25
  },
26
26
  "license": "MIT"
27
27
  }
package/src/adb.ts CHANGED
@@ -10,11 +10,12 @@ import {
10
10
  } from '@react-native-harness/tools';
11
11
  import { spawn as nodeSpawn } from 'node:child_process';
12
12
  import type { ChildProcessByStdio } from 'node:child_process';
13
- import { access, rm } from 'node:fs/promises';
13
+ import { access, appendFile, rm, writeFile } from 'node:fs/promises';
14
14
  import type { Readable } from 'node:stream';
15
15
  import {
16
16
  getAvdConfigPath,
17
17
  getAvdDirectory,
18
+ getAvdIniPath,
18
19
  readAvdConfig,
19
20
  } from './avd-config.js';
20
21
  import {
@@ -201,12 +202,6 @@ const ensureAvdConfigExists = async (name: string): Promise<string> => {
201
202
  return configPath;
202
203
  };
203
204
 
204
- const getAvdIniPath = (name: string): string => {
205
- const avdHome =
206
- process.env.ANDROID_AVD_HOME ?? `${process.env.HOME}/.android/avd`;
207
- return `${avdHome}/${name}.ini`;
208
- };
209
-
210
205
  const ensureAvdIniExists = async ({
211
206
  name,
212
207
  apiLevel,
@@ -217,10 +212,16 @@ const ensureAvdIniExists = async ({
217
212
  const iniPath = getAvdIniPath(name);
218
213
 
219
214
  const avdDirectory = getAvdDirectory(name);
220
- await spawn('bash', [
221
- '-lc',
222
- `printf '%s\n%s\n%s\n%s\n' 'avd.ini.encoding=UTF-8' 'path=${avdDirectory}' 'path.rel=avd/${name}.avd' 'target=android-${apiLevel}' > "${iniPath}"`,
223
- ]);
215
+ await writeFile(
216
+ iniPath,
217
+ [
218
+ 'avd.ini.encoding=UTF-8',
219
+ `path=${avdDirectory}`,
220
+ `path.rel=avd/${name}.avd`,
221
+ `target=android-${apiLevel}`,
222
+ '',
223
+ ].join('\n')
224
+ );
224
225
 
225
226
  return iniPath;
226
227
  };
@@ -500,12 +501,24 @@ export const createAvd = async ({
500
501
 
501
502
  await verifyAndroidEmulatorSdk(apiLevel);
502
503
  await ensureAvdProfileAvailable(profile);
503
- await spawn('bash', [
504
- '-lc',
505
- `printf 'no\n' | "${getAvdManagerBinaryPath()}" create avd --force --name "${name}" --package "${systemImagePackage}" --device "${profile}" -p "${getAvdDirectory(
506
- name
507
- )}"`,
508
- ]);
504
+ // avdmanager asks whether to create a custom hardware profile; answer "no".
505
+ await spawn(
506
+ getAvdManagerBinaryPath(),
507
+ [
508
+ 'create',
509
+ 'avd',
510
+ '--force',
511
+ '--name',
512
+ name,
513
+ '--package',
514
+ systemImagePackage,
515
+ '--device',
516
+ profile,
517
+ '-p',
518
+ getAvdDirectory(name),
519
+ ],
520
+ { stdin: { string: 'no\n' } }
521
+ );
509
522
  await ensureAvdIniExists({ name, apiLevel });
510
523
  const configPath = await ensureAvdConfigExists(name);
511
524
  // hw.cpu.ncore is baked into config.ini (rather than passed as a `-cores`
@@ -513,30 +526,25 @@ export const createAvd = async ({
513
526
  // snapshots require an identical hardware config to load, so the vCPU
514
527
  // count must be fixed at AVD-creation time and stay consistent across
515
528
  // boots.
516
- await spawn('bash', [
517
- '-lc',
518
- `printf '%s\n%s\n%s\n' 'disk.dataPartition.size=${diskSize}' 'vm.heapSize=${heapSize}' 'hw.cpu.ncore=${getEmulatorCpuCores()}' >> "${configPath}"`,
519
- ]);
529
+ await appendFile(
530
+ configPath,
531
+ [
532
+ `disk.dataPartition.size=${diskSize}`,
533
+ `vm.heapSize=${heapSize}`,
534
+ `hw.cpu.ncore=${getEmulatorCpuCores()}`,
535
+ '',
536
+ ].join('\n')
537
+ );
520
538
  };
521
539
 
522
540
  export const deleteAvd = async (name: string): Promise<void> => {
523
- await rm(
524
- `${
525
- process.env.ANDROID_AVD_HOME ?? `${process.env.HOME}/.android/avd`
526
- }/${name}.avd`,
527
- {
528
- force: true,
529
- recursive: true,
530
- }
531
- );
532
- await rm(
533
- `${
534
- process.env.ANDROID_AVD_HOME ?? `${process.env.HOME}/.android/avd`
535
- }/${name}.ini`,
536
- {
537
- force: true,
538
- }
539
- );
541
+ await rm(getAvdDirectory(name), {
542
+ force: true,
543
+ recursive: true,
544
+ });
545
+ await rm(getAvdIniPath(name), {
546
+ force: true,
547
+ });
540
548
  };
541
549
 
542
550
  export const startEmulator = async (
package/src/avd-config.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { access, readFile } from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
2
4
  import type { AndroidSystemImageArch } from './environment.js';
3
5
  import type { AndroidEmulator, AndroidEmulatorAVDConfig } from './config.js';
4
6
  import { getEmulatorCpuCores } from './emulator-startup.js';
@@ -16,14 +18,22 @@ export type AvdCompatibilityResult =
16
18
  | { compatible: true }
17
19
  | { compatible: false; reason: string };
18
20
 
21
+ export const getAvdHome = (): string => {
22
+ return (
23
+ process.env.ANDROID_AVD_HOME ?? path.join(os.homedir(), '.android', 'avd')
24
+ );
25
+ };
26
+
19
27
  export const getAvdDirectory = (name: string): string => {
20
- return `${
21
- process.env.ANDROID_AVD_HOME ?? `${process.env.HOME}/.android/avd`
22
- }/${name}.avd`;
28
+ return path.join(getAvdHome(), `${name}.avd`);
29
+ };
30
+
31
+ export const getAvdIniPath = (name: string): string => {
32
+ return path.join(getAvdHome(), `${name}.ini`);
23
33
  };
24
34
 
25
35
  export const getAvdConfigPath = (name: string): string => {
26
- return `${getAvdDirectory(name)}/config.ini`;
36
+ return path.join(getAvdDirectory(name), 'config.ini');
27
37
  };
28
38
 
29
39
  const normalizeAvdValue = (value: string | undefined): string | undefined => {
@@ -38,6 +38,14 @@ export const getDefaultUnixAndroidSdkRoot = ({
38
38
  return path.join(homeDirectory, 'Android', 'Sdk');
39
39
  }
40
40
 
41
+ if (platform === 'win32') {
42
+ return path.join(
43
+ process.env.LOCALAPPDATA ?? path.join(homeDirectory, 'AppData', 'Local'),
44
+ 'Android',
45
+ 'Sdk',
46
+ );
47
+ }
48
+
41
49
  return null;
42
50
  };
43
51
 
@@ -56,9 +64,8 @@ const pathExists = async (filePath: string): Promise<boolean> => {
56
64
  }
57
65
  };
58
66
 
59
- const quoteShell = (value: string): string => {
60
- return `'${value.replace(/'/g, `'\\''`)}'`;
61
- };
67
+ // Enough "y" answers for every license prompt sdkmanager may show.
68
+ const SDK_MANAGER_CONSENT_ANSWERS = 100;
62
69
 
63
70
  const downloadText = async (url: string): Promise<string> => {
64
71
  return new Promise((resolve, reject) => {
@@ -217,18 +224,16 @@ const ensureAndroidCommandLineTools = async (
217
224
  }
218
225
  };
219
226
 
220
- const acceptAndroidLicenses = async (sdkRoot: string): Promise<void> => {
221
- const sdkManagerBinaryPath = getSdkManagerBinaryPath(sdkRoot);
222
-
227
+ // Answers "y" to every prompt, like piping `yes` into sdkmanager.
228
+ const runSdkManagerWithConsent = async (
229
+ sdkRoot: string,
230
+ args: readonly string[],
231
+ ): Promise<void> => {
223
232
  await spawn(
224
- 'bash',
225
- [
226
- '-lc',
227
- `yes | ${quoteShell(sdkManagerBinaryPath)} --sdk_root=${quoteShell(
228
- sdkRoot,
229
- )} --licenses >/dev/null`,
230
- ],
233
+ getSdkManagerBinaryPath(sdkRoot),
234
+ [`--sdk_root=${sdkRoot}`, ...args],
231
235
  {
236
+ stdin: { string: 'y\n'.repeat(SDK_MANAGER_CONSENT_ANSWERS) },
232
237
  env: getAndroidProcessEnv({
233
238
  ...process.env,
234
239
  ANDROID_HOME: sdkRoot,
@@ -238,6 +243,10 @@ const acceptAndroidLicenses = async (sdkRoot: string): Promise<void> => {
238
243
  );
239
244
  };
240
245
 
246
+ const acceptAndroidLicenses = async (sdkRoot: string): Promise<void> => {
247
+ await runSdkManagerWithConsent(sdkRoot, ['--licenses']);
248
+ };
249
+
241
250
  const getPackageVerificationPath = (
242
251
  sdkRoot: string,
243
252
  packageName: string,
@@ -290,33 +299,13 @@ const installAndroidSdkPackages = async (
290
299
  return;
291
300
  }
292
301
 
293
- const sdkManagerBinaryPath = getSdkManagerBinaryPath(sdkRoot);
294
- const packageArgs = packages
295
- .map((packageName) => quoteShell(packageName))
296
- .join(' ');
297
-
298
302
  androidEnvironmentLogger.info(
299
303
  'Installing missing Android SDK packages: %s',
300
304
  packages.join(', '),
301
305
  );
302
306
 
303
307
  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
- );
308
+ await runSdkManagerWithConsent(sdkRoot, packages);
320
309
  };
321
310
 
322
311
  export const getAndroidSdkRoot = (
@@ -526,7 +515,8 @@ export const getAndroidProcessEnv = (
526
515
  ...env,
527
516
  ANDROID_HOME: sdkRoot,
528
517
  ANDROID_SDK_ROOT: sdkRoot,
529
- ANDROID_AVD_HOME: path.join(os.homedir(), '.android', 'avd'),
518
+ ANDROID_AVD_HOME:
519
+ env.ANDROID_AVD_HOME || path.join(os.homedir(), '.android', 'avd'),
530
520
  PATH: pathEntries.join(path.delimiter),
531
521
  };
532
522
  };
@@ -535,20 +525,40 @@ export const initializeAndroidProcessEnv = (): void => {
535
525
  Object.assign(process.env, getAndroidProcessEnv());
536
526
  };
537
527
 
528
+ // On Windows, SDK binaries ship as `.exe` and command-line tools as `.bat`.
529
+ // Spawning handles either form, but existence checks need the real file name.
530
+ const getSdkExecutableName = (
531
+ name: string,
532
+ windowsExtension: '.exe' | '.bat',
533
+ platform: NodeJS.Platform = process.platform,
534
+ ): string => (platform === 'win32' ? `${name}${windowsExtension}` : name);
535
+
538
536
  export const getAdbBinaryPath = (
539
537
  sdkRoot: string = getRequiredAndroidSdkRoot(),
540
- ): string => path.join(sdkRoot, 'platform-tools', 'adb');
538
+ ): string =>
539
+ path.join(sdkRoot, 'platform-tools', getSdkExecutableName('adb', '.exe'));
541
540
 
542
541
  export const getEmulatorBinaryPath = (
543
542
  sdkRoot: string = getRequiredAndroidSdkRoot(),
544
- ): string => path.join(sdkRoot, 'emulator', 'emulator');
543
+ ): string =>
544
+ path.join(sdkRoot, 'emulator', getSdkExecutableName('emulator', '.exe'));
545
545
 
546
546
  export const getSdkManagerBinaryPath = (
547
547
  sdkRoot: string = getRequiredAndroidSdkRoot(),
548
548
  ): string =>
549
- path.join(sdkRoot, ...CMDLINE_TOOLS_PATH_SEGMENTS, 'bin', 'sdkmanager');
549
+ path.join(
550
+ sdkRoot,
551
+ ...CMDLINE_TOOLS_PATH_SEGMENTS,
552
+ 'bin',
553
+ getSdkExecutableName('sdkmanager', '.bat'),
554
+ );
550
555
 
551
556
  export const getAvdManagerBinaryPath = (
552
557
  sdkRoot: string = getRequiredAndroidSdkRoot(),
553
558
  ): string =>
554
- path.join(sdkRoot, ...CMDLINE_TOOLS_PATH_SEGMENTS, 'bin', 'avdmanager');
559
+ path.join(
560
+ sdkRoot,
561
+ ...CMDLINE_TOOLS_PATH_SEGMENTS,
562
+ 'bin',
563
+ getSdkExecutableName('avdmanager', '.bat'),
564
+ );
package/src/instance.ts CHANGED
@@ -64,6 +64,17 @@ const getOptionalHarnessAppPath = (): string | undefined => {
64
64
  return appPath;
65
65
  };
66
66
 
67
+ // ADB reconnects can remove device-side tunnels between app launches.
68
+ const restorePortForwarding = async (
69
+ adbId: string,
70
+ metroPort: number
71
+ ): Promise<void> => {
72
+ await Promise.all([
73
+ adbModule.reversePort(adbId, metroPort),
74
+ adbModule.reversePort(adbId, 8080),
75
+ ]);
76
+ };
77
+
67
78
  const configureAndroidRuntime = async (
68
79
  adbId: string,
69
80
  config: AndroidPlatformConfig,
@@ -72,8 +83,7 @@ const configureAndroidRuntime = async (
72
83
  const metroPort = harnessConfig.metroPort;
73
84
 
74
85
  await Promise.all([
75
- adbModule.reversePort(adbId, metroPort),
76
- adbModule.reversePort(adbId, 8080),
86
+ restorePortForwarding(adbId, metroPort),
77
87
  adbModule.setHideErrorDialogs(adbId, true),
78
88
  applyHarnessDebugHttpHost(adbId, config.bundleId, `localhost:${metroPort}`),
79
89
  ]);
@@ -280,6 +290,7 @@ export const getAndroidEmulatorPlatformInstance = async (
280
290
  return {
281
291
  createAppSession: async (options) => {
282
292
  await adb.stopApp(adbId, config.bundleId);
293
+ await restorePortForwarding(adbId, harnessConfig.metroPort);
283
294
  const launchOptions =
284
295
  (options as typeof config.appLaunchOptions | undefined) ??
285
296
  config.appLaunchOptions;
@@ -353,6 +364,7 @@ export const getAndroidPhysicalDevicePlatformInstance = async (
353
364
  return {
354
365
  createAppSession: async (options) => {
355
366
  await adb.stopApp(adbId, config.bundleId);
367
+ await restorePortForwarding(adbId, harnessConfig.metroPort);
356
368
  const launchOptions =
357
369
  (options as typeof config.appLaunchOptions | undefined) ??
358
370
  config.appLaunchOptions;
@@ -1,13 +0,0 @@
1
- import { type AppMonitor, type AppCrashDetails, type CrashArtifactWriter, type CrashDetailsLookupOptions, type AppMonitorEvent } from '@react-native-harness/platforms';
2
- declare const createAndroidLogEvent: (line: string, bundleId: string) => AppMonitorEvent | null;
3
- export declare const createAndroidAppMonitor: ({ adbId, bundleId, appUid, crashArtifactWriter, }: {
4
- adbId: string;
5
- bundleId: string;
6
- appUid: number;
7
- crashArtifactWriter?: CrashArtifactWriter;
8
- }) => AndroidAppMonitor;
9
- export { createAndroidLogEvent };
10
- export type AndroidAppMonitor = AppMonitor & {
11
- getCrashDetails: (options: CrashDetailsLookupOptions) => Promise<AppCrashDetails | null>;
12
- };
13
- //# sourceMappingURL=app-monitor.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"app-monitor.d.ts","sourceRoot":"","sources":["../src/app-monitor.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,eAAe,EAErB,MAAM,iCAAiC,CAAC;AA+SzC,QAAA,MAAM,qBAAqB,GACzB,MAAM,MAAM,EACZ,UAAU,MAAM,KACf,eAAe,GAAG,IAwEpB,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,mDAKrC;IACD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;CAC3C,KAAG,iBA4KH,CAAC;AAEF,OAAO,EAAE,qBAAqB,EAAE,CAAC;AACjC,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG;IAC3C,eAAe,EAAE,CACf,OAAO,EAAE,yBAAyB,KAC/B,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;CACtC,CAAC"}
@@ -1,376 +0,0 @@
1
- import { escapeRegExp, getEmitter, logger, SubprocessError, } from '@react-native-harness/tools';
2
- import * as adb from './adb.js';
3
- import { androidCrashParser } from './crash-parser.js';
4
- const androidAppMonitorLogger = logger.child('android-app-monitor');
5
- const getLogcatArgs = (uid, fromTime) => [
6
- 'logcat',
7
- '-v',
8
- 'threadtime',
9
- '-b',
10
- 'crash',
11
- `--uid=${uid}`,
12
- '-T',
13
- fromTime,
14
- ];
15
- const MAX_RECENT_LOG_LINES = 200;
16
- const MAX_RECENT_CRASH_ARTIFACTS = 10;
17
- const CRASH_ARTIFACT_SETTLE_DELAY_MS = 100;
18
- const startProcPattern = (bundleId) => new RegExp(`Start proc (\\d+):${escapeRegExp(bundleId)}(?:/|\\s)`);
19
- const processPattern = (bundleId) => new RegExp(`Process:\\s*${escapeRegExp(bundleId)},\\s*PID:\\s*(\\d+)`);
20
- const nativeCrashPattern = (bundleId) => new RegExp(`>>>\\s*${escapeRegExp(bundleId)}\\s*<<<`);
21
- const processDiedPattern = (bundleId) => new RegExp(`Process\\s+${escapeRegExp(bundleId)}\\s+\\(pid\\s+(\\d+)\\)\\s+has\\s+died`, 'i');
22
- const getSignal = (line) => {
23
- const namedSignalMatch = line.match(/\b(SIG[A-Z0-9]+)\b/);
24
- if (namedSignalMatch) {
25
- return namedSignalMatch[1];
26
- }
27
- const signalNumberMatch = line.match(/signal\s+(\d+)/i);
28
- if (signalNumberMatch) {
29
- return `signal ${signalNumberMatch[1]}`;
30
- }
31
- return undefined;
32
- };
33
- const getAndroidLogLineCrashDetails = ({ line, bundleId, pid, }) => {
34
- const fatalExceptionMatch = line.match(/FATAL EXCEPTION:\s*(.+)$/i);
35
- const processMatch = line.match(processPattern(bundleId));
36
- return {
37
- source: 'logs',
38
- summary: line.trim(),
39
- signal: getSignal(line),
40
- exceptionType: fatalExceptionMatch?.[1]?.trim(),
41
- processName: processMatch
42
- ? bundleId
43
- : line.includes(bundleId)
44
- ? bundleId
45
- : undefined,
46
- pid: pid ?? (processMatch ? Number(processMatch[1]) : undefined),
47
- rawLines: [line],
48
- };
49
- };
50
- const CRASH_BLOCK_HEADER = '--------- beginning of crash';
51
- const getLatestCrashBlock = (recentLogLines) => {
52
- const lines = recentLogLines.map(({ line }) => line);
53
- let latestCrashHeaderIndex = -1;
54
- for (let index = lines.length - 1; index >= 0; index -= 1) {
55
- if (/FATAL EXCEPTION:|Process:\s+.+,\s+PID:/i.test(lines[index])) {
56
- latestCrashHeaderIndex = index;
57
- break;
58
- }
59
- }
60
- const blockStartIndex = Math.max(lines.lastIndexOf(CRASH_BLOCK_HEADER), latestCrashHeaderIndex);
61
- if (blockStartIndex === -1) {
62
- return lines;
63
- }
64
- return lines.slice(blockStartIndex);
65
- };
66
- const getCrashBlockForArtifact = ({ artifact, recentLogLines, }) => {
67
- const targetIndex = recentLogLines.findIndex(({ line, occurredAt }) => line === artifact.triggerLine &&
68
- (artifact.triggerOccurredAt === undefined ||
69
- occurredAt === artifact.triggerOccurredAt));
70
- if (targetIndex === -1) {
71
- return artifact.rawLines ?? [];
72
- }
73
- let blockStartIndex = targetIndex;
74
- for (let index = targetIndex; index >= 0; index -= 1) {
75
- const { line } = recentLogLines[index];
76
- if (line === CRASH_BLOCK_HEADER) {
77
- blockStartIndex = index;
78
- break;
79
- }
80
- }
81
- let blockEndIndex = recentLogLines.length;
82
- for (let index = targetIndex + 1; index < recentLogLines.length; index += 1) {
83
- if (recentLogLines[index].line === CRASH_BLOCK_HEADER) {
84
- blockEndIndex = index;
85
- break;
86
- }
87
- }
88
- return recentLogLines
89
- .slice(blockStartIndex, blockEndIndex)
90
- .map(({ line }) => line);
91
- };
92
- const hydrateCrashArtifact = ({ artifact, recentLogLines, }) => {
93
- const rawLines = getCrashBlockForArtifact({ artifact, recentLogLines });
94
- if (rawLines.length === 0) {
95
- return artifact;
96
- }
97
- const parsedDetails = androidCrashParser.parse({
98
- contents: rawLines.join('\n'),
99
- bundleId: artifact.processName ?? '',
100
- pid: artifact.pid,
101
- });
102
- return {
103
- ...artifact,
104
- ...parsedDetails,
105
- artifactType: artifact.artifactType,
106
- artifactPath: artifact.artifactPath,
107
- rawLines,
108
- };
109
- };
110
- const createCrashArtifact = ({ details, recentLogLines, }) => {
111
- const occurredAt = Date.now();
112
- const rawLines = getLatestCrashBlock(recentLogLines);
113
- const triggerOccurredAt = [...recentLogLines]
114
- .reverse()
115
- .find(({ line }) => line === details.summary)?.occurredAt;
116
- const contents = rawLines.length > 0
117
- ? rawLines.join('\n')
118
- : (details.rawLines ?? []).join('\n');
119
- const parsedDetails = details.processName !== undefined
120
- ? androidCrashParser.parse({
121
- contents,
122
- bundleId: details.processName,
123
- pid: details.pid,
124
- })
125
- : details;
126
- return {
127
- ...parsedDetails,
128
- occurredAt,
129
- triggerLine: details.summary ?? '',
130
- triggerOccurredAt,
131
- artifactType: 'logcat',
132
- rawLines: rawLines.length > 0
133
- ? rawLines
134
- : parsedDetails.rawLines ?? details.rawLines,
135
- };
136
- };
137
- const persistCrashArtifact = ({ details, crashArtifactWriter, }) => {
138
- if (!crashArtifactWriter || details.artifactType !== 'logcat') {
139
- return details;
140
- }
141
- const artifactBody = details.rawLines?.join('\n');
142
- if (!artifactBody) {
143
- return details;
144
- }
145
- return {
146
- ...details,
147
- artifactPath: crashArtifactWriter.persistArtifact({
148
- artifactKind: details.artifactType,
149
- source: {
150
- kind: 'text',
151
- fileName: 'logcat.txt',
152
- text: `${artifactBody}\n`,
153
- },
154
- }),
155
- };
156
- };
157
- const getLatestCrashArtifact = ({ crashArtifacts, recentLogLines, processName, pid, occurredAt, }) => {
158
- const matchingByPid = pid
159
- ? crashArtifacts.filter((artifact) => artifact.pid === pid)
160
- : [];
161
- const matchingByProcess = processName
162
- ? crashArtifacts.filter((artifact) => artifact.processName === processName)
163
- : [];
164
- const candidates = matchingByPid.length > 0
165
- ? matchingByPid
166
- : matchingByProcess.length > 0
167
- ? matchingByProcess
168
- : crashArtifacts;
169
- const sortedCandidates = [...candidates].sort((left, right) => Math.abs(left.occurredAt - occurredAt) -
170
- Math.abs(right.occurredAt - occurredAt));
171
- const artifact = sortedCandidates[0];
172
- if (!artifact) {
173
- return null;
174
- }
175
- return hydrateCrashArtifact({
176
- artifact,
177
- recentLogLines,
178
- });
179
- };
180
- const createAndroidLogEvent = (line, bundleId) => {
181
- const startMatch = line.match(startProcPattern(bundleId));
182
- if (startMatch) {
183
- return {
184
- type: 'app_started',
185
- pid: Number(startMatch[1]),
186
- source: 'logs',
187
- line,
188
- };
189
- }
190
- const processMatch = line.match(processPattern(bundleId));
191
- if (processMatch) {
192
- return {
193
- type: 'possible_crash',
194
- pid: Number(processMatch[1]),
195
- source: 'logs',
196
- line,
197
- crashDetails: getAndroidLogLineCrashDetails({
198
- line,
199
- bundleId,
200
- pid: Number(processMatch[1]),
201
- }),
202
- };
203
- }
204
- if (nativeCrashPattern(bundleId).test(line)) {
205
- return {
206
- type: 'possible_crash',
207
- source: 'logs',
208
- line,
209
- crashDetails: getAndroidLogLineCrashDetails({
210
- line,
211
- bundleId,
212
- }),
213
- };
214
- }
215
- const diedMatch = line.match(processDiedPattern(bundleId));
216
- if (diedMatch) {
217
- return {
218
- type: 'app_exited',
219
- pid: Number(diedMatch[1]),
220
- source: 'logs',
221
- line,
222
- crashDetails: getAndroidLogLineCrashDetails({
223
- line,
224
- bundleId,
225
- pid: Number(diedMatch[1]),
226
- }),
227
- };
228
- }
229
- if (line.includes(bundleId) &&
230
- /fatal|crash|signal 11|signal 6|backtrace/i.test(line)) {
231
- return {
232
- type: 'possible_crash',
233
- source: 'logs',
234
- line,
235
- crashDetails: getAndroidLogLineCrashDetails({
236
- line,
237
- bundleId,
238
- }),
239
- };
240
- }
241
- return null;
242
- };
243
- export const createAndroidAppMonitor = ({ adbId, bundleId, appUid, crashArtifactWriter, }) => {
244
- const emitter = getEmitter();
245
- let isStarted = false;
246
- let logcatProcess = null;
247
- let logTask = null;
248
- let recentLogLines = [];
249
- let recentCrashArtifacts = [];
250
- const emit = (event) => {
251
- emitter.emit(event);
252
- };
253
- const recordLogLine = (line) => {
254
- recentLogLines = [
255
- ...recentLogLines,
256
- { line, occurredAt: Date.now() },
257
- ].slice(-MAX_RECENT_LOG_LINES);
258
- };
259
- const recordCrashArtifact = (details) => {
260
- if (!details) {
261
- return;
262
- }
263
- recentCrashArtifacts = [
264
- ...recentCrashArtifacts,
265
- createCrashArtifact({
266
- details,
267
- recentLogLines,
268
- }),
269
- ].slice(-MAX_RECENT_CRASH_ARTIFACTS);
270
- };
271
- const stopProcess = async (child) => {
272
- if (!child) {
273
- return;
274
- }
275
- try {
276
- (await child.nodeChildProcess).kill();
277
- }
278
- catch {
279
- // Ignore termination failures for background monitors.
280
- }
281
- };
282
- const startLogcat = async () => {
283
- const logcatTimestamp = await adb.getLogcatTimestamp(adbId);
284
- logcatProcess = adb.startLogcat(adbId, getLogcatArgs(appUid, logcatTimestamp));
285
- const currentProcess = logcatProcess;
286
- if (!currentProcess) {
287
- return;
288
- }
289
- logTask = (async () => {
290
- try {
291
- for await (const line of currentProcess) {
292
- recordLogLine(line);
293
- emit({ type: 'log', source: 'logs', line });
294
- const event = createAndroidLogEvent(line, bundleId);
295
- if (event) {
296
- if (event.type === 'possible_crash' ||
297
- event.type === 'app_exited') {
298
- recordCrashArtifact(event.crashDetails);
299
- }
300
- emit(event);
301
- }
302
- }
303
- }
304
- catch (error) {
305
- if (!(error instanceof SubprocessError && error.signalName === 'SIGTERM')) {
306
- androidAppMonitorLogger.debug('Android logcat monitor stopped', error);
307
- }
308
- }
309
- })();
310
- };
311
- const start = async () => {
312
- if (isStarted) {
313
- return;
314
- }
315
- try {
316
- await startLogcat();
317
- isStarted = true;
318
- }
319
- catch (error) {
320
- const currentProcess = logcatProcess;
321
- const currentTask = logTask;
322
- logcatProcess = null;
323
- logTask = null;
324
- await stopProcess(currentProcess);
325
- await currentTask;
326
- throw error;
327
- }
328
- };
329
- const stop = async () => {
330
- if (!isStarted) {
331
- return;
332
- }
333
- isStarted = false;
334
- const currentProcess = logcatProcess;
335
- const currentTask = logTask;
336
- logcatProcess = null;
337
- logTask = null;
338
- await stopProcess(currentProcess);
339
- await currentTask;
340
- };
341
- const dispose = async () => {
342
- await stop();
343
- emitter.clearAllListeners();
344
- recentLogLines = [];
345
- recentCrashArtifacts = [];
346
- };
347
- const addListener = (listener) => {
348
- emitter.addListener(listener);
349
- };
350
- const removeListener = (listener) => {
351
- emitter.removeListener(listener);
352
- };
353
- return {
354
- start,
355
- stop,
356
- dispose,
357
- addListener,
358
- removeListener,
359
- getCrashDetails: async (options) => {
360
- await new Promise((resolve) => setTimeout(resolve, CRASH_ARTIFACT_SETTLE_DELAY_MS));
361
- const details = getLatestCrashArtifact({
362
- crashArtifacts: recentCrashArtifacts,
363
- recentLogLines,
364
- ...options,
365
- });
366
- if (!details) {
367
- return null;
368
- }
369
- return persistCrashArtifact({
370
- details,
371
- crashArtifactWriter,
372
- });
373
- },
374
- };
375
- };
376
- export { createAndroidLogEvent };
@@ -1,6 +0,0 @@
1
- import type { DeviceStateController } from '@react-native-harness/platforms';
2
- export declare const createAndroidDeviceState: (options: {
3
- adbId: string;
4
- bundleId: string;
5
- }) => DeviceStateController;
6
- //# sourceMappingURL=device-state.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"device-state.d.ts","sourceRoot":"","sources":["../src/device-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,qBAAqB,EAEtB,MAAM,iCAAiC,CAAC;AA0HzC,eAAO,MAAM,wBAAwB,GAAI,SAAS;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB,KAAG,qBAqFH,CAAC"}
@@ -1,142 +0,0 @@
1
- import * as adb from './adb.js';
2
- const ANDROID_PERMISSION_ALIASES = {
3
- bluetooth: [
4
- 'android.permission.BLUETOOTH_CONNECT',
5
- 'android.permission.BLUETOOTH_SCAN',
6
- ],
7
- calendar: [
8
- 'android.permission.READ_CALENDAR',
9
- 'android.permission.WRITE_CALENDAR',
10
- ],
11
- camera: ['android.permission.CAMERA'],
12
- contacts: [
13
- 'android.permission.READ_CONTACTS',
14
- 'android.permission.WRITE_CONTACTS',
15
- ],
16
- location: [
17
- 'android.permission.ACCESS_FINE_LOCATION',
18
- 'android.permission.ACCESS_COARSE_LOCATION',
19
- ],
20
- mediaLibrary: [
21
- 'android.permission.READ_MEDIA_IMAGES',
22
- 'android.permission.READ_MEDIA_VIDEO',
23
- 'android.permission.READ_MEDIA_AUDIO',
24
- ],
25
- microphone: ['android.permission.RECORD_AUDIO'],
26
- notifications: ['android.permission.POST_NOTIFICATIONS'],
27
- phone: [
28
- 'android.permission.CALL_PHONE',
29
- 'android.permission.ANSWER_PHONE_CALLS',
30
- ],
31
- sms: [
32
- 'android.permission.READ_SMS',
33
- 'android.permission.RECEIVE_SMS',
34
- 'android.permission.SEND_SMS',
35
- ],
36
- storage: [
37
- 'android.permission.READ_EXTERNAL_STORAGE',
38
- 'android.permission.WRITE_EXTERNAL_STORAGE',
39
- ],
40
- };
41
- const normalizePermissionName = (name) => {
42
- return name.trim();
43
- };
44
- const resolvePermissions = async (options) => {
45
- if (options.command.kind === 'permission-all') {
46
- return adb.getDeclaredDangerousPermissions(options.adbId, options.bundleId);
47
- }
48
- const normalized = normalizePermissionName(options.command.permission);
49
- const aliases = ANDROID_PERMISSION_ALIASES[normalized];
50
- if (aliases) {
51
- return aliases;
52
- }
53
- return [normalized];
54
- };
55
- const snapshotPermissions = async (options) => {
56
- const granted = await Promise.all(options.permissions.map(async (permission) => {
57
- const isGranted = await adb.isPermissionGranted(options.adbId, options.bundleId, permission);
58
- return isGranted ? permission : null;
59
- }));
60
- return granted.filter((permission) => permission != null);
61
- };
62
- const applyPermissionSnapshot = async (options) => {
63
- const grantedSet = new Set(options.snapshot.previousGranted);
64
- const toGrant = options.snapshot.permissions.filter((permission) => grantedSet.has(permission));
65
- const toRevoke = options.snapshot.permissions.filter((permission) => !grantedSet.has(permission));
66
- if (toGrant.length > 0) {
67
- await adb.setPermissions(options.adbId, options.bundleId, toGrant, 'grant', {
68
- bestEffort: true,
69
- });
70
- }
71
- if (toRevoke.length > 0) {
72
- await adb.setPermissions(options.adbId, options.bundleId, toRevoke, 'reset', {
73
- bestEffort: true,
74
- });
75
- }
76
- };
77
- export const createAndroidDeviceState = (options) => {
78
- const outstandingMutations = new Map();
79
- let mutationCounter = 0;
80
- const rememberMutation = (snapshot) => {
81
- const id = `android-permissions-${++mutationCounter}`;
82
- const mutation = {
83
- id,
84
- snapshot,
85
- revert: async () => {
86
- await controller.permissions.revert(id);
87
- },
88
- };
89
- outstandingMutations.set(id, mutation);
90
- return mutation;
91
- };
92
- const controller = {
93
- permissions: {
94
- apply: async (command) => {
95
- const permissions = await resolvePermissions({
96
- adbId: options.adbId,
97
- bundleId: options.bundleId,
98
- command,
99
- });
100
- if (permissions.length === 0) {
101
- return { mutation: null };
102
- }
103
- const previousGranted = await snapshotPermissions({
104
- adbId: options.adbId,
105
- bundleId: options.bundleId,
106
- permissions,
107
- });
108
- await adb.setPermissions(options.adbId, options.bundleId, permissions, command.decision, { bestEffort: command.kind === 'permission-all' });
109
- const mutation = rememberMutation({
110
- kind: 'permission-set',
111
- permissions,
112
- previousGranted,
113
- });
114
- return { mutation };
115
- },
116
- revert: async (mutationId) => {
117
- const mutation = outstandingMutations.get(mutationId);
118
- if (!mutation) {
119
- return;
120
- }
121
- outstandingMutations.delete(mutationId);
122
- await applyPermissionSnapshot({
123
- adbId: options.adbId,
124
- bundleId: options.bundleId,
125
- snapshot: mutation.snapshot,
126
- });
127
- },
128
- resetOutstanding: async () => {
129
- const mutations = [...outstandingMutations.values()].reverse();
130
- outstandingMutations.clear();
131
- for (const mutation of mutations) {
132
- await applyPermissionSnapshot({
133
- adbId: options.adbId,
134
- bundleId: options.bundleId,
135
- snapshot: mutation.snapshot,
136
- });
137
- }
138
- },
139
- },
140
- };
141
- return controller;
142
- };