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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +9 -2
  2. package/dist/__tests__/adb.test.js +283 -10
  3. package/dist/__tests__/avd-config.test.d.ts +2 -0
  4. package/dist/__tests__/avd-config.test.d.ts.map +1 -0
  5. package/dist/__tests__/avd-config.test.js +174 -0
  6. package/dist/__tests__/ci-action.test.d.ts +2 -0
  7. package/dist/__tests__/ci-action.test.d.ts.map +1 -0
  8. package/dist/__tests__/ci-action.test.js +46 -0
  9. package/dist/__tests__/emulator-startup.test.d.ts +2 -0
  10. package/dist/__tests__/emulator-startup.test.d.ts.map +1 -0
  11. package/dist/__tests__/emulator-startup.test.js +19 -0
  12. package/dist/__tests__/environment.test.d.ts +2 -0
  13. package/dist/__tests__/environment.test.d.ts.map +1 -0
  14. package/dist/__tests__/environment.test.js +51 -0
  15. package/dist/__tests__/instance.test.d.ts +2 -0
  16. package/dist/__tests__/instance.test.d.ts.map +1 -0
  17. package/dist/__tests__/instance.test.js +423 -0
  18. package/dist/adb.d.ts +23 -0
  19. package/dist/adb.d.ts.map +1 -1
  20. package/dist/adb.js +265 -16
  21. package/dist/app-monitor.d.ts.map +1 -1
  22. package/dist/app-monitor.js +27 -7
  23. package/dist/assertions.d.ts +5 -0
  24. package/dist/assertions.d.ts.map +1 -0
  25. package/dist/assertions.js +6 -0
  26. package/dist/avd-config.d.ts +41 -0
  27. package/dist/avd-config.d.ts.map +1 -0
  28. package/dist/avd-config.js +173 -0
  29. package/dist/config.d.ts +77 -0
  30. package/dist/config.d.ts.map +1 -1
  31. package/dist/config.js +5 -0
  32. package/dist/emulator-startup.d.ts +3 -0
  33. package/dist/emulator-startup.d.ts.map +1 -0
  34. package/dist/emulator-startup.js +17 -0
  35. package/dist/emulator.d.ts +6 -0
  36. package/dist/emulator.d.ts.map +1 -0
  37. package/dist/emulator.js +27 -0
  38. package/dist/environment.d.ts +28 -0
  39. package/dist/environment.d.ts.map +1 -0
  40. package/dist/environment.js +295 -0
  41. package/dist/errors.d.ts +7 -0
  42. package/dist/errors.d.ts.map +1 -0
  43. package/dist/errors.js +14 -0
  44. package/dist/factory.d.ts.map +1 -1
  45. package/dist/factory.js +3 -0
  46. package/dist/index.d.ts +3 -0
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +3 -0
  49. package/dist/instance.d.ts +6 -0
  50. package/dist/instance.d.ts.map +1 -0
  51. package/dist/instance.js +234 -0
  52. package/dist/reader.d.ts +6 -0
  53. package/dist/reader.d.ts.map +1 -0
  54. package/dist/reader.js +57 -0
  55. package/dist/runner.d.ts +2 -2
  56. package/dist/runner.d.ts.map +1 -1
  57. package/dist/runner.js +12 -52
  58. package/dist/targets.d.ts +1 -1
  59. package/dist/targets.d.ts.map +1 -1
  60. package/dist/targets.js +2 -0
  61. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  62. package/dist/types.d.ts +381 -0
  63. package/dist/types.d.ts.map +1 -0
  64. package/dist/types.js +107 -0
  65. package/package.json +4 -4
  66. package/src/__tests__/adb.test.ts +419 -15
  67. package/src/__tests__/avd-config.test.ts +206 -0
  68. package/src/__tests__/ci-action.test.ts +81 -0
  69. package/src/__tests__/emulator-startup.test.ts +32 -0
  70. package/src/__tests__/environment.test.ts +87 -0
  71. package/src/__tests__/instance.test.ts +610 -0
  72. package/src/adb.ts +423 -16
  73. package/src/app-monitor.ts +56 -18
  74. package/src/avd-config.ts +290 -0
  75. package/src/config.ts +8 -0
  76. package/src/emulator-startup.ts +28 -0
  77. package/src/environment.ts +510 -0
  78. package/src/errors.ts +19 -0
  79. package/src/factory.ts +4 -0
  80. package/src/index.ts +7 -1
  81. package/src/instance.ts +380 -0
  82. package/src/runner.ts +23 -69
  83. package/src/targets.ts +11 -8
package/dist/config.d.ts CHANGED
@@ -11,16 +11,29 @@ export declare const AndroidEmulatorAVDConfigSchema: z.ZodObject<{
11
11
  profile: z.ZodString;
12
12
  diskSize: z.ZodDefault<z.ZodString>;
13
13
  heapSize: z.ZodDefault<z.ZodString>;
14
+ snapshot: z.ZodOptional<z.ZodObject<{
15
+ enabled: z.ZodOptional<z.ZodBoolean>;
16
+ }, "strip", z.ZodTypeAny, {
17
+ enabled?: boolean | undefined;
18
+ }, {
19
+ enabled?: boolean | undefined;
20
+ }>>;
14
21
  }, "strip", z.ZodTypeAny, {
15
22
  apiLevel: number;
16
23
  profile: string;
17
24
  diskSize: string;
18
25
  heapSize: string;
26
+ snapshot?: {
27
+ enabled?: boolean | undefined;
28
+ } | undefined;
19
29
  }, {
20
30
  apiLevel: number;
21
31
  profile: string;
22
32
  diskSize?: string | undefined;
23
33
  heapSize?: string | undefined;
34
+ snapshot?: {
35
+ enabled?: boolean | undefined;
36
+ } | undefined;
24
37
  }>;
25
38
  export declare const AndroidEmulatorSchema: z.ZodObject<{
26
39
  type: z.ZodLiteral<"emulator">;
@@ -30,16 +43,29 @@ export declare const AndroidEmulatorSchema: z.ZodObject<{
30
43
  profile: z.ZodString;
31
44
  diskSize: z.ZodDefault<z.ZodString>;
32
45
  heapSize: z.ZodDefault<z.ZodString>;
46
+ snapshot: z.ZodOptional<z.ZodObject<{
47
+ enabled: z.ZodOptional<z.ZodBoolean>;
48
+ }, "strip", z.ZodTypeAny, {
49
+ enabled?: boolean | undefined;
50
+ }, {
51
+ enabled?: boolean | undefined;
52
+ }>>;
33
53
  }, "strip", z.ZodTypeAny, {
34
54
  apiLevel: number;
35
55
  profile: string;
36
56
  diskSize: string;
37
57
  heapSize: string;
58
+ snapshot?: {
59
+ enabled?: boolean | undefined;
60
+ } | undefined;
38
61
  }, {
39
62
  apiLevel: number;
40
63
  profile: string;
41
64
  diskSize?: string | undefined;
42
65
  heapSize?: string | undefined;
66
+ snapshot?: {
67
+ enabled?: boolean | undefined;
68
+ } | undefined;
43
69
  }>>;
44
70
  }, "strip", z.ZodTypeAny, {
45
71
  name: string;
@@ -49,6 +75,9 @@ export declare const AndroidEmulatorSchema: z.ZodObject<{
49
75
  profile: string;
50
76
  diskSize: string;
51
77
  heapSize: string;
78
+ snapshot?: {
79
+ enabled?: boolean | undefined;
80
+ } | undefined;
52
81
  } | undefined;
53
82
  }, {
54
83
  name: string;
@@ -58,6 +87,9 @@ export declare const AndroidEmulatorSchema: z.ZodObject<{
58
87
  profile: string;
59
88
  diskSize?: string | undefined;
60
89
  heapSize?: string | undefined;
90
+ snapshot?: {
91
+ enabled?: boolean | undefined;
92
+ } | undefined;
61
93
  } | undefined;
62
94
  }>;
63
95
  export declare const PhysicalAndroidDeviceSchema: z.ZodObject<{
@@ -81,16 +113,29 @@ export declare const AndroidDeviceSchema: z.ZodDiscriminatedUnion<"type", [z.Zod
81
113
  profile: z.ZodString;
82
114
  diskSize: z.ZodDefault<z.ZodString>;
83
115
  heapSize: z.ZodDefault<z.ZodString>;
116
+ snapshot: z.ZodOptional<z.ZodObject<{
117
+ enabled: z.ZodOptional<z.ZodBoolean>;
118
+ }, "strip", z.ZodTypeAny, {
119
+ enabled?: boolean | undefined;
120
+ }, {
121
+ enabled?: boolean | undefined;
122
+ }>>;
84
123
  }, "strip", z.ZodTypeAny, {
85
124
  apiLevel: number;
86
125
  profile: string;
87
126
  diskSize: string;
88
127
  heapSize: string;
128
+ snapshot?: {
129
+ enabled?: boolean | undefined;
130
+ } | undefined;
89
131
  }, {
90
132
  apiLevel: number;
91
133
  profile: string;
92
134
  diskSize?: string | undefined;
93
135
  heapSize?: string | undefined;
136
+ snapshot?: {
137
+ enabled?: boolean | undefined;
138
+ } | undefined;
94
139
  }>>;
95
140
  }, "strip", z.ZodTypeAny, {
96
141
  name: string;
@@ -100,6 +145,9 @@ export declare const AndroidDeviceSchema: z.ZodDiscriminatedUnion<"type", [z.Zod
100
145
  profile: string;
101
146
  diskSize: string;
102
147
  heapSize: string;
148
+ snapshot?: {
149
+ enabled?: boolean | undefined;
150
+ } | undefined;
103
151
  } | undefined;
104
152
  }, {
105
153
  name: string;
@@ -109,6 +157,9 @@ export declare const AndroidDeviceSchema: z.ZodDiscriminatedUnion<"type", [z.Zod
109
157
  profile: string;
110
158
  diskSize?: string | undefined;
111
159
  heapSize?: string | undefined;
160
+ snapshot?: {
161
+ enabled?: boolean | undefined;
162
+ } | undefined;
112
163
  } | undefined;
113
164
  }>, z.ZodObject<{
114
165
  type: z.ZodLiteral<"physical">;
@@ -133,16 +184,29 @@ export declare const AndroidPlatformConfigSchema: z.ZodObject<{
133
184
  profile: z.ZodString;
134
185
  diskSize: z.ZodDefault<z.ZodString>;
135
186
  heapSize: z.ZodDefault<z.ZodString>;
187
+ snapshot: z.ZodOptional<z.ZodObject<{
188
+ enabled: z.ZodOptional<z.ZodBoolean>;
189
+ }, "strip", z.ZodTypeAny, {
190
+ enabled?: boolean | undefined;
191
+ }, {
192
+ enabled?: boolean | undefined;
193
+ }>>;
136
194
  }, "strip", z.ZodTypeAny, {
137
195
  apiLevel: number;
138
196
  profile: string;
139
197
  diskSize: string;
140
198
  heapSize: string;
199
+ snapshot?: {
200
+ enabled?: boolean | undefined;
201
+ } | undefined;
141
202
  }, {
142
203
  apiLevel: number;
143
204
  profile: string;
144
205
  diskSize?: string | undefined;
145
206
  heapSize?: string | undefined;
207
+ snapshot?: {
208
+ enabled?: boolean | undefined;
209
+ } | undefined;
146
210
  }>>;
147
211
  }, "strip", z.ZodTypeAny, {
148
212
  name: string;
@@ -152,6 +216,9 @@ export declare const AndroidPlatformConfigSchema: z.ZodObject<{
152
216
  profile: string;
153
217
  diskSize: string;
154
218
  heapSize: string;
219
+ snapshot?: {
220
+ enabled?: boolean | undefined;
221
+ } | undefined;
155
222
  } | undefined;
156
223
  }, {
157
224
  name: string;
@@ -161,6 +228,9 @@ export declare const AndroidPlatformConfigSchema: z.ZodObject<{
161
228
  profile: string;
162
229
  diskSize?: string | undefined;
163
230
  heapSize?: string | undefined;
231
+ snapshot?: {
232
+ enabled?: boolean | undefined;
233
+ } | undefined;
164
234
  } | undefined;
165
235
  }>, z.ZodObject<{
166
236
  type: z.ZodLiteral<"physical">;
@@ -194,6 +264,9 @@ export declare const AndroidPlatformConfigSchema: z.ZodObject<{
194
264
  profile: string;
195
265
  diskSize: string;
196
266
  heapSize: string;
267
+ snapshot?: {
268
+ enabled?: boolean | undefined;
269
+ } | undefined;
197
270
  } | undefined;
198
271
  } | {
199
272
  manufacturer: string;
@@ -215,6 +288,9 @@ export declare const AndroidPlatformConfigSchema: z.ZodObject<{
215
288
  profile: string;
216
289
  diskSize?: string | undefined;
217
290
  heapSize?: string | undefined;
291
+ snapshot?: {
292
+ enabled?: boolean | undefined;
293
+ } | undefined;
218
294
  } | undefined;
219
295
  } | {
220
296
  manufacturer: string;
@@ -233,6 +309,7 @@ export type AndroidDevice = z.infer<typeof AndroidDeviceSchema>;
233
309
  export type AndroidPlatformConfig = z.infer<typeof AndroidPlatformConfigSchema>;
234
310
  export type AndroidAppLaunchOptions = z.infer<typeof AndroidAppLaunchOptionsSchema>;
235
311
  export type AndroidEmulatorAVDConfig = z.infer<typeof AndroidEmulatorAVDConfigSchema>;
312
+ export type AndroidEmulatorAVDSnapshotConfig = NonNullable<AndroidEmulatorAVDConfig['snapshot']>;
236
313
  export declare const isAndroidDeviceEmulator: (device: AndroidDevice) => device is AndroidEmulator;
237
314
  export declare const isAndroidDevicePhysical: (device: AndroidDevice) => device is PhysicalAndroidDevice;
238
315
  export declare function assertAndroidDeviceEmulator(device: AndroidDevice): asserts device is AndroidEmulator;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,6BAA6B;;;;;;EAIxC,CAAC;AAEH,eAAO,MAAM,8BAA8B;;;;;;;;;;;;;;;EAKzC,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAIhC,CAAC;AAEH,eAAO,MAAM,2BAA2B;;;;;;;;;;;;EAItC,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAG9B,CAAC;AAEH,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAStC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACpE,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAChF,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAChE,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAChF,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAC3C,OAAO,6BAA6B,CACrC,CAAC;AACF,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAC5C,OAAO,8BAA8B,CACtC,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,QAAQ,aAAa,KACpB,MAAM,IAAI,eAEZ,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,QAAQ,aAAa,KACpB,MAAM,IAAI,qBAEZ,CAAC;AAEF,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,MAAM,IAAI,eAAe,CAInC;AAED,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,MAAM,IAAI,qBAAqB,CAIzC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,6BAA6B;;;;;;EAIxC,CAAC;AAEH,eAAO,MAAM,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAUzC,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAIhC,CAAC;AAEH,eAAO,MAAM,2BAA2B;;;;;;;;;;;;EAItC,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAG9B,CAAC;AAEH,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAStC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACpE,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAChF,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAChE,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAChF,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAC3C,OAAO,6BAA6B,CACrC,CAAC;AACF,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAC5C,OAAO,8BAA8B,CACtC,CAAC;AACF,MAAM,MAAM,gCAAgC,GAAG,WAAW,CACxD,wBAAwB,CAAC,UAAU,CAAC,CACrC,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,QAAQ,aAAa,KACpB,MAAM,IAAI,eAEZ,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,QAAQ,aAAa,KACpB,MAAM,IAAI,qBAEZ,CAAC;AAEF,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,MAAM,IAAI,eAAe,CAInC;AAED,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,MAAM,IAAI,qBAAqB,CAIzC"}
package/dist/config.js CHANGED
@@ -9,6 +9,11 @@ export const AndroidEmulatorAVDConfigSchema = z.object({
9
9
  profile: z.string().min(1, 'Profile is required'),
10
10
  diskSize: z.string().min(1, 'Disk size is required').default('1G'),
11
11
  heapSize: z.string().min(1, 'Heap size is required').default('1G'),
12
+ snapshot: z
13
+ .object({
14
+ enabled: z.boolean().optional(),
15
+ })
16
+ .optional(),
12
17
  });
13
18
  export const AndroidEmulatorSchema = z.object({
14
19
  type: z.literal('emulator'),
@@ -0,0 +1,3 @@
1
+ export type EmulatorBootMode = 'default-boot' | 'clean-snapshot-generation' | 'snapshot-reuse';
2
+ export declare const getEmulatorStartupArgs: (name: string, mode: EmulatorBootMode) => string[];
3
+ //# sourceMappingURL=emulator-startup.d.ts.map
@@ -0,0 +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;AAYrB,eAAO,MAAM,sBAAsB,GACjC,MAAM,MAAM,EACZ,MAAM,gBAAgB,KACrB,MAAM,EASR,CAAC"}
@@ -0,0 +1,17 @@
1
+ const COMMON_EMULATOR_ARGS = [
2
+ '-no-window',
3
+ '-gpu',
4
+ 'swiftshader_indirect',
5
+ '-noaudio',
6
+ '-no-boot-anim',
7
+ '-camera-back',
8
+ 'none',
9
+ ];
10
+ export const getEmulatorStartupArgs = (name, mode) => {
11
+ const modeArgs = mode === 'clean-snapshot-generation'
12
+ ? ['-no-snapshot-load']
13
+ : mode === 'snapshot-reuse'
14
+ ? ['-no-snapshot-save']
15
+ : ['-no-snapshot-load', '-no-snapshot-save'];
16
+ return [`@${name}`, ...modeArgs, ...COMMON_EMULATOR_ARGS];
17
+ };
@@ -0,0 +1,6 @@
1
+ export type AndroidEmulator = {
2
+ adbId: string;
3
+ stop: () => Promise<void>;
4
+ };
5
+ export declare const runEmulator: (avdName: string) => Promise<AndroidEmulator>;
6
+ //# sourceMappingURL=emulator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emulator.d.ts","sourceRoot":"","sources":["../src/emulator.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B,CAAC;AAEF,eAAO,MAAM,WAAW,GACtB,SAAS,MAAM,KACd,OAAO,CAAC,eAAe,CA8BzB,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { spawn } from '@react-native-harness/tools';
2
+ import * as adb from './adb.js';
3
+ export const runEmulator = async (avdName) => {
4
+ const process = spawn('emulator', ['-avd', avdName]);
5
+ await process.nodeChildProcess;
6
+ const adbId = await adb.getEmulatorName(avdName);
7
+ if (!adbId) {
8
+ throw new Error('Emulator not found');
9
+ }
10
+ // Poll for emulator status until it's fully running
11
+ const checkStatus = async () => {
12
+ const status = await adb.isBootCompleted(adbId);
13
+ if (!status) {
14
+ await new Promise((resolve) => setTimeout(resolve, 2000));
15
+ await checkStatus();
16
+ }
17
+ };
18
+ // Start checking status after a brief delay to allow emulator to start
19
+ await new Promise((resolve) => setTimeout(resolve, 3000));
20
+ await checkStatus();
21
+ return {
22
+ adbId,
23
+ stop: async () => {
24
+ await adb.stopEmulator(adbId);
25
+ },
26
+ };
27
+ };
@@ -0,0 +1,28 @@
1
+ export type AndroidSystemImageArch = 'x86_64' | 'arm64-v8a' | 'armeabi-v7a';
2
+ type AndroidSdkRootOptions = {
3
+ env?: NodeJS.ProcessEnv;
4
+ platform?: NodeJS.Platform;
5
+ homeDirectory?: string;
6
+ };
7
+ export declare const getDefaultUnixAndroidSdkRoot: ({ platform, homeDirectory, }?: Omit<AndroidSdkRootOptions, "env">) => string | null;
8
+ export declare const getAndroidSdkRoot: (env?: NodeJS.ProcessEnv, options?: Omit<AndroidSdkRootOptions, "env">) => string | null;
9
+ export declare const getHostAndroidSystemImageArch: (architecture?: string) => AndroidSystemImageArch;
10
+ export declare const getAndroidPlatformPackage: (apiLevel: number) => string;
11
+ export declare const getAndroidSystemImagePackage: (apiLevel: number, architecture?: AndroidSystemImageArch) => string;
12
+ export declare const getRequiredAndroidSdkPackages: ({ apiLevel, includeEmulator, architecture, }?: {
13
+ apiLevel?: number;
14
+ includeEmulator?: boolean;
15
+ architecture?: AndroidSystemImageArch;
16
+ }) => string[];
17
+ export declare const ensureAndroidSdkPackages: (packages: readonly string[], { env, platform, homeDirectory, }?: AndroidSdkRootOptions) => Promise<string>;
18
+ export declare const ensureAndroidDiscoveryEnvironment: () => Promise<string>;
19
+ export declare const ensureAndroidPhysicalDeviceEnvironment: () => Promise<string>;
20
+ export declare const ensureAndroidEmulatorEnvironment: (apiLevel: number) => Promise<string>;
21
+ export declare const getAndroidProcessEnv: (env?: NodeJS.ProcessEnv) => NodeJS.ProcessEnv;
22
+ export declare const initializeAndroidProcessEnv: () => void;
23
+ export declare const getAdbBinaryPath: (sdkRoot?: string) => string;
24
+ export declare const getEmulatorBinaryPath: (sdkRoot?: string) => string;
25
+ export declare const getSdkManagerBinaryPath: (sdkRoot?: string) => string;
26
+ export declare const getAvdManagerBinaryPath: (sdkRoot?: string) => string;
27
+ export {};
28
+ //# sourceMappingURL=environment.d.ts.map
@@ -0,0 +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;AAEF,eAAO,MAAM,wBAAwB,GACnC,UAAU,SAAS,MAAM,EAAE,EAC3B,oCAIG,qBAA0B,KAC5B,OAAO,CAAC,MAAM,CA0BhB,CAAC;AAEF,eAAO,MAAM,iCAAiC,QAAa,OAAO,CAAC,MAAM,CAMxE,CAAC;AAEF,eAAO,MAAM,sCAAsC,QACvC,OAAO,CAAC,MAAM,CAIvB,CAAC;AAEJ,eAAO,MAAM,gCAAgC,GAC3C,UAAU,MAAM,KACf,OAAO,CAAC,MAAM,CAShB,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"}
@@ -0,0 +1,295 @@
1
+ import { spawn } from '@react-native-harness/tools';
2
+ import { logger } from '@react-native-harness/tools';
3
+ import { createWriteStream } from 'node:fs';
4
+ import { access, cp, mkdir, mkdtemp, rm } from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import { pipeline } from 'node:stream/promises';
8
+ import https from 'node:https';
9
+ const CMDLINE_TOOLS_PATH_SEGMENTS = ['cmdline-tools', 'latest'];
10
+ const ANDROID_REPOSITORY_INDEX_URL = 'https://dl.google.com/android/repository/repository2-1.xml';
11
+ const androidEnvironmentLogger = logger.child('android-environment');
12
+ const getConfiguredAndroidSdkRoot = (env = process.env) => {
13
+ return env.ANDROID_HOME ?? env.ANDROID_SDK_ROOT ?? null;
14
+ };
15
+ export const getDefaultUnixAndroidSdkRoot = ({ platform = process.platform, homeDirectory = os.homedir(), } = {}) => {
16
+ if (platform === 'darwin') {
17
+ return path.join(homeDirectory, 'Library', 'Android', 'sdk');
18
+ }
19
+ if (platform === 'linux') {
20
+ return path.join(homeDirectory, 'Android', 'Sdk');
21
+ }
22
+ return null;
23
+ };
24
+ const canBootstrapAndroidSdk = (platform = process.platform) => {
25
+ return platform === 'darwin' || platform === 'linux';
26
+ };
27
+ const pathExists = async (filePath) => {
28
+ try {
29
+ await access(filePath);
30
+ return true;
31
+ }
32
+ catch {
33
+ return false;
34
+ }
35
+ };
36
+ const quoteShell = (value) => {
37
+ return `'${value.replace(/'/g, `'\\''`)}'`;
38
+ };
39
+ const downloadText = async (url) => {
40
+ return new Promise((resolve, reject) => {
41
+ const request = https.get(url, (response) => {
42
+ const { statusCode = 0, headers } = response;
43
+ if (statusCode >= 300 &&
44
+ statusCode < 400 &&
45
+ typeof headers.location === 'string') {
46
+ response.resume();
47
+ resolve(downloadText(headers.location));
48
+ return;
49
+ }
50
+ if (statusCode !== 200) {
51
+ response.resume();
52
+ reject(new Error(`Failed to download Android repository index from ${url} (status ${statusCode}).`));
53
+ return;
54
+ }
55
+ response.setEncoding('utf8');
56
+ let body = '';
57
+ response.on('data', (chunk) => {
58
+ body += chunk;
59
+ });
60
+ response.once('end', () => {
61
+ resolve(body);
62
+ });
63
+ });
64
+ request.once('error', reject);
65
+ });
66
+ };
67
+ const downloadFile = async (url, destinationPath) => {
68
+ await new Promise((resolve, reject) => {
69
+ const request = https.get(url, (response) => {
70
+ const { statusCode = 0, headers } = response;
71
+ if (statusCode >= 300 &&
72
+ statusCode < 400 &&
73
+ typeof headers.location === 'string') {
74
+ response.resume();
75
+ resolve(downloadFile(headers.location, destinationPath));
76
+ return;
77
+ }
78
+ if (statusCode !== 200) {
79
+ response.resume();
80
+ reject(new Error(`Failed to download Android command-line tools from ${url} (status ${statusCode}).`));
81
+ return;
82
+ }
83
+ const output = createWriteStream(destinationPath);
84
+ pipeline(response, output).then(resolve).catch(reject);
85
+ });
86
+ request.once('error', reject);
87
+ });
88
+ };
89
+ const getCommandLineToolsArchiveUrl = async (platform = process.platform) => {
90
+ const archivePlatform = platform === 'darwin' ? 'mac' : platform === 'linux' ? 'linux' : null;
91
+ if (!archivePlatform) {
92
+ throw new Error('Automatic Android SDK bootstrap is only supported on macOS and Linux.');
93
+ }
94
+ const repositoryIndex = await downloadText(ANDROID_REPOSITORY_INDEX_URL);
95
+ const archivePattern = new RegExp(`commandlinetools-${archivePlatform}-(\\d+)_latest\\.zip`, 'g');
96
+ const matches = [...repositoryIndex.matchAll(archivePattern)];
97
+ if (matches.length === 0) {
98
+ throw new Error(`Failed to resolve Android command-line tools archive for ${archivePlatform}.`);
99
+ }
100
+ const newestArchive = matches
101
+ .map((match) => ({
102
+ fileName: match[0],
103
+ revision: Number(match[1]),
104
+ }))
105
+ .sort((left, right) => right.revision - left.revision)[0];
106
+ return `https://dl.google.com/android/repository/${newestArchive.fileName}`;
107
+ };
108
+ const ensureAndroidCommandLineTools = async (sdkRoot, platform = process.platform) => {
109
+ if ((await pathExists(getSdkManagerBinaryPath(sdkRoot))) &&
110
+ (await pathExists(getAvdManagerBinaryPath(sdkRoot)))) {
111
+ return;
112
+ }
113
+ if (!canBootstrapAndroidSdk(platform)) {
114
+ throw new Error('Android command-line tools are missing. Set ANDROID_HOME or ANDROID_SDK_ROOT to an initialized SDK.');
115
+ }
116
+ androidEnvironmentLogger.info('Bootstrapping Android command-line tools in %s', sdkRoot);
117
+ await mkdir(sdkRoot, { recursive: true });
118
+ const temporaryDirectory = await mkdtemp(path.join(os.tmpdir(), 'android-cmdline-tools-'));
119
+ const archivePath = path.join(temporaryDirectory, 'cmdline-tools.zip');
120
+ const extractedPath = path.join(temporaryDirectory, 'extracted');
121
+ const sourceDirectory = path.join(extractedPath, 'cmdline-tools');
122
+ const targetDirectory = path.join(sdkRoot, ...CMDLINE_TOOLS_PATH_SEGMENTS);
123
+ try {
124
+ await downloadFile(await getCommandLineToolsArchiveUrl(platform), archivePath);
125
+ await spawn('unzip', ['-q', archivePath, '-d', extractedPath]);
126
+ await rm(targetDirectory, { force: true, recursive: true });
127
+ await mkdir(path.dirname(targetDirectory), { recursive: true });
128
+ await cp(sourceDirectory, targetDirectory, { recursive: true });
129
+ }
130
+ finally {
131
+ await rm(temporaryDirectory, { force: true, recursive: true });
132
+ }
133
+ };
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
+ ], {
140
+ env: getAndroidProcessEnv({
141
+ ...process.env,
142
+ ANDROID_HOME: sdkRoot,
143
+ ANDROID_SDK_ROOT: sdkRoot,
144
+ }),
145
+ });
146
+ };
147
+ const getPackageVerificationPath = (sdkRoot, packageName) => {
148
+ if (packageName === 'platform-tools') {
149
+ return getAdbBinaryPath(sdkRoot);
150
+ }
151
+ if (packageName === 'emulator') {
152
+ return getEmulatorBinaryPath(sdkRoot);
153
+ }
154
+ if (packageName.startsWith('platforms;android-')) {
155
+ return path.join(sdkRoot, packageName.replace(';', '/'));
156
+ }
157
+ if (packageName.startsWith('system-images;android-')) {
158
+ return path.join(sdkRoot, packageName.replaceAll(';', path.sep));
159
+ }
160
+ return null;
161
+ };
162
+ const getMissingAndroidSdkPackages = async (sdkRoot, packages) => {
163
+ const missingPackages = [];
164
+ for (const packageName of packages) {
165
+ const verificationPath = getPackageVerificationPath(sdkRoot, packageName);
166
+ if (!verificationPath) {
167
+ continue;
168
+ }
169
+ if (!(await pathExists(verificationPath))) {
170
+ missingPackages.push(packageName);
171
+ }
172
+ }
173
+ return missingPackages;
174
+ };
175
+ const installAndroidSdkPackages = async (sdkRoot, packages) => {
176
+ if (packages.length === 0) {
177
+ return;
178
+ }
179
+ const sdkManagerBinaryPath = getSdkManagerBinaryPath(sdkRoot);
180
+ const packageArgs = packages
181
+ .map((packageName) => quoteShell(packageName))
182
+ .join(' ');
183
+ androidEnvironmentLogger.info('Installing missing Android SDK packages: %s', packages.join(', '));
184
+ 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
+ });
195
+ };
196
+ export const getAndroidSdkRoot = (env = process.env, options = {}) => {
197
+ return (getConfiguredAndroidSdkRoot(env) ?? getDefaultUnixAndroidSdkRoot(options));
198
+ };
199
+ const getRequiredAndroidSdkRoot = (env = process.env, options = {}) => {
200
+ const sdkRoot = getAndroidSdkRoot(env, options);
201
+ if (!sdkRoot) {
202
+ throw new Error('Android SDK root is not configured. Set ANDROID_HOME or ANDROID_SDK_ROOT.');
203
+ }
204
+ return sdkRoot;
205
+ };
206
+ export const getHostAndroidSystemImageArch = (architecture = process.arch) => {
207
+ switch (architecture) {
208
+ case 'arm64':
209
+ return 'arm64-v8a';
210
+ case 'arm':
211
+ return 'armeabi-v7a';
212
+ case 'x64':
213
+ default:
214
+ return 'x86_64';
215
+ }
216
+ };
217
+ export const getAndroidPlatformPackage = (apiLevel) => {
218
+ return `platforms;android-${apiLevel}`;
219
+ };
220
+ export const getAndroidSystemImagePackage = (apiLevel, architecture = getHostAndroidSystemImageArch()) => {
221
+ return `system-images;android-${apiLevel};default;${architecture}`;
222
+ };
223
+ export const getRequiredAndroidSdkPackages = ({ apiLevel, includeEmulator = false, architecture = getHostAndroidSystemImageArch(), } = {}) => {
224
+ const packages = ['platform-tools'];
225
+ if (!includeEmulator) {
226
+ return packages;
227
+ }
228
+ packages.push('emulator');
229
+ if (typeof apiLevel === 'number') {
230
+ packages.push(getAndroidPlatformPackage(apiLevel));
231
+ packages.push(getAndroidSystemImagePackage(apiLevel, architecture));
232
+ }
233
+ return packages;
234
+ };
235
+ export const ensureAndroidSdkPackages = async (packages, { env = process.env, platform = process.platform, homeDirectory = os.homedir(), } = {}) => {
236
+ const sdkRoot = getRequiredAndroidSdkRoot(env, { platform, homeDirectory });
237
+ await mkdir(sdkRoot, { recursive: true });
238
+ await ensureAndroidCommandLineTools(sdkRoot, platform);
239
+ const missingPackages = await getMissingAndroidSdkPackages(sdkRoot, packages);
240
+ if (missingPackages.length > 0) {
241
+ await installAndroidSdkPackages(sdkRoot, missingPackages);
242
+ }
243
+ const unresolvedPackages = await getMissingAndroidSdkPackages(sdkRoot, packages);
244
+ if (unresolvedPackages.length > 0) {
245
+ throw new Error(`Android SDK packages are still missing after installation: ${unresolvedPackages.join(', ')}`);
246
+ }
247
+ return sdkRoot;
248
+ };
249
+ export const ensureAndroidDiscoveryEnvironment = async () => {
250
+ initializeAndroidProcessEnv();
251
+ return ensureAndroidSdkPackages(getRequiredAndroidSdkPackages({ includeEmulator: true }));
252
+ };
253
+ export const ensureAndroidPhysicalDeviceEnvironment = async () => {
254
+ initializeAndroidProcessEnv();
255
+ return ensureAndroidSdkPackages(getRequiredAndroidSdkPackages());
256
+ };
257
+ export const ensureAndroidEmulatorEnvironment = async (apiLevel) => {
258
+ initializeAndroidProcessEnv();
259
+ return ensureAndroidSdkPackages(getRequiredAndroidSdkPackages({
260
+ apiLevel,
261
+ includeEmulator: true,
262
+ }));
263
+ };
264
+ export const getAndroidProcessEnv = (env = process.env) => {
265
+ const sdkRoot = getAndroidSdkRoot(env);
266
+ if (!sdkRoot) {
267
+ return env;
268
+ }
269
+ const platformToolsPath = path.join(sdkRoot, 'platform-tools');
270
+ const emulatorPath = path.join(sdkRoot, 'emulator');
271
+ const cmdlineToolsPath = path.join(sdkRoot, ...CMDLINE_TOOLS_PATH_SEGMENTS);
272
+ const cmdlineToolsBinPath = path.join(cmdlineToolsPath, 'bin');
273
+ const currentPath = env.PATH ?? '';
274
+ const pathEntries = [
275
+ platformToolsPath,
276
+ emulatorPath,
277
+ cmdlineToolsPath,
278
+ cmdlineToolsBinPath,
279
+ currentPath,
280
+ ].filter((entry) => entry !== '');
281
+ return {
282
+ ...env,
283
+ ANDROID_HOME: sdkRoot,
284
+ ANDROID_SDK_ROOT: sdkRoot,
285
+ ANDROID_AVD_HOME: path.join(os.homedir(), '.android', 'avd'),
286
+ PATH: pathEntries.join(path.delimiter),
287
+ };
288
+ };
289
+ export const initializeAndroidProcessEnv = () => {
290
+ Object.assign(process.env, getAndroidProcessEnv());
291
+ };
292
+ export const getAdbBinaryPath = (sdkRoot = getRequiredAndroidSdkRoot()) => path.join(sdkRoot, 'platform-tools', 'adb');
293
+ export const getEmulatorBinaryPath = (sdkRoot = getRequiredAndroidSdkRoot()) => path.join(sdkRoot, 'emulator', 'emulator');
294
+ export const getSdkManagerBinaryPath = (sdkRoot = getRequiredAndroidSdkRoot()) => path.join(sdkRoot, ...CMDLINE_TOOLS_PATH_SEGMENTS, 'bin', 'sdkmanager');
295
+ export const getAvdManagerBinaryPath = (sdkRoot = getRequiredAndroidSdkRoot()) => path.join(sdkRoot, ...CMDLINE_TOOLS_PATH_SEGMENTS, 'bin', 'avdmanager');
@@ -0,0 +1,7 @@
1
+ export declare class HarnessAppPathError extends Error {
2
+ constructor(reason: 'missing' | 'invalid', appPath?: string);
3
+ }
4
+ export declare class HarnessEmulatorConfigError extends Error {
5
+ constructor(deviceName: string);
6
+ }
7
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,MAAM,EAAE,SAAS,GAAG,SAAS,EAAE,OAAO,CAAC,EAAE,MAAM;CAQ5D;AAED,qBAAa,0BAA2B,SAAQ,KAAK;gBACvC,UAAU,EAAE,MAAM;CAM/B"}
package/dist/errors.js ADDED
@@ -0,0 +1,14 @@
1
+ export class HarnessAppPathError extends Error {
2
+ constructor(reason, appPath) {
3
+ super(reason === 'missing'
4
+ ? 'App is not installed on the emulator and HARNESS_APP_PATH is not set.'
5
+ : `HARNESS_APP_PATH points to a missing APK: ${appPath ?? '<unknown>'}`);
6
+ this.name = 'HarnessAppPathError';
7
+ }
8
+ }
9
+ export class HarnessEmulatorConfigError extends Error {
10
+ constructor(deviceName) {
11
+ super(`Android emulator "${deviceName}" is not running and no AVD config was provided. Add the "avd" property to this runner config so Harness can create and boot the emulator.`);
12
+ this.name = 'HarnessEmulatorConfigError';
13
+ }
14
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC9B,MAAM,aAAa,CAAC;AAErB,eAAO,MAAM,eAAe,GAC1B,MAAM,MAAM,EACZ,MAAM,wBAAwB,KAC7B,eAID,CAAC;AAEH,eAAO,MAAM,qBAAqB,GAChC,cAAc,MAAM,EACpB,OAAO,MAAM,KACZ,qBAID,CAAC;AAEH,eAAO,MAAM,eAAe,GAC1B,QAAQ,qBAAqB,KAC5B,eAAe,CAAC,qBAAqB,CAKtC,CAAC"}
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC9B,MAAM,aAAa,CAAC;AAErB,eAAO,MAAM,eAAe,GAC1B,MAAM,MAAM,EACZ,MAAM,wBAAwB,KAC7B,eAID,CAAC;AAEH,eAAO,MAAM,qBAAqB,GAChC,cAAc,MAAM,EACpB,OAAO,MAAM,KACZ,qBAID,CAAC;AAEH,eAAO,MAAM,eAAe,GAC1B,QAAQ,qBAAqB,KAC5B,eAAe,CAAC,qBAAqB,CAStC,CAAC"}
package/dist/factory.js CHANGED
@@ -13,4 +13,7 @@ export const androidPlatform = (config) => ({
13
13
  config,
14
14
  runner: import.meta.resolve('./runner.js'),
15
15
  platformId: 'android',
16
+ getResourceLockKey: () => config.device.type === 'emulator'
17
+ ? `android:emulator:${config.device.name}`
18
+ : `android:physical:${config.device.manufacturer}:${config.device.model}`,
16
19
  });