From d2ff8eb3bec3aa172415c03ab986974c9bf770ba Mon Sep 17 00:00:00 2001 From: Shawn Jackson Date: Sat, 18 Apr 2026 16:20:07 -0700 Subject: [PATCH 1/2] RR-T43 Fixing build issue --- app.config.ts | 7 ++++++- env.js | 7 +++++++ plugins/withLiveActivities.js | 31 +++++++++++++++++++------------ 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/app.config.ts b/app.config.ts index bfb130e..86fb369 100644 --- a/app.config.ts +++ b/app.config.ts @@ -289,7 +289,12 @@ export default ({ config }: ConfigContext): ExpoConfig => ({ './customGradle.plugin.js', './customManifest.plugin.js', './plugins/withInCallAudioModule.js', - './plugins/withLiveActivities.js', + [ + './plugins/withLiveActivities.js', + { + appGroupId: Env.IOS_APP_GROUP, + }, + ], ['app-icon-badge', appIconBadgeConfig], ], extra: { diff --git a/env.js b/env.js index a9762ff..7d29e7d 100644 --- a/env.js +++ b/env.js @@ -40,6 +40,7 @@ const NAME = 'Resgrid Responder'; // app name const EXPO_ACCOUNT_OWNER = 'resgrid'; // expo account owner const EAS_PROJECT_ID = '026d4a74-f01d-41db-ae57-67f8c65a5f79'; // eas project id const SCHEME = 'ResgridRespond'; // app scheme +const IOS_APP_GROUP_SHARED = 'group.com.wavetech.resgrid.shared'; /** * We declare a function withEnvSuffix that will add a suffix to the variable name based on the APP_ENV @@ -52,6 +53,10 @@ const withEnvSuffix = (name) => { return APP_ENV === 'production' || APP_ENV === 'internal' ? name : `${name}.${APP_ENV}`; }; +const getIosAppGroup = () => { + return APP_ENV === 'production' || APP_ENV === 'internal' ? IOS_APP_GROUP_SHARED : `group.${withEnvSuffix(BUNDLE_ID)}`; +}; + /** * 2nd part: Define your env variables schema * we use zod to define our env variables schema @@ -100,6 +105,7 @@ const client = z.object({ const buildTime = z.object({ EXPO_ACCOUNT_OWNER: z.string(), EAS_PROJECT_ID: z.string(), + IOS_APP_GROUP: z.string(), // ADD YOUR BUILD TIME ENV VARS HERE }); @@ -139,6 +145,7 @@ const _clientEnv = { const _buildTimeEnv = { EXPO_ACCOUNT_OWNER, EAS_PROJECT_ID, + IOS_APP_GROUP: getIosAppGroup(), // ADD YOUR ENV VARS HERE TOO }; diff --git a/plugins/withLiveActivities.js b/plugins/withLiveActivities.js index d4aa14b..636ea16 100644 --- a/plugins/withLiveActivities.js +++ b/plugins/withLiveActivities.js @@ -37,6 +37,15 @@ function entitlementsXml(appGroupId) { `; } +function resolveAppGroupId(cfg, appGroupId) { + if (appGroupId) { + return appGroupId; + } + + const bundleId = cfg.ios?.bundleIdentifier ?? 'com.example.app'; + return `group.${bundleId}`; +} + /** * Returns the Info.plist XML for the widget extension target. */ @@ -92,7 +101,7 @@ const withLiveActivitiesInfoPlist = (config) => { * WidgetKit extension sandbox. CallCheckInAttributes.swift is duplicated * so that both the extension and the main app each have the type in scope. */ -const withLiveActivitiesFiles = (config) => { +const withLiveActivitiesFiles = (config, appGroupId) => { return withDangerousMod(config, [ 'ios', async (cfg) => { @@ -101,8 +110,7 @@ const withLiveActivitiesFiles = (config) => { const widgetDir = path.join(iosRoot, WIDGET_EXTENSION_NAME); const appName = IOSConfig.XcodeUtils.getHackyProjectName(projectRoot, cfg) || 'ResgridResponder'; const appDir = path.join(iosRoot, appName); - const bundleId = cfg.ios?.bundleIdentifier ?? 'com.example.app'; - const appGroupId = `group.${bundleId}`; + const resolvedAppGroupId = resolveAppGroupId(cfg, appGroupId); // ── 1. Widget extension directory ────────────────────────────────────── if (!fs.existsSync(widgetDir)) { @@ -138,7 +146,7 @@ struct CheckInTimerWidgetBundle: WidgetBundle { fs.writeFileSync(path.join(widgetDir, 'Info.plist'), widgetInfoPlistXml()); // ── 5. Widget extension entitlements (App Group) ─────────────────────── - fs.writeFileSync(path.join(widgetDir, `${WIDGET_EXTENSION_NAME}.entitlements`), entitlementsXml(appGroupId)); + fs.writeFileSync(path.join(widgetDir, `${WIDGET_EXTENSION_NAME}.entitlements`), entitlementsXml(resolvedAppGroupId)); // ── 6. LiveActivityModule.swift → main app dir ───────────────────────── // This file imports React and uses RCTPromiseResolveBlock; it must be @@ -173,13 +181,12 @@ struct CheckInTimerWidgetBundle: WidgetBundle { * This uses the managed withEntitlementsPlist modifier so that the change * is written back through Expo's plist serialiser (safe, idempotent). */ -const withLiveActivitiesAppEntitlements = (config) => { +const withLiveActivitiesAppEntitlements = (config, appGroupId) => { return withEntitlementsPlist(config, (cfg) => { - const bundleId = cfg.ios?.bundleIdentifier ?? 'com.example.app'; - const appGroupId = `group.${bundleId}`; + const resolvedAppGroupId = resolveAppGroupId(cfg, appGroupId); const existing = cfg.modResults['com.apple.security.application-groups']; - if (!Array.isArray(existing) || !existing.includes(appGroupId)) { - cfg.modResults['com.apple.security.application-groups'] = [...(Array.isArray(existing) ? existing : []), appGroupId]; + if (!Array.isArray(existing) || !existing.includes(resolvedAppGroupId)) { + cfg.modResults['com.apple.security.application-groups'] = [...(Array.isArray(existing) ? existing : []), resolvedAppGroupId]; } return cfg; }); @@ -306,10 +313,10 @@ const withLiveActivitiesXcodeProject = (config) => { * 3. Entitlements for the main app (withEntitlementsPlist) * 4. Xcode project registration last (needs the files to already exist) */ -module.exports = (config) => { +module.exports = (config, { appGroupId } = {}) => { config = withLiveActivitiesInfoPlist(config); - config = withLiveActivitiesFiles(config); - config = withLiveActivitiesAppEntitlements(config); + config = withLiveActivitiesFiles(config, appGroupId); + config = withLiveActivitiesAppEntitlements(config, appGroupId); config = withLiveActivitiesXcodeProject(config); return config; }; From 03ab681020d45fa38e5cbf1df14d4155efff3141 Mon Sep 17 00:00:00 2001 From: Shawn Jackson Date: Sun, 19 Apr 2026 16:27:20 -0700 Subject: [PATCH 2/2] Build fix --- README.md | 4 ++- app.config.ts | 1 + env.js | 2 ++ plugins/withLiveActivities.js | 53 ++++++++++++++++++++++++++++++++--- 4 files changed, 55 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1229b1d..d39ced4 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,8 @@ To run this project, you will need to add the following environment variables to `appKey` +For iOS CI builds that generate the `CheckInTimerWidget` target, set `IOS_APPLE_TEAM_ID` (or `EXPO_APPLE_TEAM_ID` / `APPLE_TEAM_ID`) so the widget extension inherits the correct signing team during prebuild. + ## :toolbox: Getting Started @@ -116,4 +118,4 @@ npm run start ## :warning: License -Distributed under the Apache License 2.0. See LICENSE.txt for more information. \ No newline at end of file +Distributed under the Apache License 2.0. See LICENSE.txt for more information. diff --git a/app.config.ts b/app.config.ts index 86fb369..a32453c 100644 --- a/app.config.ts +++ b/app.config.ts @@ -48,6 +48,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({ buildNumber: packageJSON.version, supportsTablet: true, bundleIdentifier: Env.BUNDLE_ID, + ...(Env.IOS_APPLE_TEAM_ID ? { appleTeamId: Env.IOS_APPLE_TEAM_ID } : {}), requireFullScreen: true, infoPlist: { UIBackgroundModes: ['remote-notification', 'audio', 'bluetooth-central', 'voip'], diff --git a/env.js b/env.js index 7d29e7d..f3b90e1 100644 --- a/env.js +++ b/env.js @@ -106,6 +106,7 @@ const buildTime = z.object({ EXPO_ACCOUNT_OWNER: z.string(), EAS_PROJECT_ID: z.string(), IOS_APP_GROUP: z.string(), + IOS_APPLE_TEAM_ID: z.string().optional(), // ADD YOUR BUILD TIME ENV VARS HERE }); @@ -146,6 +147,7 @@ const _buildTimeEnv = { EXPO_ACCOUNT_OWNER, EAS_PROJECT_ID, IOS_APP_GROUP: getIosAppGroup(), + IOS_APPLE_TEAM_ID: process.env.IOS_APPLE_TEAM_ID || process.env.EXPO_APPLE_TEAM_ID || process.env.APPLE_TEAM_ID, // ADD YOUR ENV VARS HERE TOO }; diff --git a/plugins/withLiveActivities.js b/plugins/withLiveActivities.js index 636ea16..416f8e8 100644 --- a/plugins/withLiveActivities.js +++ b/plugins/withLiveActivities.js @@ -46,6 +46,37 @@ function resolveAppGroupId(cfg, appGroupId) { return `group.${bundleId}`; } +function trimDoubleQuotes(value) { + return value.replace(/^"(.*)"$/, '$1'); +} + +function ensureDoubleQuotes(value) { + return value.startsWith('"') ? value : `"${value}"`; +} + +function getTargetBuildConfigurations(project, target) { + const configurationListId = target?.target?.buildConfigurationList ?? target?.pbxNativeTarget?.buildConfigurationList; + if (!configurationListId) { + return []; + } + + return IOSConfig.XcodeUtils.getBuildConfigurationsForListId(project, configurationListId); +} + +function getBuildSettingValue(buildConfigurations, key) { + for (const [, buildConfiguration] of buildConfigurations) { + const value = buildConfiguration?.buildSettings?.[key]; + if (typeof value === 'string' || typeof value === 'number') { + const normalizedValue = trimDoubleQuotes(String(value)); + if (normalizedValue) { + return normalizedValue; + } + } + } + + return null; +} + /** * Returns the Info.plist XML for the widget extension target. */ @@ -212,6 +243,12 @@ const withLiveActivitiesXcodeProject = (config) => { const widgetBundleId = `${bundleId}.${WIDGET_EXTENSION_NAME}`; const deploymentTarget = '16.2'; const projectName = IOSConfig.XcodeUtils.getProductName(xcodeProject) || 'ResgridResponder'; + const appTarget = xcodeProject.getTarget('com.apple.product-type.application'); + const appBuildConfigurations = appTarget ? getTargetBuildConfigurations(xcodeProject, appTarget) : []; + const developmentTeam = cfg.ios?.appleTeamId ?? getBuildSettingValue(appBuildConfigurations, 'DEVELOPMENT_TEAM'); + const currentProjectVersion = getBuildSettingValue(appBuildConfigurations, 'CURRENT_PROJECT_VERSION') ?? '1'; + const marketingVersion = getBuildSettingValue(appBuildConfigurations, 'MARKETING_VERSION') ?? '1.0'; + const targetedDeviceFamily = getBuildSettingValue(appBuildConfigurations, 'TARGETED_DEVICE_FAMILY') ?? '1,2'; // ── Idempotency guard ──────────────────────────────────────────────────── // addTarget() stores target names with surrounding quotes in the comment @@ -251,7 +288,6 @@ const withLiveActivitiesXcodeProject = (config) => { // ── 5. Add LiveActivityModule + shared attributes to the main app target ── // These files have React imports and must be compiled in the app target. - const appTarget = xcodeProject.getTarget('com.apple.product-type.application'); if (appTarget) { for (const filename of APP_SWIFT_FILES) { IOSConfig.XcodeUtils.addBuildSourceFileToGroup({ @@ -295,13 +331,22 @@ const withLiveActivitiesXcodeProject = (config) => { // Minimum deployment target required for Live Activities s.IPHONEOS_DEPLOYMENT_TARGET = deploymentTarget; // Mirror the main app's device family and versioning - s.TARGETED_DEVICE_FAMILY = '"1,2"'; - s.CURRENT_PROJECT_VERSION = 1; - s.MARKETING_VERSION = '1.0'; + s.TARGETED_DEVICE_FAMILY = ensureDoubleQuotes(targetedDeviceFamily); + s.CURRENT_PROJECT_VERSION = currentProjectVersion; + s.MARKETING_VERSION = marketingVersion; s.SKIP_INSTALL = 'YES'; + if (developmentTeam) { + s.DEVELOPMENT_TEAM = developmentTeam; + s.CODE_SIGN_STYLE = 'Automatic'; + } } } + if (developmentTeam) { + xcodeProject.addTargetAttribute('DevelopmentTeam', ensureDoubleQuotes(developmentTeam), target); + xcodeProject.addTargetAttribute('ProvisioningStyle', 'Automatic', target); + } + return cfg; }); };