@ankhorage/expo-runtime 2.6.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +6 -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 +45 -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
@@ -11,6 +11,24 @@ import {
11
11
  type PermissionSupport,
12
12
  } from '@ankhorage/permissions/expo/manifest';
13
13
 
14
+ import {
15
+ EXPO_CAPABILITY_RUNTIME_REGISTRY,
16
+ EXPO_RUNTIME_PROVIDER_PACKAGES,
17
+ type ExpoRuntimeAdapterId,
18
+ type ExpoRuntimeCapabilityMetadata,
19
+ type ExpoRuntimeConfigPlugin,
20
+ type ExpoRuntimeDiagnostic,
21
+ type ExpoRuntimeProviderId,
22
+ findCapabilityMetadata,
23
+ findConfigHintMetadata,
24
+ GENERATED_RUNTIME_DEPENDENCY_VERSIONS,
25
+ unknownCapabilityDiagnostic,
26
+ unknownPermissionDiagnostic,
27
+ unsupportedPermissionDiagnostic,
28
+ } from './expoRuntimePlanningMetadata';
29
+
30
+ export type { ExpoRuntimeAdapterId, ExpoRuntimeProviderId };
31
+
14
32
  export interface ExpoRuntimePlanningScreen {
15
33
  readonly requires?: {
16
34
  readonly capabilities?: readonly ScreenCapabilityRequirement[];
@@ -28,22 +46,6 @@ interface ExpoRuntimeDependency {
28
46
  readonly reasons: readonly string[];
29
47
  }
30
48
 
31
- interface ExpoRuntimeConfigPlugin {
32
- readonly name: string;
33
- readonly options?: Readonly<Record<string, boolean | string>>;
34
- }
35
-
36
- export type ExpoRuntimeProviderId = 'permissions';
37
- export type ExpoRuntimeAdapterId = 'ExpoBarcodeScannerAdapter';
38
-
39
- interface ExpoRuntimeDiagnostic {
40
- readonly severity: 'error' | 'warning';
41
- readonly requirementType: 'capability' | 'configHint' | 'package' | 'permission';
42
- readonly requirement: string;
43
- readonly message: string;
44
- readonly support?: PermissionSupport;
45
- }
46
-
47
49
  export interface ExpoRuntimePlan {
48
50
  readonly permissions: readonly ScreenPermissionRequirement[];
49
51
  readonly capabilities: readonly ScreenCapabilityRequirement[];
@@ -61,20 +63,6 @@ export interface ExpoRuntimePlan {
61
63
  readonly diagnostics: readonly ExpoRuntimeDiagnostic[];
62
64
  }
63
65
 
64
- interface ExpoRuntimeCapabilityMetadata {
65
- readonly impliedPermissions?: readonly ScreenPermissionRequirement[];
66
- readonly requiredPackages?: readonly string[];
67
- readonly providers?: readonly ExpoRuntimeProviderId[];
68
- readonly runtimeAdapters?: readonly ExpoRuntimeAdapterId[];
69
- readonly androidPermissions?: readonly string[];
70
- readonly plugins?: readonly ExpoRuntimeConfigPlugin[];
71
- }
72
-
73
- interface ExpoRuntimeHintMetadata {
74
- readonly androidPermissions?: readonly string[];
75
- readonly plugin?: ExpoRuntimeConfigPlugin;
76
- }
77
-
78
66
  interface ResolveExpoRuntimePlanOptions {
79
67
  readonly capabilityRegistry?: Readonly<
80
68
  Partial<Record<AnkhorageCapabilityName, ExpoRuntimeCapabilityMetadata>>
@@ -83,263 +71,245 @@ interface ResolveExpoRuntimePlanOptions {
83
71
  readonly permissionSupport?: Readonly<Record<Permission, ExpoPermissionMetadata>>;
84
72
  }
85
73
 
86
- const CAMERA_ANDROID_PERMISSION = 'android.permission.CAMERA';
87
- const EXPO_RUNTIME_PACKAGE_NAME = '@ankhorage/expo-runtime';
88
-
89
- const GENERATED_EXPO_DEPENDENCY_VERSIONS = {
90
- '@ankhorage/permissions': '^0.2.0',
91
- '@ankhorage/expo-runtime': 'latest',
92
- 'expo-camera': '~17.0.10',
93
- } as const satisfies Record<string, string>;
94
-
95
- const EXPO_RUNTIME_PROVIDER_PACKAGES = {
96
- permissions: '@ankhorage/permissions',
97
- } as const satisfies Record<ExpoRuntimeProviderId, string>;
74
+ interface PlanningState {
75
+ readonly permissions: Map<string, ScreenPermissionRequirement>;
76
+ readonly impliedPermissions: Map<string, ScreenPermissionRequirement>;
77
+ readonly capabilities: Map<string, ScreenCapabilityRequirement>;
78
+ readonly diagnostics: ExpoRuntimeDiagnostic[];
79
+ readonly dependencies: Map<string, ExpoRuntimeDependency>;
80
+ readonly pluginOptions: Map<string, Record<string, boolean | string>>;
81
+ readonly configHints: Set<string>;
82
+ readonly androidPermissions: Set<string>;
83
+ readonly providers: Set<ExpoRuntimeProviderId>;
84
+ readonly runtimeAdapters: Set<ExpoRuntimeAdapterId>;
85
+ }
98
86
 
99
- const EXPO_RUNTIME_CONFIG_HINTS = {
100
- cameraPermission: {
101
- androidPermissions: [CAMERA_ANDROID_PERMISSION],
102
- plugin: {
103
- name: 'expo-camera',
104
- options: {
105
- cameraPermission: 'Allow camera access.',
106
- },
107
- },
108
- },
109
- } as const satisfies Readonly<Record<string, ExpoRuntimeHintMetadata>>;
87
+ interface PlanningContext {
88
+ readonly state: PlanningState;
89
+ readonly capabilityRegistry: Readonly<
90
+ Partial<Record<AnkhorageCapabilityName, ExpoRuntimeCapabilityMetadata>>
91
+ >;
92
+ readonly dependencyVersions: Readonly<Record<string, string>>;
93
+ readonly permissionSupport: Readonly<Record<Permission, ExpoPermissionMetadata>>;
94
+ }
110
95
 
111
- const EXPO_CAPABILITY_RUNTIME_REGISTRY = {
112
- barcodeScanner: {
113
- impliedPermissions: [{ permission: 'camera' }],
114
- requiredPackages: ['expo-camera'],
115
- providers: ['permissions'],
116
- runtimeAdapters: ['ExpoBarcodeScannerAdapter'],
117
- androidPermissions: [CAMERA_ANDROID_PERMISSION],
118
- plugins: [
119
- {
120
- name: 'expo-camera',
121
- options: {
122
- cameraPermission: 'Allow camera access to scan barcodes.',
123
- },
124
- },
125
- ],
126
- },
127
- } as const satisfies Readonly<
128
- Partial<Record<AnkhorageCapabilityName, ExpoRuntimeCapabilityMetadata>>
129
- >;
96
+ const EXPO_RUNTIME_PACKAGE_NAME = '@ankhorage/expo-runtime';
130
97
 
131
98
  export function resolveExpoRuntimePlan(
132
99
  manifest: ExpoRuntimePlanningManifest,
133
100
  options: ResolveExpoRuntimePlanOptions = {},
134
101
  ): ExpoRuntimePlan {
135
- const permissionsByName = new Map<string, ScreenPermissionRequirement>();
136
- const impliedPermissionsByName = new Map<string, ScreenPermissionRequirement>();
137
- const capabilitiesByName = new Map<string, ScreenCapabilityRequirement>();
138
-
139
- for (const screen of Object.values(manifest.screens)) {
140
- screen.requires?.permissions?.forEach((requirement) => {
141
- permissionsByName.set(requirement.permission, requirement);
142
- });
143
- screen.requires?.capabilities?.forEach((requirement) => {
144
- capabilitiesByName.set(requirement.capability, requirement);
145
- });
146
- }
147
-
148
- const capabilityRegistry: Readonly<
149
- Partial<Record<AnkhorageCapabilityName, ExpoRuntimeCapabilityMetadata>>
150
- > = options.capabilityRegistry ?? EXPO_CAPABILITY_RUNTIME_REGISTRY;
151
-
152
- for (const capabilityRequirement of capabilitiesByName.values()) {
153
- capabilityRegistry[capabilityRequirement.capability]?.impliedPermissions?.forEach(
154
- (permissionRequirement: ScreenPermissionRequirement) => {
155
- if (!permissionsByName.has(permissionRequirement.permission)) {
156
- permissionsByName.set(permissionRequirement.permission, permissionRequirement);
157
- impliedPermissionsByName.set(permissionRequirement.permission, permissionRequirement);
158
- }
159
- },
160
- );
102
+ const state = createPlanningState(manifest);
103
+ const context: PlanningContext = {
104
+ state,
105
+ capabilityRegistry: options.capabilityRegistry ?? EXPO_CAPABILITY_RUNTIME_REGISTRY,
106
+ dependencyVersions: options.dependencyVersions ?? GENERATED_RUNTIME_DEPENDENCY_VERSIONS,
107
+ permissionSupport: options.permissionSupport ?? EXPO_PERMISSION_SUPPORT,
108
+ };
109
+ addImpliedPermissions(context);
110
+ planPermissions(context);
111
+ planCapabilities(context);
112
+ if (state.providers.size > 0 || state.runtimeAdapters.size > 0) {
113
+ addDependency(context, EXPO_RUNTIME_PACKAGE_NAME, 'runtime:expo');
161
114
  }
115
+ return buildPlan(state);
116
+ }
162
117
 
163
- const diagnostics: ExpoRuntimeDiagnostic[] = [];
164
- const dependencies = new Map<string, ExpoRuntimeDependency>();
165
- const pluginOptions = new Map<string, Record<string, boolean | string>>();
166
- const configHints = new Set<string>();
167
- const androidPermissions = new Set<string>();
168
- const providers = new Set<ExpoRuntimeProviderId>();
169
- const runtimeAdapters = new Set<ExpoRuntimeAdapterId>();
170
- const dependencyVersions: Readonly<Record<string, string>> =
171
- options.dependencyVersions ?? GENERATED_EXPO_DEPENDENCY_VERSIONS;
172
- const permissionSupport = options.permissionSupport ?? EXPO_PERMISSION_SUPPORT;
173
- const configHintRegistry: Readonly<Record<string, ExpoRuntimeHintMetadata>> =
174
- EXPO_RUNTIME_CONFIG_HINTS;
175
-
176
- const addDependency = (name: string, reason: string) => {
177
- const version = dependencyVersions[name];
178
- if (version === undefined) {
179
- diagnostics.push({
180
- severity: 'warning',
181
- requirementType: 'package',
182
- requirement: name,
183
- message: `No generated-app dependency version is registered for '${name}'.`,
184
- });
185
- return;
118
+ function createPlanningState(manifest: ExpoRuntimePlanningManifest): PlanningState {
119
+ const permissions = new Map<string, ScreenPermissionRequirement>();
120
+ const capabilities = new Map<string, ScreenCapabilityRequirement>();
121
+ for (const screen of Object.values(manifest.screens)) {
122
+ for (const requirement of screen.requires?.permissions ?? []) {
123
+ permissions.set(requirement.permission, requirement);
186
124
  }
187
-
188
- const existing = dependencies.get(name);
189
- if (existing) {
190
- dependencies.set(name, {
191
- ...existing,
192
- reasons: Array.from(new Set([...existing.reasons, reason])),
193
- });
194
- return;
125
+ for (const requirement of screen.requires?.capabilities ?? []) {
126
+ capabilities.set(requirement.capability, requirement);
195
127
  }
196
-
197
- dependencies.set(name, {
198
- name,
199
- version,
200
- reasons: [reason],
201
- });
202
- };
203
-
204
- const addProvider = (provider: ExpoRuntimeProviderId, reason: string) => {
205
- providers.add(provider);
206
- addDependency(EXPO_RUNTIME_PROVIDER_PACKAGES[provider], reason);
207
- };
208
-
209
- const addPlugin = (plugin: ExpoRuntimeConfigPlugin) => {
210
- const existingOptions = pluginOptions.get(plugin.name) ?? {};
211
- pluginOptions.set(plugin.name, {
212
- ...existingOptions,
213
- ...(plugin.options ?? {}),
214
- });
128
+ }
129
+ return {
130
+ permissions,
131
+ capabilities,
132
+ impliedPermissions: new Map(),
133
+ diagnostics: [],
134
+ dependencies: new Map(),
135
+ pluginOptions: new Map(),
136
+ configHints: new Set(),
137
+ androidPermissions: new Set(),
138
+ providers: new Set(),
139
+ runtimeAdapters: new Set(),
215
140
  };
141
+ }
216
142
 
217
- for (const permissionRequirement of permissionsByName.values()) {
218
- if (!isPermission(permissionRequirement.permission)) {
219
- diagnostics.push({
220
- severity: 'error',
221
- requirementType: 'permission',
222
- requirement: permissionRequirement.permission,
223
- message: `Unknown permission '${permissionRequirement.permission}' cannot be resolved for Expo runtime generation.`,
224
- });
225
- continue;
226
- }
227
-
228
- const supportMetadata = permissionSupport[permissionRequirement.permission];
229
- if (
230
- supportMetadata.support === 'unsupported' ||
231
- supportMetadata.support === 'notImplemented' ||
232
- supportMetadata.support === 'limited'
233
- ) {
234
- diagnostics.push({
235
- severity: supportMetadata.support === 'limited' ? 'warning' : 'error',
236
- requirementType: 'permission',
237
- requirement: permissionRequirement.permission,
238
- message: `Expo runtime support for '${permissionRequirement.permission}' is '${supportMetadata.support}'.`,
239
- support: supportMetadata.support,
240
- });
241
- continue;
242
- }
243
-
244
- if (supportMetadata.support === 'notRequired') {
245
- continue;
143
+ function addImpliedPermissions(context: PlanningContext): void {
144
+ const { state } = context;
145
+ for (const capability of state.capabilities.values()) {
146
+ const metadata = findCapabilityMetadata(context.capabilityRegistry, capability);
147
+ for (const permission of metadata?.impliedPermissions ?? []) {
148
+ if (!state.permissions.has(permission.permission)) {
149
+ state.permissions.set(permission.permission, permission);
150
+ state.impliedPermissions.set(permission.permission, permission);
151
+ }
246
152
  }
153
+ }
154
+ }
247
155
 
248
- addProvider('permissions', `permission:${permissionRequirement.permission}`);
249
- supportMetadata.requiredPackages.forEach((packageName) => {
250
- addDependency(packageName, `permission:${permissionRequirement.permission}`);
251
- });
252
-
253
- supportMetadata.configHints.forEach((configHint) => {
254
- configHints.add(configHint);
255
- const configHintMetadata = configHintRegistry[configHint];
256
- if (!configHintMetadata) {
257
- diagnostics.push({
258
- severity: 'warning',
259
- requirementType: 'configHint',
260
- requirement: configHint,
261
- message: `Config hint '${configHint}' is not translated into generated Expo config yet.`,
262
- });
263
- return;
264
- }
156
+ function planPermissions(context: PlanningContext): void {
157
+ for (const requirement of context.state.permissions.values()) {
158
+ planPermission(context, requirement);
159
+ }
160
+ }
265
161
 
266
- configHintMetadata.androidPermissions?.forEach((permissionName) => {
267
- androidPermissions.add(permissionName);
268
- });
269
- if (configHintMetadata.plugin) {
270
- addPlugin(configHintMetadata.plugin);
271
- }
272
- });
162
+ function planPermission(context: PlanningContext, requirement: ScreenPermissionRequirement): void {
163
+ if (!isPermission(requirement.permission)) {
164
+ context.state.diagnostics.push(unknownPermissionDiagnostic(requirement.permission));
165
+ return;
166
+ }
167
+ const metadata = findPermissionSupport(context.permissionSupport, requirement.permission);
168
+ if (isUnsupported(metadata.support)) {
169
+ context.state.diagnostics.push(
170
+ unsupportedPermissionDiagnostic(requirement.permission, metadata.support),
171
+ );
172
+ return;
173
+ }
174
+ if (metadata.support === 'notRequired') {
175
+ return;
273
176
  }
177
+ const reason = `permission:${requirement.permission}`;
178
+ addProvider(context, 'permissions', reason);
179
+ metadata.requiredPackages.forEach((name) => addDependency(context, name, reason));
180
+ metadata.configHints.forEach((hint) => applyConfigHint(context.state, hint));
181
+ }
274
182
 
275
- for (const capabilityRequirement of capabilitiesByName.values()) {
276
- const capabilityMetadata = capabilityRegistry[capabilityRequirement.capability];
277
- if (!capabilityMetadata) {
278
- diagnostics.push({
279
- severity: 'warning',
280
- requirementType: 'capability',
281
- requirement: capabilityRequirement.capability,
282
- message: `No Expo runtime metadata is registered for capability '${capabilityRequirement.capability}'.`,
283
- });
183
+ function planCapabilities(context: PlanningContext): void {
184
+ for (const requirement of context.state.capabilities.values()) {
185
+ const metadata = findCapabilityMetadata(context.capabilityRegistry, requirement);
186
+ if (metadata === undefined) {
187
+ context.state.diagnostics.push(unknownCapabilityDiagnostic(requirement.capability));
284
188
  continue;
285
189
  }
190
+ const reason = `capability:${requirement.capability}`;
191
+ metadata.requiredPackages?.forEach((name) => addDependency(context, name, reason));
192
+ metadata.providers?.forEach((provider) => addProvider(context, provider, reason));
193
+ metadata.runtimeAdapters?.forEach((adapter) => context.state.runtimeAdapters.add(adapter));
194
+ metadata.androidPermissions?.forEach((permission) =>
195
+ context.state.androidPermissions.add(permission),
196
+ );
197
+ metadata.plugins?.forEach((plugin) => addPlugin(context.state, plugin));
198
+ }
199
+ }
286
200
 
287
- capabilityMetadata.requiredPackages?.forEach((packageName) => {
288
- addDependency(packageName, `capability:${capabilityRequirement.capability}`);
289
- });
290
- capabilityMetadata.providers?.forEach((provider) => {
291
- addProvider(provider, `capability:${capabilityRequirement.capability}`);
292
- });
293
- capabilityMetadata.runtimeAdapters?.forEach((runtimeAdapter) => {
294
- runtimeAdapters.add(runtimeAdapter);
201
+ function applyConfigHint(state: PlanningState, configHint: string): void {
202
+ state.configHints.add(configHint);
203
+ const metadata = findConfigHintMetadata(configHint);
204
+ if (metadata === undefined) {
205
+ state.diagnostics.push({
206
+ severity: 'warning',
207
+ requirementType: 'configHint',
208
+ requirement: configHint,
209
+ message: `Config hint '${configHint}' is not translated into generated Expo config yet.`,
295
210
  });
296
- capabilityMetadata.androidPermissions?.forEach((permissionName) => {
297
- androidPermissions.add(permissionName);
211
+ return;
212
+ }
213
+ metadata.androidPermissions?.forEach((permission) => state.androidPermissions.add(permission));
214
+ if (metadata.plugin !== undefined) {
215
+ addPlugin(state, metadata.plugin);
216
+ }
217
+ }
218
+
219
+ function addDependency(context: PlanningContext, name: string, reason: string): void {
220
+ const version = findDependencyVersion(context.dependencyVersions, name);
221
+ if (version === undefined) {
222
+ context.state.diagnostics.push({
223
+ severity: 'warning',
224
+ requirementType: 'package',
225
+ requirement: name,
226
+ message: `No generated-app dependency version is registered for '${name}'.`,
298
227
  });
299
- capabilityMetadata.plugins?.forEach(addPlugin);
228
+ return;
300
229
  }
230
+ const existing = context.state.dependencies.get(name);
231
+ context.state.dependencies.set(name, {
232
+ name,
233
+ version,
234
+ reasons: existing === undefined ? [reason] : Array.from(new Set([...existing.reasons, reason])),
235
+ });
236
+ }
301
237
 
302
- if (providers.size > 0 || runtimeAdapters.size > 0) {
303
- addDependency(EXPO_RUNTIME_PACKAGE_NAME, 'runtime:expo');
238
+ function addProvider(
239
+ context: PlanningContext,
240
+ provider: ExpoRuntimeProviderId,
241
+ reason: string,
242
+ ): void {
243
+ context.state.providers.add(provider);
244
+ const packageName = Object.entries(EXPO_RUNTIME_PROVIDER_PACKAGES).find(
245
+ ([name]) => name === String(provider),
246
+ )?.[1];
247
+ if (packageName === undefined) {
248
+ throw new Error(`Runtime package metadata is missing for provider '${provider}'.`);
304
249
  }
250
+ addDependency(context, packageName, reason);
251
+ }
305
252
 
306
- const plugins = Array.from(pluginOptions.entries())
307
- .map(([name, pluginOptions]) => ({
308
- name,
309
- options: Object.keys(pluginOptions).length > 0 ? pluginOptions : undefined,
310
- }))
311
- .sort((left, right) => left.name.localeCompare(right.name));
253
+ function addPlugin(state: PlanningState, plugin: ExpoRuntimeConfigPlugin): void {
254
+ const existingOptions = state.pluginOptions.get(plugin.name) ?? {};
255
+ state.pluginOptions.set(plugin.name, { ...existingOptions, ...(plugin.options ?? {}) });
256
+ }
312
257
 
258
+ function buildPlan(state: PlanningState): ExpoRuntimePlan {
259
+ const plugins = Array.from(state.pluginOptions, ([name, options]) => ({
260
+ name,
261
+ options: Object.keys(options).length > 0 ? options : undefined,
262
+ })).sort((left, right) => left.name.localeCompare(right.name));
313
263
  return {
314
- permissions: Array.from(permissionsByName.values()).sort(comparePermissionRequirements),
315
- capabilities: Array.from(capabilitiesByName.values()).sort(compareCapabilityRequirements),
316
- impliedPermissions: Array.from(impliedPermissionsByName.values()).sort(
317
- comparePermissionRequirements,
318
- ),
319
- dependencies: Array.from(dependencies.values()).sort((left, right) =>
264
+ permissions: Array.from(state.permissions.values()).sort(comparePermissions),
265
+ capabilities: Array.from(state.capabilities.values()).sort(compareCapabilities),
266
+ impliedPermissions: Array.from(state.impliedPermissions.values()).sort(comparePermissions),
267
+ dependencies: Array.from(state.dependencies.values()).sort((left, right) =>
320
268
  left.name.localeCompare(right.name),
321
269
  ),
322
270
  nativeConfig: {
323
- androidPermissions: Array.from(androidPermissions).sort(),
324
- configHints: Array.from(configHints).sort(),
271
+ androidPermissions: Array.from(state.androidPermissions).sort(),
272
+ configHints: Array.from(state.configHints).sort(),
325
273
  plugins,
326
274
  },
327
- providers: Array.from(providers).sort(),
328
- runtimeAdapters: Array.from(runtimeAdapters).sort(),
329
- usesExpoRuntimeRegistry: runtimeAdapters.size > 0,
330
- needsPermissionsProvider: providers.has('permissions'),
331
- diagnostics,
275
+ providers: Array.from(state.providers).sort(),
276
+ runtimeAdapters: Array.from(state.runtimeAdapters).sort(),
277
+ usesExpoRuntimeRegistry: state.runtimeAdapters.size > 0,
278
+ needsPermissionsProvider: state.providers.has('permissions'),
279
+ diagnostics: state.diagnostics,
332
280
  };
333
281
  }
334
282
 
335
- function compareCapabilityRequirements(
283
+ function findPermissionSupport(
284
+ registry: Readonly<Record<Permission, ExpoPermissionMetadata>>,
285
+ permission: Permission,
286
+ ): ExpoPermissionMetadata {
287
+ const metadata = Object.entries(registry).find(([name]) => name === String(permission))?.[1];
288
+ if (metadata === undefined) {
289
+ throw new Error(`Permission metadata is missing for '${permission}'.`);
290
+ }
291
+ return metadata;
292
+ }
293
+
294
+ function findDependencyVersion(
295
+ versions: Readonly<Record<string, string>>,
296
+ name: string,
297
+ ): string | undefined {
298
+ return Object.entries(versions).find(([candidate]) => candidate === name)?.[1];
299
+ }
300
+
301
+ function isUnsupported(support: PermissionSupport): boolean {
302
+ return support === 'unsupported' || support === 'notImplemented' || support === 'limited';
303
+ }
304
+
305
+ function compareCapabilities(
336
306
  left: ScreenCapabilityRequirement,
337
307
  right: ScreenCapabilityRequirement,
338
308
  ): number {
339
309
  return left.capability.localeCompare(right.capability);
340
310
  }
341
311
 
342
- function comparePermissionRequirements(
312
+ function comparePermissions(
343
313
  left: ScreenPermissionRequirement,
344
314
  right: ScreenPermissionRequirement,
345
315
  ): number {