-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathwithCheckInLiveActivity.js
More file actions
558 lines (503 loc) · 22.8 KB
/
Copy pathwithCheckInLiveActivity.js
File metadata and controls
558 lines (503 loc) · 22.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
const { withDangerousMod, withInfoPlist, withEntitlementsPlist, withXcodeProject } = require('expo/config-plugins');
const fs = require('fs');
const path = require('path');
/**
* Resolves the iOS app target name so bridge files land in the correct folder.
*
* Resolution order:
* 1. config.modRequest.projectName (set by Expo during prebuild — preferred)
* 2. Parse ios/<project>.xcodeproj/project.pbxproj and find the PBXNativeTarget
* whose productType is "com.apple.product-type.application"
*
* Throws an explicit error if neither source yields a name, so the developer is
* informed immediately instead of files being silently written to the wrong path.
*
* @param {object} config - Expo config object inside withDangerousMod callback
* @param {string} projectRoot - absolute path to the project root
* @returns {string} iOS app target / folder name
*/
function resolveIosAppName(config, projectRoot) {
// 1. Trust Expo's own projectName first (present during `expo prebuild`)
if (config.modRequest.projectName) {
return config.modRequest.projectName;
}
// 2. Derive the name by parsing project.pbxproj
const iosDir = path.join(projectRoot, 'ios');
if (fs.existsSync(iosDir)) {
let pbxprojPath = null;
try {
const entries = fs.readdirSync(iosDir);
const xcodeprojDir = entries.find((e) => e.endsWith('.xcodeproj'));
if (xcodeprojDir) {
pbxprojPath = path.join(iosDir, xcodeprojDir, 'project.pbxproj');
}
} catch (_) {
// iosDir not readable — fall through to throw below
}
if (pbxprojPath && fs.existsSync(pbxprojPath)) {
const pbxContent = fs.readFileSync(pbxprojPath, 'utf8');
// Within a PBXNativeTarget block the fields appear in this order:
// name = TargetName;
// productName = TargetName;
// productReference = <hash> /* TargetName.app */;
// productType = "com.apple.product-type.application";
// The `s` (dotAll) flag lets the pattern span newlines.
const match = pbxContent.match(
/name\s*=\s*([^\s;]+)\s*;\s*productName\s*=\s*[^;]+;\s*productReference\s*=\s*[^;]+;\s*productType\s*=\s*"com\.apple\.product-type\.application"/s
);
if (match) {
return match[1].trim();
}
}
}
throw new Error(
'[withCheckInLiveActivity] Cannot determine the iOS app target name.\n' +
' • config.modRequest.projectName is not set\n' +
' • No PBXNativeTarget with productType=com.apple.product-type.application\n' +
' was found in ios/*.xcodeproj/project.pbxproj\n' +
'Ensure the iOS project has been initialised via `npx expo prebuild` before\n' +
'running this plugin, or set the `name` field in your app.config.'
);
}
/**
* CheckInTimerAttributes.swift — ActivityKit attributes for the check-in timer Live Activity
*/
const ATTRIBUTES_SWIFT = `import ActivityKit
import Foundation
struct CheckInTimerAttributes: ActivityAttributes {
public struct ContentState: Codable, Hashable {
var elapsedMinutes: Int
var status: String
var lastCheckIn: String
}
var callName: String
var callNumber: String
var timerName: String
var durationMinutes: Int
}
`;
/**
* CheckInTimerLiveActivity.swift — SwiftUI views for lock screen and Dynamic Island
*/
const LIVE_ACTIVITY_SWIFT = `import ActivityKit
import SwiftUI
import WidgetKit
struct CheckInTimerLiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: CheckInTimerAttributes.self) { context in
// Lock screen / banner UI
HStack {
VStack(alignment: .leading, spacing: 4) {
Text("\\(context.attributes.callName) #\\(context.attributes.callNumber)")
.font(.headline)
.foregroundColor(.white)
Text(context.attributes.timerName)
.font(.subheadline)
.foregroundColor(.white.opacity(0.8))
}
Spacer()
VStack(alignment: .trailing, spacing: 4) {
Text("\\(context.state.elapsedMinutes)/\\(context.attributes.durationMinutes) min")
.font(.title3)
.bold()
.foregroundColor(statusColor(context.state.status))
Text(context.state.status)
.font(.caption)
.foregroundColor(statusColor(context.state.status))
}
}
.padding()
.background(Color.black)
} dynamicIsland: { context in
DynamicIsland {
DynamicIslandExpandedRegion(.leading) {
Text(context.attributes.timerName)
.font(.caption)
}
DynamicIslandExpandedRegion(.trailing) {
Text("\\(context.state.elapsedMinutes)m")
.font(.title3)
.foregroundColor(statusColor(context.state.status))
}
DynamicIslandExpandedRegion(.bottom) {
ProgressView(value: Double(context.state.elapsedMinutes), total: Double(context.attributes.durationMinutes))
.tint(statusColor(context.state.status))
}
} compactLeading: {
Image(systemName: "timer")
.foregroundColor(statusColor(context.state.status))
} compactTrailing: {
Text("\\(context.state.elapsedMinutes)m")
.foregroundColor(statusColor(context.state.status))
} minimal: {
Image(systemName: "timer")
.foregroundColor(statusColor(context.state.status))
}
}
}
private func statusColor(_ status: String) -> Color {
switch status {
case "Ok": return .green
case "Warning": return .yellow
case "Overdue": return .red
default: return .gray
}
}
}
`;
/**
* CheckInTimerWidgetBundle.swift — Widget extension entry point
*/
const WIDGET_BUNDLE_SWIFT = `import SwiftUI
import WidgetKit
@main
struct CheckInTimerWidgetBundle: WidgetBundle {
var body: some Widget {
CheckInTimerLiveActivity()
}
}
`;
/**
* CheckInTimerActivityManager.swift — Native bridge for managing Live Activities from RN
*/
const ACTIVITY_MANAGER_SWIFT = `import ActivityKit
import Foundation
import React
@objc(CheckInTimerActivityManager)
class CheckInTimerActivityManager: NSObject {
@objc static func requiresMainQueueSetup() -> Bool { return false }
@objc
func startActivity(_ callName: String, callNumber: String, timerName: String, durationMinutes: Int,
resolver resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
if #available(iOS 16.1, *) {
let attributes = CheckInTimerAttributes(
callName: callName, callNumber: callNumber,
timerName: timerName, durationMinutes: durationMinutes)
let state = CheckInTimerAttributes.ContentState(
elapsedMinutes: 0, status: "Ok", lastCheckIn: "")
do {
let _ = try Activity.request(attributes: attributes, contentState: state)
resolve(true)
} catch {
reject("LIVE_ACTIVITY_ERROR", error.localizedDescription, error)
}
} else {
resolve(false)
}
}
@objc
func updateActivity(_ elapsedMinutes: Int, status: String,
resolver resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
if #available(iOS 16.1, *) {
Task {
let state = CheckInTimerAttributes.ContentState(
elapsedMinutes: elapsedMinutes, status: status, lastCheckIn: "")
for activity in Activity<CheckInTimerAttributes>.activities {
await activity.update(using: state)
}
resolve(true)
}
} else {
resolve(false)
}
}
@objc
func endActivity(_ resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
if #available(iOS 16.1, *) {
Task {
for activity in Activity<CheckInTimerAttributes>.activities {
await activity.end(dismissalPolicy: .immediate)
}
resolve(true)
}
} else {
resolve(false)
}
}
}
`;
/**
* CheckInTimerActivityBridge.m — ObjC bridge
*/
const BRIDGE_OBJC = `#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(CheckInTimerActivityManager, NSObject)
RCT_EXTERN_METHOD(startActivity:(NSString *)callName
callNumber:(NSString *)callNumber
timerName:(NSString *)timerName
durationMinutes:(int)durationMinutes
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(updateActivity:(int)elapsedMinutes
status:(NSString *)status
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(endActivity:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
@end
`;
/**
* Info.plist for the CheckInTimerWidget extension target.
* Required by Xcode; bundle metadata is resolved at build time via build settings.
*/
const WIDGET_INFO_PLIST = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>CheckInTimerWidget</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.widgetkit-extension</string>
</dict>
</dict>
</plist>
`;
const withCheckInLiveActivity = (config, props = {}) => {
const { teamId, enableLiveActivityEntitlement = true } = props;
// Step 1: Add NSSupportsLiveActivities to Info.plist
config = withInfoPlist(config, (config) => {
config.modResults.NSSupportsLiveActivities = true;
return config;
});
// Step 2: Add live activity entitlement (only if the provisioning profile supports it)
if (enableLiveActivityEntitlement) {
config = withEntitlementsPlist(config, (config) => {
config.modResults['com.apple.developer.live-activity'] = true;
return config;
});
}
// Step 3: Write Swift Widget Extension files and native bridge
config = withDangerousMod(config, [
'ios',
async (config) => {
const projectRoot = config.modRequest.projectRoot;
// Write Widget Extension files
const widgetDir = path.join(projectRoot, 'ios', 'CheckInTimerWidget');
if (!fs.existsSync(widgetDir)) {
fs.mkdirSync(widgetDir, { recursive: true });
}
fs.writeFileSync(path.join(widgetDir, 'CheckInTimerAttributes.swift'), ATTRIBUTES_SWIFT);
fs.writeFileSync(path.join(widgetDir, 'CheckInTimerLiveActivity.swift'), LIVE_ACTIVITY_SWIFT);
fs.writeFileSync(path.join(widgetDir, 'CheckInTimerWidgetBundle.swift'), WIDGET_BUNDLE_SWIFT);
fs.writeFileSync(path.join(widgetDir, 'Info.plist'), WIDGET_INFO_PLIST);
// Write an entitlements file for the widget extension.
// Required for signing; the entitlements can be empty but the file must exist
// so Xcode doesn't fall back to the main target's entitlements.
const widgetEntitlements = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>
`;
fs.writeFileSync(path.join(widgetDir, 'CheckInTimerWidget.entitlements'), widgetEntitlements);
// Write native bridge files to the main app directory.
// resolveIosAppName throws explicitly if the target cannot be determined,
// preventing files from being written to a wrong/hardcoded path.
const appName = resolveIosAppName(config, projectRoot);
const appDir = path.join(projectRoot, 'ios', appName);
if (!fs.existsSync(appDir)) {
fs.mkdirSync(appDir, { recursive: true });
}
fs.writeFileSync(path.join(appDir, 'CheckInTimerActivityManager.swift'), ACTIVITY_MANAGER_SWIFT);
fs.writeFileSync(path.join(appDir, 'CheckInTimerActivityBridge.m'), BRIDGE_OBJC);
return config;
},
]);
// Step 4: Add Widget Extension target to Xcode project and wire all required
// build phases, source files, and frameworks so Live Activities actually build.
config = withXcodeProject(config, (config) => {
const project = config.modResults;
const projectRoot = config.modRequest.projectRoot;
const appBundleId = config.ios?.bundleIdentifier;
if (!appBundleId) {
throw new Error(
'[withCheckInLiveActivity] config.ios.bundleIdentifier is required ' +
'to derive the widget extension bundle identifier.'
);
}
const WIDGET_NAME = 'CheckInTimerWidget';
const widgetBundleId = `${appBundleId}.${WIDGET_NAME}`;
// Idempotent: skip if the target was already added in a previous prebuild run.
// addTarget stores names with surrounding quotes in the comment key, so check both forms.
if (project.pbxTargetByName(WIDGET_NAME) || project.pbxTargetByName(`"${WIDGET_NAME}"`)) {
return config;
}
// 1. Create the PBXNativeTarget.
// addTarget('app_extension') also:
// - adds an "Embed App Extensions" CopyFiles phase to the main target
// - adds a PBXTargetDependency from main app → widget
// - creates Debug/Release XCBuildConfigurations with basic defaults
const widgetTarget = project.addTarget(WIDGET_NAME, 'app_extension', WIDGET_NAME, widgetBundleId);
// 2. Add the three build phases the widget target needs.
// These must be added before files/frameworks are wired, because the
// addSourceFile / addFramework helpers find phases by scanning the
// target's buildPhases array.
project.addBuildPhase([], 'PBXSourcesBuildPhase', 'Sources', widgetTarget.uuid);
project.addBuildPhase([], 'PBXResourcesBuildPhase', 'Resources', widgetTarget.uuid);
project.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', widgetTarget.uuid);
// 3. Create a PBX group for the widget folder and attach it to the project's
// main group so the files appear in the Xcode file navigator.
const { uuid: widgetGroupUuid } = project.addPbxGroup([], WIDGET_NAME, WIDGET_NAME);
const { firstProject } = project.getFirstProject();
const mainGroup = project.getPBXGroupByKey(firstProject.mainGroup);
if (mainGroup && !mainGroup.children.find((c) => c.comment === WIDGET_NAME)) {
mainGroup.children.push({ value: widgetGroupUuid, comment: WIDGET_NAME });
}
// 4. Add Swift source files to the widget group and to the widget's Sources phase.
// Passing the group key as the third argument to addSourceFile ensures the
// file reference lands in the right PBX group; opt.target routes the build
// file to the widget's PBXSourcesBuildPhase rather than the main app's.
const SWIFT_SOURCES = [
'CheckInTimerAttributes.swift',
'CheckInTimerLiveActivity.swift',
'CheckInTimerWidgetBundle.swift',
];
for (const filename of SWIFT_SOURCES) {
project.addSourceFile(
filename,
{ target: widgetTarget.uuid },
widgetGroupUuid
);
}
// 5. Link WidgetKit and ActivityKit into the widget's Frameworks phase.
// opt.target directs addToPbxFrameworksBuildPhase to use the widget's
// PBXFrameworksBuildPhase (added above) instead of the main app's.
project.addFramework('WidgetKit.framework', { target: widgetTarget.uuid });
project.addFramework('ActivityKit.framework', { target: widgetTarget.uuid });
// 6. Patch build settings on both Debug and Release configurations so the
// widget compiles as a Swift 5 app-extension targeting iOS 16.1+.
const targetSection = project.pbxNativeTargetSection();
// Resolve host app version numbers so the widget extension stays in sync.
// Priority: main target build settings → Expo config → hardcoded fallback.
const hostTarget = project.getFirstTarget();
let hostMarketingVersion = null;
let hostCurrentProjectVersion = null;
const hostConfigListId = targetSection[hostTarget.uuid].buildConfigurationList;
const hostConfigList = project.pbxXCConfigurationList()[hostConfigListId];
if (hostConfigList) {
const firstHostConfigUuid = hostConfigList.buildConfigurations[0]?.value;
if (firstHostConfigUuid) {
const firstHostConfig = project.pbxXCBuildConfigurationSection()[firstHostConfigUuid];
if (firstHostConfig?.buildSettings) {
hostMarketingVersion = firstHostConfig.buildSettings.MARKETING_VERSION || null;
hostCurrentProjectVersion = firstHostConfig.buildSettings.CURRENT_PROJECT_VERSION || null;
}
}
}
// MARKETING_VERSION in pbxproj is already quoted (e.g., '"1.2.3"');
// config.version is raw (e.g., '1.2.3'), so wrap it in quotes.
const resolvedMarketingVersion =
hostMarketingVersion || (config.version ? `"${config.version}"` : '"1.0"');
// CURRENT_PROJECT_VERSION in pbxproj is an unquoted number string (e.g., '42');
// config.ios?.buildNumber is raw, pass through as-is.
const resolvedCurrentProjectVersion =
hostCurrentProjectVersion || (config.ios?.buildNumber ?? '1');
// Resolve DEVELOPMENT_TEAM for the widget extension.
// Priority: plugin config param > host target build settings > env var.
// Note: EAS applies credentials *after* prebuild, so reading from the
// host target at this point will usually return null. Pass teamId via
// the plugin config in app.config.ts for reliable widget signing.
let hostDevelopmentTeam = teamId || null;
if (!hostDevelopmentTeam) {
const hostBuildConfigListId = targetSection[hostTarget.uuid].buildConfigurationList;
const hostBuildConfigList = project.pbxXCConfigurationList()[hostBuildConfigListId];
if (hostBuildConfigList) {
const firstHostConfigUuid = hostBuildConfigList.buildConfigurations[0]?.value;
if (firstHostConfigUuid) {
const firstHostConfig = project.pbxXCBuildConfigurationSection()[firstHostConfigUuid];
if (firstHostConfig?.buildSettings) {
hostDevelopmentTeam = firstHostConfig.buildSettings.DEVELOPMENT_TEAM || null;
}
}
}
}
if (!hostDevelopmentTeam) {
hostDevelopmentTeam = process.env.EXPO_APPLE_TEAM_ID || null;
}
const buildConfigListId = targetSection[widgetTarget.uuid].buildConfigurationList;
const buildConfigList = project.pbxXCConfigurationList()[buildConfigListId];
if (buildConfigList) {
for (const { value: configUuid } of buildConfigList.buildConfigurations) {
const buildConfig = project.pbxXCBuildConfigurationSection()[configUuid];
if (buildConfig) {
Object.assign(buildConfig.buildSettings, {
// Override the default addTarget placeholder (TargetName-Info.plist)
INFOPLIST_FILE: `"${WIDGET_NAME}/Info.plist"`,
CODE_SIGN_ENTITLEMENTS: `"${WIDGET_NAME}/CheckInTimerWidget.entitlements"`,
SWIFT_VERSION: '"5.0"',
TARGETED_DEVICE_FAMILY: '"1,2"',
// ActivityKit requires iOS 16.1 or later
IPHONEOS_DEPLOYMENT_TARGET: '16.1',
SKIP_INSTALL: 'YES',
CODE_SIGN_STYLE: 'Automatic',
MARKETING_VERSION: resolvedMarketingVersion,
CURRENT_PROJECT_VERSION: resolvedCurrentProjectVersion,
// Disable independent code signing for the widget extension.
// The widget is embedded in the main app and signed during the
// main target's archive/export phase. Without this, EAS (which
// uses manual signing) fails because it has no provisioning
// profile for the widget's bundle identifier.
CODE_SIGNING_ALLOWED: 'NO',
// Propagate the development team from the host target so the
// widget extension can be signed (required since Xcode 14+).
...(hostDevelopmentTeam ? { DEVELOPMENT_TEAM: hostDevelopmentTeam } : {}),
});
}
}
}
// 7. Resolve the iOS app target name inside this callback scope.
const appName = resolveIosAppName(config, projectRoot);
// 8. Ensure the bridge files are compiled as part of the main app target
// so the native module is linked at runtime.
const mainGroupKey = project.findPBXGroupKey({ name: appName });
const BRIDGE_FILES = [
`${appName}/CheckInTimerActivityManager.swift`,
`${appName}/CheckInTimerActivityBridge.m`,
];
for (const filePath of BRIDGE_FILES) {
if (!project.hasFile(filePath)) {
project.addSourceFile(filePath, { target: hostTarget.uuid }, mainGroupKey);
}
}
// 8. Ensure the main target has a bridging header configured so the ObjC
// bridge module is visible to Swift.
const BRIDGING_HEADER_FILE = `${appName}/${appName}-Bridging-Header.h`;
const bridgingHeaderPath = path.join(projectRoot, 'ios', BRIDGING_HEADER_FILE);
if (!fs.existsSync(bridgingHeaderPath)) {
fs.writeFileSync(bridgingHeaderPath, `// Auto-generated bridging header for Live Activity native bridge.\n#import <React/RCTBridgeModule.h>\n`);
}
const mainBuildConfigListId = targetSection[hostTarget.uuid].buildConfigurationList;
const mainBuildConfigList = project.pbxXCConfigurationList()[mainBuildConfigListId];
if (mainBuildConfigList) {
for (const { value: mainConfigUuid } of mainBuildConfigList.buildConfigurations) {
const mainBuildConfig = project.pbxXCBuildConfigurationSection()[mainConfigUuid];
if (mainBuildConfig && !mainBuildConfig.buildSettings.SWIFT_OBJC_BRIDGING_HEADER) {
mainBuildConfig.buildSettings.SWIFT_OBJC_BRIDGING_HEADER = `"${BRIDGING_HEADER_FILE}"`;
}
}
}
return config;
});
return config;
};
module.exports = withCheckInLiveActivity;