@react-native-harness/platform-android 1.4.0-rc.1 → 1.4.1

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 (78) hide show
  1. package/dist/adb.d.ts +3 -1
  2. package/dist/adb.d.ts.map +1 -1
  3. package/dist/adb.js +30 -21
  4. package/dist/app-session.d.ts +11 -2
  5. package/dist/app-session.d.ts.map +1 -1
  6. package/dist/app-session.js +17 -12
  7. package/dist/avd-config.d.ts +1 -0
  8. package/dist/avd-config.d.ts.map +1 -1
  9. package/dist/avd-config.js +11 -0
  10. package/dist/emulator-startup.d.ts +23 -0
  11. package/dist/emulator-startup.d.ts.map +1 -1
  12. package/dist/emulator-startup.js +58 -0
  13. package/dist/index.d.ts +1 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1 -0
  16. package/dist/instance.d.ts.map +1 -1
  17. package/dist/instance.js +21 -15
  18. package/dist/runner.d.ts +2 -2
  19. package/dist/runner.d.ts.map +1 -1
  20. package/package.json +4 -4
  21. package/src/adb.ts +72 -65
  22. package/src/app-session.ts +28 -12
  23. package/src/avd-config.ts +17 -0
  24. package/src/emulator-startup.ts +74 -0
  25. package/src/index.ts +1 -0
  26. package/src/instance.ts +30 -16
  27. package/src/runner.ts +5 -9
  28. package/dist/__tests__/adb-id.test.d.ts +0 -2
  29. package/dist/__tests__/adb-id.test.d.ts.map +0 -1
  30. package/dist/__tests__/adb-id.test.js +0 -41
  31. package/dist/__tests__/adb.test.d.ts +0 -2
  32. package/dist/__tests__/adb.test.d.ts.map +0 -1
  33. package/dist/__tests__/adb.test.js +0 -552
  34. package/dist/__tests__/app-monitor.test.d.ts +0 -2
  35. package/dist/__tests__/app-monitor.test.d.ts.map +0 -1
  36. package/dist/__tests__/app-monitor.test.js +0 -257
  37. package/dist/__tests__/avd-config.test.d.ts +0 -2
  38. package/dist/__tests__/avd-config.test.d.ts.map +0 -1
  39. package/dist/__tests__/avd-config.test.js +0 -174
  40. package/dist/__tests__/ci-action.test.d.ts +0 -2
  41. package/dist/__tests__/ci-action.test.d.ts.map +0 -1
  42. package/dist/__tests__/ci-action.test.js +0 -46
  43. package/dist/__tests__/crash-diagnostics.test.d.ts +0 -2
  44. package/dist/__tests__/crash-diagnostics.test.d.ts.map +0 -1
  45. package/dist/__tests__/crash-diagnostics.test.js +0 -119
  46. package/dist/__tests__/crash-parser.test.d.ts +0 -2
  47. package/dist/__tests__/crash-parser.test.d.ts.map +0 -1
  48. package/dist/__tests__/crash-parser.test.js +0 -45
  49. package/dist/__tests__/crash-reporter.test.d.ts +0 -2
  50. package/dist/__tests__/crash-reporter.test.d.ts.map +0 -1
  51. package/dist/__tests__/crash-reporter.test.js +0 -150
  52. package/dist/__tests__/emulator-startup.test.d.ts +0 -2
  53. package/dist/__tests__/emulator-startup.test.d.ts.map +0 -1
  54. package/dist/__tests__/emulator-startup.test.js +0 -20
  55. package/dist/__tests__/environment.test.d.ts +0 -2
  56. package/dist/__tests__/environment.test.d.ts.map +0 -1
  57. package/dist/__tests__/environment.test.js +0 -117
  58. package/dist/__tests__/instance.test.d.ts +0 -2
  59. package/dist/__tests__/instance.test.d.ts.map +0 -1
  60. package/dist/__tests__/instance.test.js +0 -593
  61. package/dist/__tests__/shared-prefs.test.d.ts +0 -2
  62. package/dist/__tests__/shared-prefs.test.d.ts.map +0 -1
  63. package/dist/__tests__/shared-prefs.test.js +0 -87
  64. package/dist/__tests__/targets.test.d.ts +0 -2
  65. package/dist/__tests__/targets.test.d.ts.map +0 -1
  66. package/dist/__tests__/targets.test.js +0 -49
  67. package/dist/tsconfig.lib.tsbuildinfo +0 -1
  68. package/src/__tests__/adb-id.test.ts +0 -51
  69. package/src/__tests__/avd-config.test.ts +0 -206
  70. package/src/__tests__/ci-action.test.ts +0 -81
  71. package/src/__tests__/crash-diagnostics.test.ts +0 -144
  72. package/src/__tests__/crash-parser.test.ts +0 -52
  73. package/src/__tests__/crash-reporter.test.ts +0 -167
  74. package/src/__tests__/emulator-startup.test.ts +0 -35
  75. package/src/__tests__/instance.test.ts +0 -845
  76. package/src/__tests__/shared-prefs.test.ts +0 -144
  77. package/src/__tests__/targets.test.ts +0 -53
  78. package/tsconfig.tsbuildinfo +0 -1
package/dist/adb.d.ts CHANGED
@@ -44,7 +44,9 @@ export declare const getAppPid: (adbId: string, bundleId: string) => Promise<num
44
44
  export declare const getAppUid: (adbId: string, bundleId: string) => Promise<number>;
45
45
  export declare const setHideErrorDialogs: (adbId: string, hide: boolean) => Promise<void>;
46
46
  export declare const getLogcatTimestamp: (adbId: string) => Promise<string>;
47
- export declare const startLogcat: (adbId: string, args: readonly string[]) => Subprocess;
47
+ export declare const startLogcat: (adbId: string, args: readonly string[], options?: {
48
+ signal?: AbortSignal;
49
+ }) => Subprocess;
48
50
  export declare const DROPBOX_CRASH_TAGS: readonly ["data_app_crash", "data_app_native_crash"];
49
51
  export declare const getDropboxPrint: (adbId: string, tags?: readonly string[]) => Promise<string>;
50
52
  export declare const getActivityExitInfo: (adbId: string, bundleId: string) => Promise<string>;
package/dist/adb.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"adb.d.ts","sourceRoot":"","sources":["../src/adb.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"adb.d.ts","sourceRoot":"","sources":["../src/adb.ts"],"names":[],"mappings":"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"}
package/dist/adb.js CHANGED
@@ -1,30 +1,27 @@
1
- import { logger, spawn, SubprocessError, } from '@react-native-harness/tools';
1
+ import { delay, logger, spawn, SubprocessError, waitForAbort, } from '@react-native-harness/tools';
2
2
  import { spawn as nodeSpawn } from 'node:child_process';
3
3
  import { access, rm } from 'node:fs/promises';
4
4
  import { getAvdConfigPath, getAvdDirectory, readAvdConfig, } from './avd-config.js';
5
5
  import { ensureAndroidEmulatorAvailable, ensureAndroidSdkPackages, getAdbBinaryPath, getAndroidSystemImagePackage, getAvdManagerBinaryPath, getEmulatorBinaryPath, getHostAndroidSystemImageArch, getRequiredAndroidSdkPackages, } from './environment.js';
6
- import { getEmulatorStartupArgs, } from './emulator-startup.js';
6
+ import { getEmulatorCpuCores, getEmulatorStartupArgs, } from './emulator-startup.js';
7
7
  import { AdbAppNotInstalledError, AdbPermissionGrantError, } from './adb-errors.js';
8
- const wait = async (ms) => {
9
- await new Promise((resolve) => {
10
- setTimeout(resolve, ms);
11
- });
12
- };
13
- const waitForAbort = (signal) => {
14
- if (signal.aborted) {
15
- return Promise.reject(signal.reason);
16
- }
17
- return new Promise((_, reject) => {
18
- signal.addEventListener('abort', () => {
19
- reject(signal.reason);
20
- }, { once: true });
21
- });
22
- };
23
8
  const waitWithSignal = async (ms, signal) => {
24
9
  if (signal.aborted) {
25
10
  throw signal.reason;
26
11
  }
27
- await Promise.race([wait(ms), waitForAbort(signal)]);
12
+ // Both sides of this race must be torn down explicitly when they lose:
13
+ // an uncancelled timer keeps a ref'd Timeout alive, and an uncancelled
14
+ // abort wait leaks a listener on `signal`, which is a session-lifetime
15
+ // signal here and gets polled against once per second.
16
+ const timer = delay(ms);
17
+ const abort = waitForAbort(signal);
18
+ try {
19
+ await Promise.race([timer.promise, abort.promise]);
20
+ }
21
+ finally {
22
+ timer.cancel();
23
+ abort.cancel();
24
+ }
28
25
  };
29
26
  const EMULATOR_STARTUP_OBSERVATION_TIMEOUT_MS = 5000;
30
27
  const EMULATOR_OUTPUT_BUFFER_LIMIT = 16 * 1024;
@@ -311,9 +308,14 @@ export const createAvd = async ({ name, apiLevel, profile, diskSize, heapSize, }
311
308
  ]);
312
309
  await ensureAvdIniExists({ name, apiLevel });
313
310
  const configPath = await ensureAvdConfigExists(name);
311
+ // hw.cpu.ncore is baked into config.ini (rather than passed as a `-cores`
312
+ // boot flag) so it's part of the AVD's persisted hardware profile: boot
313
+ // snapshots require an identical hardware config to load, so the vCPU
314
+ // count must be fixed at AVD-creation time and stay consistent across
315
+ // boots.
314
316
  await spawn('bash', [
315
317
  '-lc',
316
- `printf '%s\n%s\n' 'disk.dataPartition.size=${diskSize}' 'vm.heapSize=${heapSize}' >> "${configPath}"`,
318
+ `printf '%s\n%s\n%s\n' 'disk.dataPartition.size=${diskSize}' 'vm.heapSize=${heapSize}' 'hw.cpu.ncore=${getEmulatorCpuCores()}' >> "${configPath}"`,
317
319
  ]);
318
320
  };
319
321
  export const deleteAvd = async (name) => {
@@ -375,11 +377,17 @@ export const startEmulator = async (name, mode = 'default-boot') => {
375
377
  }
376
378
  throw error;
377
379
  });
378
- const observationTimeout = wait(EMULATOR_STARTUP_OBSERVATION_TIMEOUT_MS).then(() => 'timeout');
380
+ const observationTimer = delay(EMULATOR_STARTUP_OBSERVATION_TIMEOUT_MS);
381
+ const observationTimeout = observationTimer.promise.then(() => 'timeout');
379
382
  try {
380
383
  await Promise.race([earlyExit, observedBoot, observationTimeout]);
381
384
  }
382
385
  finally {
386
+ // If `earlyExit` or `observedBoot` won the race, this timer would
387
+ // otherwise sit ref'd in the event loop for the rest of its 5s;
388
+ // cancelling here is a no-op on the happy "proceed detached" path
389
+ // where the timer itself won.
390
+ observationTimer.cancel();
383
391
  cleanup();
384
392
  }
385
393
  childProcess.stdout?.destroy();
@@ -497,9 +505,10 @@ export const getLogcatTimestamp = async (adbId) => {
497
505
  ]);
498
506
  return stdout.trim().replace(/^'+|'+$/g, '');
499
507
  };
500
- export const startLogcat = (adbId, args) => spawn(getAdbBinaryPath(), ['-s', adbId, ...args], {
508
+ export const startLogcat = (adbId, args, options) => spawn(getAdbBinaryPath(), ['-s', adbId, ...args], {
501
509
  stdout: 'pipe',
502
510
  stderr: 'pipe',
511
+ signal: options?.signal,
503
512
  });
504
513
  export const DROPBOX_CRASH_TAGS = [
505
514
  'data_app_crash',
@@ -7,11 +7,20 @@ type CreateAndroidAppSessionOptions = {
7
7
  stopApp: () => Promise<void>;
8
8
  getAppPid: () => Promise<number | null>;
9
9
  getLogcatTimestamp: () => Promise<string>;
10
- startLogcat: (args: readonly string[]) => Subprocess;
10
+ startLogcat: (args: readonly string[], options: {
11
+ signal: AbortSignal;
12
+ }) => Subprocess;
11
13
  getDropboxOutput?: () => Promise<string>;
12
14
  getExitInfo?: () => Promise<string>;
13
15
  crashArtifactWriter?: CrashArtifactWriter;
16
+ /**
17
+ * Session-lifetime abort signal (see HarnessPlatformInitOptions.signal).
18
+ * Combined with the app session's own logcatAbortController so that
19
+ * aborting the session stops the logcat stream even if dispose() hasn't
20
+ * been called yet (e.g. SIGTERM during teardown of other resources).
21
+ */
22
+ signal?: AbortSignal;
14
23
  };
15
- export declare const createAndroidAppSession: ({ appUid, bundleId, startApp, stopApp, getAppPid, getLogcatTimestamp, startLogcat, getDropboxOutput, getExitInfo, crashArtifactWriter, }: CreateAndroidAppSessionOptions) => Promise<AppSession>;
24
+ export declare const createAndroidAppSession: ({ appUid, bundleId, startApp, stopApp, getAppPid, getLogcatTimestamp, startLogcat, getDropboxOutput, getExitInfo, crashArtifactWriter, signal, }: CreateAndroidAppSessionOptions) => Promise<AppSession>;
16
25
  export {};
17
26
  //# sourceMappingURL=app-session.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"app-session.d.ts","sourceRoot":"","sources":["../src/app-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,UAAU,EAEf,KAAK,mBAAmB,EACzB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAGL,KAAK,UAAU,EAChB,MAAM,6BAA6B,CAAC;AAkErC,KAAK,8BAA8B,GAAG;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,SAAS,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxC,kBAAkB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,WAAW,EAAE,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,UAAU,CAAC;IACrD,gBAAgB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACpC,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;CAC3C,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAU,0IAW3C,8BAA8B,KAAG,OAAO,CAAC,UAAU,CAsMrD,CAAC"}
1
+ {"version":3,"file":"app-session.d.ts","sourceRoot":"","sources":["../src/app-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,UAAU,EAEf,KAAK,mBAAmB,EACzB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAGL,KAAK,UAAU,EAChB,MAAM,6BAA6B,CAAC;AA0DrC,KAAK,8BAA8B,GAAG;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,SAAS,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxC,kBAAkB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,WAAW,EAAE,CACX,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE;QAAE,MAAM,EAAE,WAAW,CAAA;KAAE,KAC7B,UAAU,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACpC,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C;;;;;OAKG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAU,kJAY3C,8BAA8B,KAAG,OAAO,CAAC,UAAU,CAmNrD,CAAC"}
@@ -30,16 +30,8 @@ const isCrashSignal = (line, bundleId) => {
30
30
  (line.includes(bundleId) &&
31
31
  /fatal|crash|signal 11|signal 6|backtrace/i.test(line)));
32
32
  };
33
- const stopSubprocess = async (child) => {
34
- try {
35
- (await child.nodeChildProcess).kill();
36
- }
37
- catch {
38
- // Ignore termination failures for already-ended background processes.
39
- }
40
- };
41
33
  const isExitedState = (state) => state.status === 'exited';
42
- export const createAndroidAppSession = async ({ appUid, bundleId, startApp, stopApp, getAppPid, getLogcatTimestamp, startLogcat, getDropboxOutput, getExitInfo, crashArtifactWriter, }) => {
34
+ export const createAndroidAppSession = async ({ appUid, bundleId, startApp, stopApp, getAppPid, getLogcatTimestamp, startLogcat, getDropboxOutput, getExitInfo, crashArtifactWriter, signal, }) => {
43
35
  const emitter = createAppSessionEmitter();
44
36
  const logBuffer = createBoundedLogBuffer();
45
37
  let state = { status: 'running' };
@@ -113,7 +105,20 @@ export const createAndroidAppSession = async ({ appUid, bundleId, startApp, stop
113
105
  };
114
106
  const logcatTimestamp = await getLogcatTimestamp();
115
107
  const sessionStartedAt = Date.now();
116
- const logcatProcess = startLogcat(getLogcatArgs(appUid, logcatTimestamp));
108
+ const logcatAbortController = new AbortController();
109
+ const logcatSignal = signal
110
+ ? AbortSignal.any([signal, logcatAbortController.signal])
111
+ : logcatAbortController.signal;
112
+ const logcatProcess = startLogcat(getLogcatArgs(appUid, logcatTimestamp), {
113
+ signal: logcatSignal,
114
+ });
115
+ // Aborting is nano-spawn's own cancellation path, so the resulting
116
+ // SubprocessError is settled the same way regardless of which of the
117
+ // merged stdout/stderr iterators observes it first. Without this, killing
118
+ // the underlying process directly can make both iterators reject at once,
119
+ // and nano-spawn's internal `Promise.race` only reports one of them,
120
+ // leaving the other an unhandled rejection.
121
+ logcatProcess.catch(() => undefined);
117
122
  const crashReporter = createAndroidCrashReporter({
118
123
  bundleId,
119
124
  crashArtifactWriter,
@@ -156,7 +161,7 @@ export const createAndroidAppSession = async ({ appUid, bundleId, startApp, stop
156
161
  disposed = true;
157
162
  stopPolling = true;
158
163
  emitter.clear();
159
- await stopSubprocess(logcatProcess);
164
+ logcatAbortController.abort();
160
165
  await Promise.allSettled([logTask]);
161
166
  throw error;
162
167
  }
@@ -196,7 +201,7 @@ export const createAndroidAppSession = async ({ appUid, bundleId, startApp, stop
196
201
  }
197
202
  cancelPendingPollDelay();
198
203
  emitter.clear();
199
- await stopSubprocess(logcatProcess);
204
+ logcatAbortController.abort();
200
205
  await stopApp();
201
206
  await Promise.allSettled([logTask, pollTask]);
202
207
  },
@@ -6,6 +6,7 @@ export type AvdConfig = {
6
6
  hwDeviceName?: string;
7
7
  diskDataPartitionSize?: string;
8
8
  vmHeapSize?: string;
9
+ hwCpuNcore?: string;
9
10
  };
10
11
  export type AvdCompatibilityResult = {
11
12
  compatible: true;
@@ -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;AAE7E,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;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,SAyCjD,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,sBAmFH,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":"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,4 +1,5 @@
1
1
  import { access, readFile } from 'node:fs/promises';
2
+ import { getEmulatorCpuCores } from './emulator-startup.js';
2
3
  export const getAvdDirectory = (name) => {
3
4
  return `${process.env.ANDROID_AVD_HOME ?? `${process.env.HOME}/.android/avd`}/${name}.avd`;
4
5
  };
@@ -81,6 +82,9 @@ export const parseAvdConfig = (contents) => {
81
82
  case 'vm.heapSize':
82
83
  config.vmHeapSize = value;
83
84
  break;
85
+ case 'hw.cpu.ncore':
86
+ config.hwCpuNcore = value;
87
+ break;
84
88
  default:
85
89
  break;
86
90
  }
@@ -146,6 +150,13 @@ export const isAvdCompatible = ({ emulator, avdConfig, hostArch, }) => {
146
150
  reason: `Heap size mismatch: expected ${requestedAvdConfig.heapSize}, got ${avdConfig.vmHeapSize ?? 'missing'}.`,
147
151
  };
148
152
  }
153
+ if (normalizeConfigValue(avdConfig.hwCpuNcore ?? '') !==
154
+ normalizeConfigValue(String(getEmulatorCpuCores()))) {
155
+ return {
156
+ compatible: false,
157
+ reason: `CPU core count mismatch: expected ${getEmulatorCpuCores()}, got ${avdConfig.hwCpuNcore ?? 'missing'}.`,
158
+ };
159
+ }
149
160
  return { compatible: true };
150
161
  };
151
162
  export const getNormalizedAvdCacheConfig = ({ emulator, hostArch, }) => {
@@ -1,3 +1,26 @@
1
1
  export type EmulatorBootMode = 'default-boot' | 'clean-snapshot-generation' | 'snapshot-reuse';
2
+ /**
3
+ * Returns the vCPU count baked into an AVD for this runner.
4
+ *
5
+ * AVD snapshots require an identical hardware configuration to load, so all
6
+ * AVD creation, compatibility, and cache-key callers must use this value on
7
+ * the same host.
8
+ */
9
+ export declare const getEmulatorCpuCores: () => number;
2
10
  export declare const getEmulatorStartupArgs: (name: string, mode: EmulatorBootMode) => string[];
11
+ /**
12
+ * Parses the output of `emulator -accel-check` to determine whether
13
+ * hardware acceleration (KVM on Linux, HVF on macOS) is usable. The
14
+ * emulator prints a message containing "is installed and usable" when
15
+ * acceleration is available.
16
+ */
17
+ export declare const isAccelerationUsable: (accelCheckOutput: string) => boolean;
18
+ /**
19
+ * Runs `emulator -accel-check` and logs a non-fatal warning when hardware
20
+ * acceleration (KVM/HVF) appears unavailable, in which case the emulator
21
+ * falls back to very slow software emulation. This check must never fail or
22
+ * block emulator startup - any error running it is swallowed and only
23
+ * debug-logged.
24
+ */
25
+ export declare const warnIfEmulatorAccelerationUnavailable: () => Promise<void>;
3
26
  //# sourceMappingURL=emulator-startup.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"emulator-startup.d.ts","sourceRoot":"","sources":["../src/emulator-startup.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GACxB,cAAc,GACd,2BAA2B,GAC3B,gBAAgB,CAAC;AAUrB,eAAO,MAAM,sBAAsB,GACjC,MAAM,MAAM,EACZ,MAAM,gBAAgB,KACrB,MAAM,EASR,CAAC"}
1
+ {"version":3,"file":"emulator-startup.d.ts","sourceRoot":"","sources":["../src/emulator-startup.ts"],"names":[],"mappings":"AAWA,MAAM,MAAM,gBAAgB,GACxB,cAAc,GACd,2BAA2B,GAC3B,gBAAgB,CAAC;AAErB;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,QAAO,MAEtC,CAAC;AAWF,eAAO,MAAM,sBAAsB,GACjC,MAAM,MAAM,EACZ,MAAM,gBAAgB,KACrB,MAAM,EASR,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAAI,kBAAkB,MAAM,KAAG,OAE/D,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,qCAAqC,QACtC,OAAO,CAAC,IAAI,CA+BrB,CAAC"}
@@ -1,9 +1,24 @@
1
+ import { getDeviceCoreBudget, logger, spawn, SubprocessError, } from '@react-native-harness/tools';
2
+ import os from 'node:os';
3
+ import { getEmulatorBinaryPath } from './environment.js';
4
+ const emulatorStartupLogger = logger.child('android-emulator-startup');
5
+ /**
6
+ * Returns the vCPU count baked into an AVD for this runner.
7
+ *
8
+ * AVD snapshots require an identical hardware configuration to load, so all
9
+ * AVD creation, compatibility, and cache-key callers must use this value on
10
+ * the same host.
11
+ */
12
+ export const getEmulatorCpuCores = () => {
13
+ return getDeviceCoreBudget(os.availableParallelism());
14
+ };
1
15
  const COMMON_EMULATOR_ARGS = [
2
16
  '-no-window',
3
17
  '-gpu',
4
18
  'swiftshader_indirect',
5
19
  '-noaudio',
6
20
  '-no-boot-anim',
21
+ '-no-metrics',
7
22
  ];
8
23
  export const getEmulatorStartupArgs = (name, mode) => {
9
24
  const modeArgs = mode === 'clean-snapshot-generation'
@@ -13,3 +28,46 @@ export const getEmulatorStartupArgs = (name, mode) => {
13
28
  : ['-no-snapshot-load', '-no-snapshot-save'];
14
29
  return [`@${name}`, ...modeArgs, ...COMMON_EMULATOR_ARGS];
15
30
  };
31
+ /**
32
+ * Parses the output of `emulator -accel-check` to determine whether
33
+ * hardware acceleration (KVM on Linux, HVF on macOS) is usable. The
34
+ * emulator prints a message containing "is installed and usable" when
35
+ * acceleration is available.
36
+ */
37
+ export const isAccelerationUsable = (accelCheckOutput) => {
38
+ return accelCheckOutput.includes('is installed and usable');
39
+ };
40
+ /**
41
+ * Runs `emulator -accel-check` and logs a non-fatal warning when hardware
42
+ * acceleration (KVM/HVF) appears unavailable, in which case the emulator
43
+ * falls back to very slow software emulation. This check must never fail or
44
+ * block emulator startup - any error running it is swallowed and only
45
+ * debug-logged.
46
+ */
47
+ export const warnIfEmulatorAccelerationUnavailable = async () => {
48
+ let combinedOutput;
49
+ try {
50
+ const emulatorBinaryPath = getEmulatorBinaryPath();
51
+ const { stdout, stderr } = await spawn(emulatorBinaryPath, [
52
+ '-accel-check',
53
+ ]);
54
+ combinedOutput = `${stdout}\n${stderr}`;
55
+ }
56
+ catch (error) {
57
+ if (error instanceof SubprocessError) {
58
+ // The check itself ran but exited non-zero - this is how
59
+ // `-accel-check` reports that acceleration is unavailable, so treat
60
+ // its output as a normal (non-fatal) result rather than swallowing it.
61
+ combinedOutput = `${error.stdout}\n${error.stderr}`;
62
+ }
63
+ else {
64
+ // Failed to even run the check (e.g. binary missing). This must never
65
+ // fail or block emulator startup.
66
+ emulatorStartupLogger.debug('Failed to run "emulator -accel-check": %o', error);
67
+ return;
68
+ }
69
+ }
70
+ if (!isAccelerationUsable(combinedOutput)) {
71
+ logger.warn('Hardware acceleration (KVM/HVF) appears unavailable for the Android emulator. It will fall back to very slow software emulation.');
72
+ }
73
+ };
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export { androidEmulator, physicalAndroidDevice, androidPlatform, } from './fact
2
2
  export type { AndroidPlatformConfig } from './config.js';
3
3
  export { getNormalizedAvdCacheConfig, resolveAvdCachingEnabled, } from './avd-config.js';
4
4
  export { getHostAndroidSystemImageArch } from './environment.js';
5
+ export { getEmulatorCpuCores } from './emulator-startup.js';
5
6
  export { HarnessAppPathError, HarnessEmulatorConfigError } from './errors.js';
6
7
  export { getRunTargets } from './targets.js';
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,eAAe,GAChB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EACL,2BAA2B,EAC3B,wBAAwB,GACzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,6BAA6B,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,eAAe,GAChB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EACL,2BAA2B,EAC3B,wBAAwB,GACzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,6BAA6B,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { androidEmulator, physicalAndroidDevice, androidPlatform, } from './factory.js';
2
2
  export { getNormalizedAvdCacheConfig, resolveAvdCachingEnabled, } from './avd-config.js';
3
3
  export { getHostAndroidSystemImageArch } from './environment.js';
4
+ export { getEmulatorCpuCores } from './emulator-startup.js';
4
5
  export { HarnessAppPathError, HarnessEmulatorConfigError } from './errors.js';
5
6
  export { getRunTargets } from './targets.js';
@@ -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;AAuKrB,eAAO,MAAM,kCAAkC,GAC7C,QAAQ,qBAAqB,EAC7B,eAAe,aAAa,EAC5B,MAAM,0BAA0B,KAC/B,OAAO,CAAC,qBAAqB,CA8H/B,CAAC;AAEF,eAAO,MAAM,wCAAwC,GACnD,QAAQ,qBAAqB,EAC7B,eAAe,aAAa,EAC5B,OAAO,0BAA0B,KAChC,OAAO,CAAC,qBAAqB,CAyD/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;AAyKrB,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
@@ -1,13 +1,14 @@
1
1
  import { AppNotInstalledError, DeviceNotFoundError, } from '@react-native-harness/platforms';
2
- import { logger } from '@react-native-harness/tools';
2
+ import { instrumented, logger, noopDiagnostics } from '@react-native-harness/tools';
3
3
  import { assertAndroidDeviceEmulator, assertAndroidDevicePhysical, } from './config.js';
4
4
  import { isAvdCompatible, readAvdConfig, resolveAvdCachingEnabled, } from './avd-config.js';
5
5
  import { getAdbId } from './adb-id.js';
6
- import * as adb from './adb.js';
6
+ import * as adbModule from './adb.js';
7
7
  import { applyHarnessDebugHttpHost, clearHarnessDebugHttpHost, } from './shared-prefs.js';
8
8
  import { getDeviceName } from './utils.js';
9
9
  import { HarnessAppPathError, HarnessEmulatorConfigError } from './errors.js';
10
10
  import { ensureAndroidEmulatorAvailable, ensureAndroidEmulatorEnvironment, getHostAndroidSystemImageArch, } from './environment.js';
11
+ import { warnIfEmulatorAccelerationUnavailable } from './emulator-startup.js';
11
12
  import { isInteractive } from '@react-native-harness/tools';
12
13
  import fs from 'node:fs';
13
14
  import { createAndroidAppSession } from './app-session.js';
@@ -35,23 +36,24 @@ const getOptionalHarnessAppPath = () => {
35
36
  const configureAndroidRuntime = async (adbId, config, harnessConfig) => {
36
37
  const metroPort = harnessConfig.metroPort;
37
38
  await Promise.all([
38
- adb.reversePort(adbId, metroPort),
39
- adb.reversePort(adbId, 8080),
40
- adb.setHideErrorDialogs(adbId, true),
39
+ adbModule.reversePort(adbId, metroPort),
40
+ adbModule.reversePort(adbId, 8080),
41
+ adbModule.setHideErrorDialogs(adbId, true),
41
42
  applyHarnessDebugHttpHost(adbId, config.bundleId, `localhost:${metroPort}`),
42
43
  ]);
43
- return adb.getAppUid(adbId, config.bundleId);
44
+ return adbModule.getAppUid(adbId, config.bundleId);
44
45
  };
45
46
  const startAndWaitForBoot = async ({ emulatorName, signal, mode, }) => {
46
47
  await ensureAndroidEmulatorAvailable();
47
- await adb.startEmulator(emulatorName, mode);
48
- return adb.waitForBoot(emulatorName, signal);
48
+ await warnIfEmulatorAccelerationUnavailable();
49
+ await adbModule.startEmulator(emulatorName, mode);
50
+ return adbModule.waitForBoot(emulatorName, signal);
49
51
  };
50
52
  const recreateAvd = async ({ emulatorConfig, }) => {
51
53
  if (!emulatorConfig.avd) {
52
54
  throw new HarnessEmulatorConfigError(emulatorConfig.name);
53
55
  }
54
- await adb.createAvd({
56
+ await adbModule.createAvd({
55
57
  name: emulatorConfig.name,
56
58
  apiLevel: emulatorConfig.avd.apiLevel,
57
59
  profile: emulatorConfig.avd.profile,
@@ -62,7 +64,7 @@ const recreateAvd = async ({ emulatorConfig, }) => {
62
64
  const prepareCachedAvd = async ({ emulatorConfig, signal, }) => {
63
65
  const emulatorName = emulatorConfig.name;
64
66
  const hostArch = getHostAndroidSystemImageArch();
65
- const hasExistingAvd = await adb.hasAvd(emulatorName);
67
+ const hasExistingAvd = await adbModule.hasAvd(emulatorName);
66
68
  const avdConfig = hasExistingAvd ? await readAvdConfig(emulatorName) : null;
67
69
  const compatibility = avdConfig == null
68
70
  ? { compatible: false, reason: 'Missing AVD config.ini.' }
@@ -77,7 +79,7 @@ const prepareCachedAvd = async ({ emulatorConfig, signal, }) => {
77
79
  : 'Creating Android emulator %s...', emulatorName);
78
80
  if (hasExistingAvd && !compatibility.compatible) {
79
81
  androidInstanceLogger.debug('Android AVD %s is not reusable: %s', emulatorName, compatibility.reason);
80
- await adb.deleteAvd(emulatorName);
82
+ await adbModule.deleteAvd(emulatorName);
81
83
  }
82
84
  await recreateAvd({ emulatorConfig });
83
85
  const generationAdbId = await startAndWaitForBoot({
@@ -86,8 +88,8 @@ const prepareCachedAvd = async ({ emulatorConfig, signal, }) => {
86
88
  mode: 'clean-snapshot-generation',
87
89
  });
88
90
  logger.info('Saving Android emulator snapshot for %s...', emulatorName);
89
- await adb.stopEmulator(generationAdbId);
90
- await adb.waitForEmulatorDisconnect(generationAdbId, signal);
91
+ await adbModule.stopEmulator(generationAdbId);
92
+ await adbModule.waitForEmulatorDisconnect(generationAdbId, signal);
91
93
  }
92
94
  else {
93
95
  logger.info('Using cached Android emulator %s...', emulatorName);
@@ -100,6 +102,7 @@ const prepareCachedAvd = async ({ emulatorConfig, signal, }) => {
100
102
  };
101
103
  export const getAndroidEmulatorPlatformInstance = async (config, harnessConfig, init) => {
102
104
  assertAndroidDeviceEmulator(config.device);
105
+ const adb = instrumented(adbModule, 'platform.android.adb', init.diagnostics ?? noopDiagnostics);
103
106
  const permissionsEnabled = harnessConfig.permissions ?? false;
104
107
  const emulatorConfig = config.device;
105
108
  const emulatorName = emulatorConfig.name;
@@ -169,10 +172,11 @@ export const getAndroidEmulatorPlatformInstance = async (config, harnessConfig,
169
172
  stopApp: () => adb.stopApp(adbId, config.bundleId),
170
173
  getAppPid: () => adb.getAppPid(adbId, config.bundleId),
171
174
  getLogcatTimestamp: () => adb.getLogcatTimestamp(adbId),
172
- startLogcat: (args) => adb.startLogcat(adbId, args),
175
+ startLogcat: (args, options) => adb.startLogcat(adbId, args, options),
173
176
  getDropboxOutput: () => adb.getDropboxPrint(adbId),
174
177
  getExitInfo: () => adb.getActivityExitInfo(adbId, config.bundleId),
175
178
  crashArtifactWriter: init.crashArtifactWriter,
179
+ signal: init.signal,
176
180
  });
177
181
  },
178
182
  dispose: async () => {
@@ -188,6 +192,7 @@ export const getAndroidEmulatorPlatformInstance = async (config, harnessConfig,
188
192
  };
189
193
  export const getAndroidPhysicalDevicePlatformInstance = async (config, harnessConfig, init) => {
190
194
  assertAndroidDevicePhysical(config.device);
195
+ const adb = instrumented(adbModule, 'platform.android.adb', init?.diagnostics ?? noopDiagnostics);
191
196
  const permissionsEnabled = harnessConfig.permissions ?? false;
192
197
  const adbId = await getAdbId(config.device);
193
198
  if (!adbId) {
@@ -213,10 +218,11 @@ export const getAndroidPhysicalDevicePlatformInstance = async (config, harnessCo
213
218
  stopApp: () => adb.stopApp(adbId, config.bundleId),
214
219
  getAppPid: () => adb.getAppPid(adbId, config.bundleId),
215
220
  getLogcatTimestamp: () => adb.getLogcatTimestamp(adbId),
216
- startLogcat: (args) => adb.startLogcat(adbId, args),
221
+ startLogcat: (args, options) => adb.startLogcat(adbId, args, options),
217
222
  getDropboxOutput: () => adb.getDropboxPrint(adbId),
218
223
  getExitInfo: () => adb.getActivityExitInfo(adbId, config.bundleId),
219
224
  crashArtifactWriter: init?.crashArtifactWriter,
225
+ signal: init?.signal,
220
226
  });
221
227
  },
222
228
  dispose: async () => {
package/dist/runner.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { HarnessPlatformRunner, type HarnessPlatformInitOptions } from '@react-native-harness/platforms';
1
+ import { type HarnessPlatformRunnerFactory } from '@react-native-harness/platforms';
2
2
  import type { Config as HarnessConfig } from '@react-native-harness/config';
3
3
  import { type AndroidPlatformConfig } from './config.js';
4
- declare const getAndroidRunner: (config: AndroidPlatformConfig, harnessConfig: HarnessConfig, init: HarnessPlatformInitOptions) => Promise<HarnessPlatformRunner>;
4
+ declare const getAndroidRunner: HarnessPlatformRunnerFactory<AndroidPlatformConfig, HarnessConfig>;
5
5
  export default getAndroidRunner;
6
6
  //# sourceMappingURL=runner.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,KAAK,0BAA0B,EAChC,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC5E,OAAO,EAEL,KAAK,qBAAqB,EAE3B,MAAM,aAAa,CAAC;AAUrB,QAAA,MAAM,gBAAgB,GACpB,QAAQ,qBAAqB,EAC7B,eAAe,aAAa,EAC5B,MAAM,0BAA0B,KAC/B,OAAO,CAAC,qBAAqB,CAoB/B,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,4BAA4B,EAAE,MAAM,iCAAiC,CAAC;AACpF,OAAO,KAAK,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC5E,OAAO,EAEL,KAAK,qBAAqB,EAE3B,MAAM,aAAa,CAAC;AAUrB,QAAA,MAAM,gBAAgB,EAAE,4BAA4B,CAClD,qBAAqB,EACrB,aAAa,CAqBd,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
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.4.0-rc.1",
4
+ "version": "1.4.1",
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/platforms": "1.4.0-rc.1",
23
- "@react-native-harness/config": "1.4.0-rc.1",
24
- "@react-native-harness/tools": "1.4.0-rc.1"
22
+ "@react-native-harness/config": "1.4.1",
23
+ "@react-native-harness/platforms": "1.4.1",
24
+ "@react-native-harness/tools": "1.4.1"
25
25
  },
26
26
  "license": "MIT"
27
27
  }