From 14eeba8bcbff6d877e8cf2cc346dfc852294d36a Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Tue, 13 Oct 2026 16:26:11 -0400 Subject: [PATCH 1/3] [eas-cli] Fix App Clip on-demand install capability sync Map com.apple.developer.on-demand-install-capable to Apple's ON_DEMAND_INSTALL_CAPABLE capability type. App Store Connect reports App Clip bundle IDs with that type, so the sync never matched the existing capability and tried to enable ONDEMANDINSTALL_EXTENSIONS, which Apple rejects. --- .../__tests__/bundleIdCapabilities-test.ts | 21 ++++++++++++++++++- .../ios/appstore/capabilityList.ts | 2 +- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/eas-cli/src/credentials/ios/appstore/__tests__/bundleIdCapabilities-test.ts b/packages/eas-cli/src/credentials/ios/appstore/__tests__/bundleIdCapabilities-test.ts index d4c3a9345b..c7450990c4 100644 --- a/packages/eas-cli/src/credentials/ios/appstore/__tests__/bundleIdCapabilities-test.ts +++ b/packages/eas-cli/src/credentials/ios/appstore/__tests__/bundleIdCapabilities-test.ts @@ -67,6 +67,25 @@ describe(syncCapabilitiesForEntitlementsAsync, () => { ]); }); + it('does not re-enable the on-demand install capability of an App Clip', async () => { + const capabilities = [ + { + id: 'QQT5Y8UUW9_ON_DEMAND_INSTALL_CAPABLE', + attributes: { settings: null, capabilityType: 'ON_DEMAND_INSTALL_CAPABLE' }, + }, + ].map(({ id, attributes }) => new BundleIdCapability(ctx, id, attributes as any)); + const bundleId = createMockBundleId('QQT5Y8UUW9', capabilities); + + const result = await syncCapabilitiesForEntitlementsAsync( + bundleId, + { 'com.apple.developer.on-demand-install-capable': true }, + noBroadcastNotificationOption + ); + + expect(result).toStrictEqual({ enabled: [], disabled: [] }); + expect(bundleId.updateBundleIdCapabilityAsync).not.toHaveBeenCalled(); + }); + it('skips syncing capabilities with settings if they do not contain the expected `enabled` field', async () => { const capabilityJson = { id: 'U78L9459DG_APPLE_ID_AUTH', @@ -509,7 +528,7 @@ describe(syncCapabilitiesForEntitlementsAsync, () => { option: 'ON', }, { - capabilityType: 'ONDEMANDINSTALL_EXTENSIONS', + capabilityType: 'ON_DEMAND_INSTALL_CAPABLE', option: 'ON', }, { diff --git a/packages/eas-cli/src/credentials/ios/appstore/capabilityList.ts b/packages/eas-cli/src/credentials/ios/appstore/capabilityList.ts index 1e07091678..4f517a4247 100644 --- a/packages/eas-cli/src/credentials/ios/appstore/capabilityList.ts +++ b/packages/eas-cli/src/credentials/ios/appstore/capabilityList.ts @@ -539,7 +539,7 @@ export const CapabilityMapping: CapabilityClassifier[] = [ { entitlement: 'com.apple.developer.on-demand-install-capable', name: 'On Demand Install Capable for App Clip Extensions', - capability: CapabilityType.ON_DEMAND_INSTALL_EXTENSIONS, + capability: CapabilityType.ON_DEMAND_INSTALL_CAPABLE, validateOptions: validateBooleanOptions, getSyncOperation: getBooleanSyncOperation, }, From b1af9938cd3209b24f630d29325bd464ddd50f2e Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Tue, 13 Oct 2026 18:35:34 -0400 Subject: [PATCH 2/3] Add changelog entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7315f287c7..38ae575ceb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ This is the log of notable changes to EAS CLI and related packages. ### ๐Ÿ› Bug fixes +- [eas-cli] Fix capability sync failing for App Clip targets with the on-demand install entitlement. ([#4617](https://github.com/expo/eas-cli/pull/4617) by [@janicduplessis](https://github.com/janicduplessis)) + ### ๐Ÿงน Chores - [build-tools] Disable the Argent video watermark in `eas/start_argent_remote_session`. ([#4574](https://github.com/expo/eas-cli/pull/4574) by [@szdziedzic](https://github.com/szdziedzic)) From 3f047903e5f2d723fe58bea98e34a163b64eddc1 Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Tue, 13 Oct 2026 18:38:35 -0400 Subject: [PATCH 3/3] [eas-cli] Don't disable the App Clip on-demand install capability --- .../__tests__/bundleIdCapabilities-test.ts | 21 +++++++++++++++++++ .../ios/appstore/bundleIdCapabilities.ts | 5 ++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/eas-cli/src/credentials/ios/appstore/__tests__/bundleIdCapabilities-test.ts b/packages/eas-cli/src/credentials/ios/appstore/__tests__/bundleIdCapabilities-test.ts index c7450990c4..f2ebdff713 100644 --- a/packages/eas-cli/src/credentials/ios/appstore/__tests__/bundleIdCapabilities-test.ts +++ b/packages/eas-cli/src/credentials/ios/appstore/__tests__/bundleIdCapabilities-test.ts @@ -86,6 +86,27 @@ describe(syncCapabilitiesForEntitlementsAsync, () => { expect(bundleId.updateBundleIdCapabilityAsync).not.toHaveBeenCalled(); }); + it('does not disable the on-demand install capability of an App Clip when the entitlement is absent', async () => { + const capabilities = [ + { + id: 'QQT5Y8UUW9_ON_DEMAND_INSTALL_CAPABLE', + attributes: { settings: null, capabilityType: 'ON_DEMAND_INSTALL_CAPABLE' }, + }, + ].map(({ id, attributes }) => new BundleIdCapability(ctx, id, attributes as any)); + const bundleId = createMockBundleId('QQT5Y8UUW9', capabilities); + + const result = await syncCapabilitiesForEntitlementsAsync( + bundleId, + { + 'com.apple.developer.parent-application-identifiers': ['$(AppIdentifierPrefix)com.example'], + }, + noBroadcastNotificationOption + ); + + expect(result).toStrictEqual({ enabled: [], disabled: [] }); + expect(bundleId.updateBundleIdCapabilityAsync).not.toHaveBeenCalled(); + }); + it('skips syncing capabilities with settings if they do not contain the expected `enabled` field', async () => { const capabilityJson = { id: 'U78L9459DG_APPLE_ID_AUTH', diff --git a/packages/eas-cli/src/credentials/ios/appstore/bundleIdCapabilities.ts b/packages/eas-cli/src/credentials/ios/appstore/bundleIdCapabilities.ts index bae4d86736..06b53b9824 100644 --- a/packages/eas-cli/src/credentials/ios/appstore/bundleIdCapabilities.ts +++ b/packages/eas-cli/src/credentials/ios/appstore/bundleIdCapabilities.ts @@ -185,9 +185,12 @@ function getCapabilitiesToDisable( for (const existingCapability of currentCapabilities) { // GC and IAP are always enabled in apps by default so we should avoid modifying them. + // App Clip bundle IDs are created with ON_DEMAND_INSTALL_CAPABLE, and Xcode adds its + // entitlement at signing time, so it is often missing from the local entitlements. if ( existingCapability.isType(CapabilityType.IN_APP_PURCHASE) || - existingCapability.isType(CapabilityType.GAME_CENTER) + existingCapability.isType(CapabilityType.GAME_CENTER) || + existingCapability.isType(CapabilityType.ON_DEMAND_INSTALL_CAPABLE) ) { continue; }