@ankhorage/expo-runtime 2.6.0 → 3.0.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -2
  3. package/dist/ExpoBarcodeScannerAdapter.d.ts.map +1 -1
  4. package/dist/ExpoBarcodeScannerAdapter.js +4 -16
  5. package/dist/ExpoBarcodeScannerAdapter.js.map +1 -1
  6. package/dist/barcodeScanRuntime.d.ts +4 -1
  7. package/dist/barcodeScanRuntime.d.ts.map +1 -1
  8. package/dist/barcodeScanRuntime.js +15 -1
  9. package/dist/barcodeScanRuntime.js.map +1 -1
  10. package/dist/expoRuntimePlanningMetadata.d.ts +59 -0
  11. package/dist/expoRuntimePlanningMetadata.d.ts.map +1 -0
  12. package/dist/expoRuntimePlanningMetadata.js +133 -0
  13. package/dist/expoRuntimePlanningMetadata.js.map +1 -0
  14. package/dist/mediaPicker/documentPicker.d.ts +1 -2
  15. package/dist/mediaPicker/documentPicker.d.ts.map +1 -1
  16. package/dist/mediaPicker/documentPicker.js +1 -1
  17. package/dist/mediaPicker/documentPicker.js.map +1 -1
  18. package/dist/mediaPicker/photoLibraryPicker.d.ts +1 -2
  19. package/dist/mediaPicker/photoLibraryPicker.d.ts.map +1 -1
  20. package/dist/mediaPicker/photoLibraryPicker.js +1 -1
  21. package/dist/mediaPicker/photoLibraryPicker.js.map +1 -1
  22. package/dist/mediaPicker/readSelectedBytes.d.ts +1 -4
  23. package/dist/mediaPicker/readSelectedBytes.d.ts.map +1 -1
  24. package/dist/mediaPicker/readSelectedBytes.js +7 -1
  25. package/dist/mediaPicker/readSelectedBytes.js.map +1 -1
  26. package/dist/platform.d.ts +83 -0
  27. package/dist/platform.d.ts.map +1 -0
  28. package/dist/platform.js +81 -0
  29. package/dist/platform.js.map +1 -0
  30. package/dist/resolveExpoRuntimePlan.d.ts +3 -23
  31. package/dist/resolveExpoRuntimePlan.d.ts.map +1 -1
  32. package/dist/resolveExpoRuntimePlan.js +155 -187
  33. package/dist/resolveExpoRuntimePlan.js.map +1 -1
  34. package/package.json +74 -29
  35. package/src/ExpoBarcodeScannerAdapter.test.tsx +47 -4
  36. package/src/ExpoBarcodeScannerAdapter.tsx +10 -19
  37. package/src/barcodeScanRuntime.ts +27 -2
  38. package/src/expoRuntimePlanningMetadata.ts +190 -0
  39. package/src/generatedAppOutput.test.ts +29 -7
  40. package/src/mediaPicker/documentPicker.ts +1 -6
  41. package/src/mediaPicker/photoLibraryPicker.ts +1 -6
  42. package/src/mediaPicker/readSelectedBytes.ts +12 -3
  43. package/src/oauthBrowserRuntime.test.ts +1 -1
  44. package/src/platform.test.ts +34 -0
  45. package/src/platform.ts +88 -0
  46. package/src/resolveExpoRuntimePlan.test.ts +130 -6
  47. package/src/resolveExpoRuntimePlan.ts +214 -244
@@ -6,6 +6,7 @@ import {
6
6
  resolveExpoRuntimeGeneratedAppOutput,
7
7
  resolveExpoRuntimeNativeOutput,
8
8
  } from './generatedAppOutput';
9
+ import { EXPO_PLATFORM } from './platform';
9
10
  import type { ExpoRuntimePlan } from './resolveExpoRuntimePlan';
10
11
 
11
12
  const EMPTY_PLAN: ExpoRuntimePlan = {
@@ -32,7 +33,7 @@ const CAMERA_PLAN: ExpoRuntimePlan = {
32
33
  dependencies: [
33
34
  {
34
35
  name: 'expo-camera',
35
- version: '~17.0.10',
36
+ version: EXPO_PLATFORM.packages.camera.version,
36
37
  reasons: ['capability:barcodeScanner'],
37
38
  },
38
39
  {
@@ -42,13 +43,16 @@ const CAMERA_PLAN: ExpoRuntimePlan = {
42
43
  },
43
44
  ],
44
45
  nativeConfig: {
45
- androidPermissions: ['android.permission.CAMERA'],
46
+ androidPermissions: [],
46
47
  configHints: ['cameraPermission'],
47
48
  plugins: [
48
49
  {
49
50
  name: 'expo-camera',
50
51
  options: {
51
52
  cameraPermission: 'Allow camera access to scan barcodes.',
53
+ barcodeScannerEnabled: true,
54
+ microphonePermission: false,
55
+ recordAudioAndroid: false,
52
56
  },
53
57
  },
54
58
  {
@@ -62,10 +66,18 @@ const CAMERA_PLAN: ExpoRuntimePlan = {
62
66
  needsPermissionsProvider: true,
63
67
  };
64
68
 
65
- describe('generatedAppOutput', () => {
69
+ describe('generated App config output', () => {
66
70
  test('keeps Expo config plugins as a distinct external ecosystem concept', () => {
67
71
  expect(resolveExpoRuntimeConfigPluginOutput(CAMERA_PLAN)).toEqual([
68
- ['expo-camera', { cameraPermission: 'Allow camera access to scan barcodes.' }],
72
+ [
73
+ 'expo-camera',
74
+ {
75
+ barcodeScannerEnabled: true,
76
+ cameraPermission: 'Allow camera access to scan barcodes.',
77
+ microphonePermission: false,
78
+ recordAudioAndroid: false,
79
+ },
80
+ ],
69
81
  'expo-something',
70
82
  ]);
71
83
  });
@@ -78,11 +90,13 @@ describe('generatedAppOutput', () => {
78
90
  configPlugins: [],
79
91
  });
80
92
  });
93
+ });
81
94
 
95
+ describe('generated App dependency output', () => {
82
96
  test('maps runtime dependencies into a package dependency map', () => {
83
97
  expect(resolveExpoRuntimeDependencyMap(CAMERA_PLAN)).toEqual({
84
98
  '@ankhorage/expo-runtime': '^0.0.7',
85
- 'expo-camera': '~17.0.10',
99
+ 'expo-camera': EXPO_PLATFORM.packages.camera.version,
86
100
  });
87
101
  });
88
102
 
@@ -91,18 +105,23 @@ describe('generatedAppOutput', () => {
91
105
  [
92
106
  'expo-camera',
93
107
  {
108
+ barcodeScannerEnabled: true,
94
109
  cameraPermission: 'Allow camera access to scan barcodes.',
110
+ microphonePermission: false,
111
+ recordAudioAndroid: false,
95
112
  },
96
113
  ],
97
114
  'expo-something',
98
115
  ]);
99
116
  });
117
+ });
100
118
 
119
+ describe('combined generated App output', () => {
101
120
  test('combines dependency, native and layout output', () => {
102
121
  expect(resolveExpoRuntimeGeneratedAppOutput(CAMERA_PLAN)).toEqual({
103
122
  dependencies: {
104
123
  '@ankhorage/expo-runtime': '^0.0.7',
105
- 'expo-camera': '~17.0.10',
124
+ 'expo-camera': EXPO_PLATFORM.packages.camera.version,
106
125
  },
107
126
  layoutIntegration: {
108
127
  imports: ["import { ExpoRuntimeProviders } from '@ankhorage/expo-runtime';"],
@@ -111,12 +130,15 @@ describe('generatedAppOutput', () => {
111
130
  providerEnd: ['</ExpoRuntimeProviders>'],
112
131
  },
113
132
  native: {
114
- androidPermissions: ['android.permission.CAMERA'],
133
+ androidPermissions: [],
115
134
  configPlugins: [
116
135
  [
117
136
  'expo-camera',
118
137
  {
138
+ barcodeScannerEnabled: true,
119
139
  cameraPermission: 'Allow camera access to scan barcodes.',
140
+ microphonePermission: false,
141
+ recordAudioAndroid: false,
120
142
  },
121
143
  ],
122
144
  'expo-something',
@@ -5,18 +5,13 @@ import {
5
5
  isRequestedMediaKind,
6
6
  resolveDocumentPickerMimeTypes,
7
7
  } from './mediaKinds';
8
- import {
9
- type ArrayBufferReadable,
10
- readSelectedMediaBytes,
11
- type SelectedMediaByteSource,
12
- } from './readSelectedBytes';
8
+ import { readSelectedMediaBytes, type SelectedMediaByteSource } from './readSelectedBytes';
13
9
  import type { ExpoMediaPickerInput, ExpoMediaPickerResult } from './types';
14
10
 
15
11
  interface DocumentPickerAssetLike extends SelectedMediaByteSource {
16
12
  readonly name: string;
17
13
  readonly mimeType?: string;
18
14
  readonly size?: number;
19
- readonly file?: ArrayBufferReadable;
20
15
  }
21
16
 
22
17
  interface DocumentPickerResultLike {
@@ -1,11 +1,7 @@
1
1
  import type { MediaAssetKind } from '@ankhorage/contracts';
2
2
 
3
3
  import { isRequestedMediaKind } from './mediaKinds';
4
- import {
5
- type ArrayBufferReadable,
6
- readSelectedMediaBytes,
7
- type SelectedMediaByteSource,
8
- } from './readSelectedBytes';
4
+ import { readSelectedMediaBytes, type SelectedMediaByteSource } from './readSelectedBytes';
9
5
  import type { ExpoMediaPickerInput, ExpoMediaPickerResult } from './types';
10
6
 
11
7
  export type ExpoPhotoLibraryMediaType = 'images' | 'videos';
@@ -18,7 +14,6 @@ interface PhotoLibraryAssetLike extends SelectedMediaByteSource {
18
14
  readonly height: number;
19
15
  readonly duration?: number | null;
20
16
  readonly type?: 'image' | 'video' | 'livePhoto' | 'pairedVideo' | null;
21
- readonly file?: ArrayBufferReadable;
22
17
  }
23
18
 
24
19
  interface PhotoLibraryResultLike {
@@ -1,17 +1,17 @@
1
- export interface ArrayBufferReadable {
1
+ interface ArrayBufferReadable {
2
2
  arrayBuffer(): Promise<ArrayBuffer>;
3
3
  }
4
4
 
5
5
  export interface SelectedMediaByteSource {
6
6
  readonly uri: string;
7
- readonly file?: ArrayBufferReadable;
7
+ readonly file?: unknown;
8
8
  }
9
9
 
10
10
  export async function readSelectedMediaBytes(
11
11
  source: SelectedMediaByteSource,
12
12
  ): Promise<Uint8Array | null> {
13
13
  try {
14
- if (source.file) {
14
+ if (isArrayBufferReadable(source.file)) {
15
15
  return new Uint8Array(await source.file.arrayBuffer());
16
16
  }
17
17
  const { File } = await import('expo-file-system');
@@ -20,3 +20,12 @@ export async function readSelectedMediaBytes(
20
20
  return null;
21
21
  }
22
22
  }
23
+
24
+ function isArrayBufferReadable(value: unknown): value is ArrayBufferReadable {
25
+ return (
26
+ typeof value === 'object' &&
27
+ value !== null &&
28
+ 'arrayBuffer' in value &&
29
+ typeof value.arrayBuffer === 'function'
30
+ );
31
+ }
@@ -3,7 +3,7 @@ import { describe, expect, test } from 'bun:test';
3
3
  import { resolveExpoOAuthBrowserRuntimeReadinessFromEnvironment } from './oauthBrowserRuntimeCore';
4
4
 
5
5
  describe('Expo OAuth browser runtime readiness', () => {
6
- test('allows an SDK 54 custom native build with an embedded Expo config', () => {
6
+ test('allows a custom native build with an embedded Expo config', () => {
7
7
  expect(
8
8
  resolveExpoOAuthBrowserRuntimeReadinessFromEnvironment({
9
9
  appOwnership: null,
@@ -0,0 +1,34 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import { EXPO_PLATFORM } from './platform';
4
+
5
+ describe('EXPO_PLATFORM', () => {
6
+ test('represents the supported Expo 57 native platform without a tvOS claim', () => {
7
+ expect(EXPO_PLATFORM.sdk).toBe(57);
8
+ expect(EXPO_PLATFORM.architecture).toBe('new-architecture-only');
9
+ expect(EXPO_PLATFORM.platforms).toEqual({
10
+ android: {
11
+ minimumVersion: 7,
12
+ minimumApiLevel: 24,
13
+ compileSdk: 36,
14
+ targetSdk: 36,
15
+ edgeToEdge: true,
16
+ },
17
+ ios: {
18
+ minimumVersion: '16.4',
19
+ minimumXcodeVersion: '26.4',
20
+ },
21
+ });
22
+ expect('tvos' in EXPO_PLATFORM.platforms).toBe(false);
23
+ });
24
+
25
+ test('keeps the Hermes fix, React Native and Node policy in one contract', () => {
26
+ expect(EXPO_PLATFORM.runtime.expo).toEqual({ name: 'expo', version: '~57.0.15' });
27
+ expect(EXPO_PLATFORM.runtime.reactNative).toEqual({
28
+ name: 'react-native',
29
+ version: '0.86.2',
30
+ });
31
+ expect(EXPO_PLATFORM.tooling.node).toEqual({ lts: 24, version: '24.x' });
32
+ expect(EXPO_PLATFORM.animation.worklets.version).toBe('0.10.1');
33
+ });
34
+ });
@@ -0,0 +1,88 @@
1
+ export interface ExpoPlatformPackage {
2
+ readonly name: string;
3
+ readonly version: string;
4
+ }
5
+
6
+ function definePackage(name: string, version: string): ExpoPlatformPackage {
7
+ return { name, version };
8
+ }
9
+
10
+ /**
11
+ * The canonical Ankhorage projection of the supported Expo platform.
12
+ *
13
+ * Package versions come from Expo SDK 57's bundled native module map. Keep this
14
+ * structure as the only hand-maintained Expo compatibility authority in this
15
+ * repository; generated manifests and runtime planning derive from it.
16
+ */
17
+ export const EXPO_PLATFORM = {
18
+ sdk: 57,
19
+ architecture: 'new-architecture-only',
20
+ runtime: {
21
+ expo: definePackage('expo', '~57.0.15'),
22
+ react: definePackage('react', '19.2.3'),
23
+ reactDom: definePackage('react-dom', '19.2.3'),
24
+ reactNative: definePackage('react-native', '0.86.2'),
25
+ reactNativeWeb: definePackage('react-native-web', '~0.21.0'),
26
+ },
27
+ tooling: {
28
+ node: {
29
+ lts: 24,
30
+ version: '24.x',
31
+ },
32
+ nodeTypes: definePackage('@types/node', '^24.13.3'),
33
+ typescript: definePackage('typescript', '~6.0.3'),
34
+ expoDoctor: definePackage('expo-doctor', '1.20.2'),
35
+ },
36
+ navigation: {
37
+ expoRouter: definePackage('expo-router', '~57.0.15'),
38
+ screens: definePackage('react-native-screens', '~4.26.0'),
39
+ safeArea: definePackage('react-native-safe-area-context', '~5.7.0'),
40
+ },
41
+ animation: {
42
+ reanimated: definePackage('react-native-reanimated', '4.5.1'),
43
+ worklets: definePackage('react-native-worklets', '0.10.1'),
44
+ gestureHandler: definePackage('react-native-gesture-handler', '~2.32.0'),
45
+ },
46
+ ui: {
47
+ svg: definePackage('react-native-svg', '15.15.4'),
48
+ },
49
+ packages: {
50
+ metroRuntime: definePackage('@expo/metro-runtime', '~57.0.12'),
51
+ asset: definePackage('expo-asset', '~57.0.13'),
52
+ audio: definePackage('expo-audio', '~57.0.4'),
53
+ authSession: definePackage('expo-auth-session', '~57.0.8'),
54
+ camera: definePackage('expo-camera', '~57.0.4'),
55
+ constants: definePackage('expo-constants', '~57.0.13'),
56
+ devClient: definePackage('expo-dev-client', '~57.0.14'),
57
+ documentPicker: definePackage('expo-document-picker', '~57.0.1'),
58
+ fileSystem: definePackage('expo-file-system', '~57.0.5'),
59
+ font: definePackage('expo-font', '~57.0.1'),
60
+ imagePicker: definePackage('expo-image-picker', '~57.0.12'),
61
+ linearGradient: definePackage('expo-linear-gradient', '~57.0.1'),
62
+ linking: definePackage('expo-linking', '~57.0.7'),
63
+ localization: definePackage('expo-localization', '~57.0.1'),
64
+ location: definePackage('expo-location', '~57.0.12'),
65
+ mediaLibrary: definePackage('expo-media-library', '~57.0.4'),
66
+ notifications: definePackage('expo-notifications', '~57.0.13'),
67
+ secureStore: definePackage('expo-secure-store', '~57.0.1'),
68
+ splashScreen: definePackage('expo-splash-screen', '~57.0.7'),
69
+ statusBar: definePackage('expo-status-bar', '~57.0.1'),
70
+ updates: definePackage('expo-updates', '~57.0.16'),
71
+ webBrowser: definePackage('expo-web-browser', '~57.0.2'),
72
+ },
73
+ platforms: {
74
+ android: {
75
+ minimumVersion: 7,
76
+ minimumApiLevel: 24,
77
+ compileSdk: 36,
78
+ targetSdk: 36,
79
+ edgeToEdge: true,
80
+ },
81
+ ios: {
82
+ minimumVersion: '16.4',
83
+ minimumXcodeVersion: '26.4',
84
+ },
85
+ },
86
+ } as const;
87
+
88
+ export type ExpoPlatform = typeof EXPO_PLATFORM;
@@ -2,9 +2,10 @@ import { Permission } from '@ankhorage/permissions';
2
2
  import { EXPO_PERMISSION_SUPPORT } from '@ankhorage/permissions/expo/manifest';
3
3
  import { describe, expect, test } from 'bun:test';
4
4
 
5
+ import { EXPO_PLATFORM } from './platform';
5
6
  import { type ExpoRuntimePlanningManifest, resolveExpoRuntimePlan } from './resolveExpoRuntimePlan';
6
7
 
7
- describe('resolveExpoRuntimePlan', () => {
8
+ describe('resolveExpoRuntimePlan permissions', () => {
8
9
  test('accepts a minimal screens-only planning manifest', () => {
9
10
  const manifest: ExpoRuntimePlanningManifest = {
10
11
  screens: {
@@ -30,15 +31,25 @@ describe('resolveExpoRuntimePlan', () => {
30
31
  expect(plan.providers).toEqual(['permissions']);
31
32
  expect(plan.needsPermissionsProvider).toBe(true);
32
33
  expect(plan.nativeConfig.configHints).toEqual(['cameraPermission']);
33
- expect(plan.nativeConfig.androidPermissions).toEqual(['android.permission.CAMERA']);
34
+ expect(plan.nativeConfig.androidPermissions).toEqual([]);
34
35
  expect(plan.nativeConfig.plugins).toEqual([
35
36
  {
36
37
  name: 'expo-camera',
37
- options: { cameraPermission: 'Allow camera access.' },
38
+ options: {
39
+ barcodeScannerEnabled: false,
40
+ cameraPermission: 'Allow camera access.',
41
+ microphonePermission: false,
42
+ recordAudioAndroid: false,
43
+ },
38
44
  },
39
45
  ]);
46
+ expect(plan.dependencies.find(({ name }) => name === 'expo-camera')?.version).toBe(
47
+ EXPO_PLATFORM.packages.camera.version,
48
+ );
40
49
  });
50
+ });
41
51
 
52
+ describe('resolveExpoRuntimePlan barcode capability', () => {
42
53
  test('resolves barcodeScanner capability with implied camera permission and adapter wiring', () => {
43
54
  const plan = resolveExpoRuntimePlan(
44
55
  withFirstScreenRequirements({ capabilities: [{ capability: 'barcodeScanner' }] }),
@@ -55,11 +66,79 @@ describe('resolveExpoRuntimePlan', () => {
55
66
  expect(plan.nativeConfig.plugins).toEqual([
56
67
  {
57
68
  name: 'expo-camera',
58
- options: { cameraPermission: 'Allow camera access to scan barcodes.' },
69
+ options: {
70
+ barcodeScannerEnabled: true,
71
+ cameraPermission: 'Allow camera access to scan barcodes.',
72
+ microphonePermission: false,
73
+ recordAudioAndroid: false,
74
+ },
59
75
  },
60
76
  ]);
61
77
  });
62
78
 
79
+ test('lets barcodeScanner capability override camera-only scanner disabling', () => {
80
+ const plan = resolveExpoRuntimePlan(
81
+ withFirstScreenRequirements({
82
+ capabilities: [{ capability: 'barcodeScanner' }],
83
+ permissions: [{ permission: 'camera' }],
84
+ }),
85
+ );
86
+
87
+ expect(plan.nativeConfig.plugins).toContainEqual({
88
+ name: 'expo-camera',
89
+ options: {
90
+ barcodeScannerEnabled: true,
91
+ cameraPermission: 'Allow camera access to scan barcodes.',
92
+ microphonePermission: false,
93
+ recordAudioAndroid: false,
94
+ },
95
+ });
96
+ });
97
+ });
98
+
99
+ describe('resolveExpoRuntimePlan Expo 57 plugin coverage', () => {
100
+ test('keeps microphone-only native configuration free of background audio behavior', () => {
101
+ const plan = resolveExpoRuntimePlan(
102
+ withFirstScreenRequirements({ permissions: [{ permission: 'microphone' }] }),
103
+ );
104
+
105
+ expect(plan.nativeConfig.plugins).toEqual([
106
+ {
107
+ name: 'expo-audio',
108
+ options: {
109
+ enableBackgroundPlayback: false,
110
+ enableBackgroundRecording: false,
111
+ microphonePermission: 'Allow microphone access.',
112
+ recordAudioAndroid: true,
113
+ },
114
+ },
115
+ ]);
116
+ });
117
+
118
+ test('translates every supported permission into current Expo 57 packages and plugins', () => {
119
+ const plan = resolveExpoRuntimePlan(
120
+ withFirstScreenRequirements({
121
+ permissions: [
122
+ { permission: 'microphone' },
123
+ { permission: 'mediaLibrary' },
124
+ { permission: 'mediaLibraryWrite' },
125
+ { permission: 'locationForeground' },
126
+ { permission: 'locationBackground' },
127
+ { permission: 'notifications' },
128
+ ],
129
+ }),
130
+ );
131
+
132
+ expect(plan.diagnostics).toEqual([]);
133
+ expect(plan.dependencies.map(({ name, version }) => ({ name, version }))).toEqual(
134
+ SUPPORTED_PERMISSION_DEPENDENCIES,
135
+ );
136
+ expect(plan.nativeConfig.androidPermissions).toEqual([]);
137
+ expect(plan.nativeConfig.plugins).toEqual(SUPPORTED_PERMISSION_PLUGINS);
138
+ });
139
+ });
140
+
141
+ describe('resolveExpoRuntimePlan diagnostics and deduplication', () => {
63
142
  test('dedupes repeated permission and capability requirements', () => {
64
143
  const plan = resolveExpoRuntimePlan(
65
144
  withAllScreenRequirements({
@@ -101,7 +180,9 @@ describe('resolveExpoRuntimePlan', () => {
101
180
  expect(plan.dependencies).toEqual([]);
102
181
  expect(plan.providers).toEqual([]);
103
182
  });
183
+ });
104
184
 
185
+ describe('resolveExpoRuntimePlan platform-neutral permissions', () => {
105
186
  test('does not add runtime packages or providers for clipboard permissions', () => {
106
187
  const plan = resolveExpoRuntimePlan(
107
188
  withFirstScreenRequirements({ permissions: [{ permission: 'clipboard' }] }),
@@ -113,9 +194,52 @@ describe('resolveExpoRuntimePlan', () => {
113
194
  });
114
195
  });
115
196
 
197
+ const SUPPORTED_PERMISSION_DEPENDENCIES = [
198
+ { name: '@ankhorage/expo-runtime', version: '^3.0.0' },
199
+ { name: '@ankhorage/permissions', version: '^0.2.2' },
200
+ { name: 'expo-audio', version: EXPO_PLATFORM.packages.audio.version },
201
+ { name: 'expo-location', version: EXPO_PLATFORM.packages.location.version },
202
+ { name: 'expo-media-library', version: EXPO_PLATFORM.packages.mediaLibrary.version },
203
+ { name: 'expo-notifications', version: EXPO_PLATFORM.packages.notifications.version },
204
+ ];
205
+
206
+ const SUPPORTED_PERMISSION_PLUGINS = [
207
+ {
208
+ name: 'expo-audio',
209
+ options: {
210
+ enableBackgroundPlayback: false,
211
+ enableBackgroundRecording: false,
212
+ microphonePermission: 'Allow microphone access.',
213
+ recordAudioAndroid: true,
214
+ },
215
+ },
216
+ {
217
+ name: 'expo-location',
218
+ options: {
219
+ isAndroidBackgroundLocationEnabled: true,
220
+ isAndroidForegroundServiceEnabled: true,
221
+ isIosBackgroundLocationEnabled: true,
222
+ locationAlwaysAndWhenInUsePermission: 'Allow location access.',
223
+ locationWhenInUsePermission: 'Allow location access while using the app.',
224
+ },
225
+ },
226
+ {
227
+ name: 'expo-media-library',
228
+ options: {
229
+ photosPermission: 'Allow photo library access.',
230
+ savePhotosPermission: 'Allow saving to the photo library.',
231
+ },
232
+ },
233
+ { name: 'expo-notifications' },
234
+ ];
235
+
116
236
  interface TestScreenRequirements {
117
- readonly capabilities?: readonly [{ readonly capability: 'barcodeScanner' }];
118
- readonly permissions?: readonly [{ readonly permission: 'camera' | 'clipboard' }];
237
+ readonly capabilities?: NonNullable<
238
+ ExpoRuntimePlanningManifest['screens'][string]['requires']
239
+ >['capabilities'];
240
+ readonly permissions?: NonNullable<
241
+ ExpoRuntimePlanningManifest['screens'][string]['requires']
242
+ >['permissions'];
119
243
  }
120
244
 
121
245
  function withFirstScreenRequirements(