|
1 | | -const { withDangerousMod, withInfoPlist, withEntitlementsPlist, withXcodeProject } = require('expo/config-plugins'); |
| 1 | +const { withDangerousMod, withInfoPlist, withXcodeProject } = require('expo/config-plugins'); |
2 | 2 | const fs = require('fs'); |
3 | 3 | const path = require('path'); |
4 | 4 |
|
@@ -299,21 +299,19 @@ const WIDGET_INFO_PLIST = `<?xml version="1.0" encoding="UTF-8"?> |
299 | 299 | `; |
300 | 300 |
|
301 | 301 | const withCheckInLiveActivity = (config, props = {}) => { |
302 | | - const { teamId, enableLiveActivityEntitlement = true } = props; |
| 302 | + const { teamId } = props; |
303 | 303 |
|
304 | 304 | // Step 1: Add NSSupportsLiveActivities to Info.plist |
305 | 305 | config = withInfoPlist(config, (config) => { |
306 | 306 | config.modResults.NSSupportsLiveActivities = true; |
307 | 307 | return config; |
308 | 308 | }); |
309 | 309 |
|
310 | | - // Step 2: Add live activity entitlement (only if the provisioning profile supports it) |
311 | | - if (enableLiveActivityEntitlement) { |
312 | | - config = withEntitlementsPlist(config, (config) => { |
313 | | - config.modResults['com.apple.developer.live-activity'] = true; |
314 | | - return config; |
315 | | - }); |
316 | | - } |
| 310 | + // Step 2: (intentionally none) Live Activities require NO code-signing entitlement — |
| 311 | + // only the NSSupportsLiveActivities Info.plist key above. Do NOT add |
| 312 | + // `com.apple.developer.live-activity`: it is not a valid Apple entitlement, so the |
| 313 | + // provisioning profile cannot include it and the archive fails with "Entitlement |
| 314 | + // com.apple.developer.live-activity not found and could not be included in profile." |
317 | 315 |
|
318 | 316 | // Step 3: Write Swift Widget Extension files and native bridge |
319 | 317 | config = withDangerousMod(config, [ |
@@ -426,60 +424,81 @@ const withCheckInLiveActivity = (config, props = {}) => { |
426 | 424 | } |
427 | 425 | } |
428 | 426 |
|
429 | | - // Widget-target creation is idempotent: skip if it was already added in a |
430 | | - // previous prebuild run. addTarget stores names with surrounding quotes in the |
431 | | - // comment key, so check both forms. (Only widget CREATION is gated here — the |
432 | | - // host-target wiring above already ran.) |
433 | | - if (project.pbxTargetByName(WIDGET_NAME) || project.pbxTargetByName(`"${WIDGET_NAME}"`)) { |
434 | | - return config; |
435 | | - } |
| 427 | + // Resolve the widget target: reuse an existing one (created in a previous |
| 428 | + // prebuild) or create it now. Only the one-time CREATION steps below are gated by |
| 429 | + // this check — the build-settings / signing patches further down run for BOTH the |
| 430 | + // new and existing cases, so an updated teamId or version is applied even when the |
| 431 | + // widget target already exists (e.g. on an incremental, non `--clean` prebuild). |
| 432 | + // addTarget stores names with surrounding quotes in the comment key, so check both |
| 433 | + // forms. |
| 434 | + const widgetAlreadyExists = !!(project.pbxTargetByName(WIDGET_NAME) || project.pbxTargetByName(`"${WIDGET_NAME}"`)); |
| 435 | + let widgetTargetUuid = null; |
| 436 | + |
| 437 | + if (widgetAlreadyExists) { |
| 438 | + // Find the existing target's uuid in the native-target section (skip the |
| 439 | + // companion "<uuid>_comment" string entries). |
| 440 | + for (const key of Object.keys(targetSection)) { |
| 441 | + if (key.endsWith('_comment')) continue; |
| 442 | + const existing = targetSection[key]; |
| 443 | + if (existing && (existing.name === WIDGET_NAME || existing.name === `"${WIDGET_NAME}"`)) { |
| 444 | + widgetTargetUuid = key; |
| 445 | + break; |
| 446 | + } |
| 447 | + } |
| 448 | + } else { |
| 449 | + // 1. Create the PBXNativeTarget. |
| 450 | + // addTarget('app_extension') also: |
| 451 | + // - adds an "Embed App Extensions" CopyFiles phase to the main target |
| 452 | + // - adds a PBXTargetDependency from main app → widget |
| 453 | + // - creates Debug/Release XCBuildConfigurations with basic defaults |
| 454 | + const widgetTarget = project.addTarget(WIDGET_NAME, 'app_extension', WIDGET_NAME, widgetBundleId); |
| 455 | + widgetTargetUuid = widgetTarget.uuid; |
| 456 | + |
| 457 | + // 2. Add the three build phases the widget target needs. |
| 458 | + // These must be added before files/frameworks are wired, because the |
| 459 | + // addSourceFile / addFramework helpers find phases by scanning the |
| 460 | + // target's buildPhases array. |
| 461 | + project.addBuildPhase([], 'PBXSourcesBuildPhase', 'Sources', widgetTarget.uuid); |
| 462 | + project.addBuildPhase([], 'PBXResourcesBuildPhase', 'Resources', widgetTarget.uuid); |
| 463 | + project.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', widgetTarget.uuid); |
| 464 | + |
| 465 | + // 3. Create a PBX group for the widget folder and attach it to the project's |
| 466 | + // main group so the files appear in the Xcode file navigator. |
| 467 | + const { uuid: widgetGroupUuid } = project.addPbxGroup([], WIDGET_NAME, WIDGET_NAME); |
| 468 | + const { firstProject } = project.getFirstProject(); |
| 469 | + const mainGroup = project.getPBXGroupByKey(firstProject.mainGroup); |
| 470 | + if (mainGroup && !mainGroup.children.find((c) => c.comment === WIDGET_NAME)) { |
| 471 | + mainGroup.children.push({ value: widgetGroupUuid, comment: WIDGET_NAME }); |
| 472 | + } |
436 | 473 |
|
437 | | - // 1. Create the PBXNativeTarget. |
438 | | - // addTarget('app_extension') also: |
439 | | - // - adds an "Embed App Extensions" CopyFiles phase to the main target |
440 | | - // - adds a PBXTargetDependency from main app → widget |
441 | | - // - creates Debug/Release XCBuildConfigurations with basic defaults |
442 | | - const widgetTarget = project.addTarget(WIDGET_NAME, 'app_extension', WIDGET_NAME, widgetBundleId); |
443 | | - |
444 | | - // 2. Add the three build phases the widget target needs. |
445 | | - // These must be added before files/frameworks are wired, because the |
446 | | - // addSourceFile / addFramework helpers find phases by scanning the |
447 | | - // target's buildPhases array. |
448 | | - project.addBuildPhase([], 'PBXSourcesBuildPhase', 'Sources', widgetTarget.uuid); |
449 | | - project.addBuildPhase([], 'PBXResourcesBuildPhase', 'Resources', widgetTarget.uuid); |
450 | | - project.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', widgetTarget.uuid); |
451 | | - |
452 | | - // 3. Create a PBX group for the widget folder and attach it to the project's |
453 | | - // main group so the files appear in the Xcode file navigator. |
454 | | - const { uuid: widgetGroupUuid } = project.addPbxGroup([], WIDGET_NAME, WIDGET_NAME); |
455 | | - const { firstProject } = project.getFirstProject(); |
456 | | - const mainGroup = project.getPBXGroupByKey(firstProject.mainGroup); |
457 | | - if (mainGroup && !mainGroup.children.find((c) => c.comment === WIDGET_NAME)) { |
458 | | - mainGroup.children.push({ value: widgetGroupUuid, comment: WIDGET_NAME }); |
459 | | - } |
| 474 | + // 4. Add Swift source files to the widget group and to the widget's Sources phase. |
| 475 | + // Passing the group key as the third argument to addSourceFile ensures the |
| 476 | + // file reference lands in the right PBX group; opt.target routes the build |
| 477 | + // file to the widget's PBXSourcesBuildPhase rather than the main app's. |
| 478 | + const SWIFT_SOURCES = [ |
| 479 | + 'CheckInTimerAttributes.swift', |
| 480 | + 'CheckInTimerLiveActivity.swift', |
| 481 | + 'CheckInTimerWidgetBundle.swift', |
| 482 | + ]; |
| 483 | + for (const filename of SWIFT_SOURCES) { |
| 484 | + project.addSourceFile( |
| 485 | + filename, |
| 486 | + { target: widgetTarget.uuid }, |
| 487 | + widgetGroupUuid |
| 488 | + ); |
| 489 | + } |
460 | 490 |
|
461 | | - // 4. Add Swift source files to the widget group and to the widget's Sources phase. |
462 | | - // Passing the group key as the third argument to addSourceFile ensures the |
463 | | - // file reference lands in the right PBX group; opt.target routes the build |
464 | | - // file to the widget's PBXSourcesBuildPhase rather than the main app's. |
465 | | - const SWIFT_SOURCES = [ |
466 | | - 'CheckInTimerAttributes.swift', |
467 | | - 'CheckInTimerLiveActivity.swift', |
468 | | - 'CheckInTimerWidgetBundle.swift', |
469 | | - ]; |
470 | | - for (const filename of SWIFT_SOURCES) { |
471 | | - project.addSourceFile( |
472 | | - filename, |
473 | | - { target: widgetTarget.uuid }, |
474 | | - widgetGroupUuid |
475 | | - ); |
| 491 | + // 5. Link WidgetKit and ActivityKit into the widget's Frameworks phase. |
| 492 | + // opt.target directs addToPbxFrameworksBuildPhase to use the widget's |
| 493 | + // PBXFrameworksBuildPhase (added above) instead of the main app's. |
| 494 | + project.addFramework('WidgetKit.framework', { target: widgetTarget.uuid }); |
| 495 | + project.addFramework('ActivityKit.framework', { target: widgetTarget.uuid }); |
476 | 496 | } |
477 | 497 |
|
478 | | - // 5. Link WidgetKit and ActivityKit into the widget's Frameworks phase. |
479 | | - // opt.target directs addToPbxFrameworksBuildPhase to use the widget's |
480 | | - // PBXFrameworksBuildPhase (added above) instead of the main app's. |
481 | | - project.addFramework('WidgetKit.framework', { target: widgetTarget.uuid }); |
482 | | - project.addFramework('ActivityKit.framework', { target: widgetTarget.uuid }); |
| 498 | + // If the widget target can't be resolved, skip the build-settings/signing patches. |
| 499 | + if (!widgetTargetUuid) { |
| 500 | + return config; |
| 501 | + } |
483 | 502 |
|
484 | 503 | // 6. Patch build settings on both Debug and Release configurations so the |
485 | 504 | // widget compiles as a Swift 5 app-extension targeting iOS 16.1+. |
@@ -533,7 +552,7 @@ const withCheckInLiveActivity = (config, props = {}) => { |
533 | 552 | hostDevelopmentTeam = process.env.EXPO_APPLE_TEAM_ID || null; |
534 | 553 | } |
535 | 554 |
|
536 | | - const buildConfigListId = targetSection[widgetTarget.uuid].buildConfigurationList; |
| 555 | + const buildConfigListId = targetSection[widgetTargetUuid].buildConfigurationList; |
537 | 556 | const buildConfigList = project.pbxXCConfigurationList()[buildConfigListId]; |
538 | 557 | if (buildConfigList) { |
539 | 558 | for (const { value: configUuid } of buildConfigList.buildConfigurations) { |
@@ -569,8 +588,8 @@ const withCheckInLiveActivity = (config, props = {}) => { |
569 | 588 | // the parent app (matches the host target's signing identity). Mirrors the |
570 | 589 | // Responder app's working Live Activity signing setup. |
571 | 590 | if (hostDevelopmentTeam) { |
572 | | - project.addTargetAttribute('DevelopmentTeam', hostDevelopmentTeam, widgetTarget); |
573 | | - project.addTargetAttribute('ProvisioningStyle', 'Automatic', widgetTarget); |
| 591 | + project.addTargetAttribute('DevelopmentTeam', hostDevelopmentTeam, { uuid: widgetTargetUuid }); |
| 592 | + project.addTargetAttribute('ProvisioningStyle', 'Automatic', { uuid: widgetTargetUuid }); |
574 | 593 | } |
575 | 594 |
|
576 | 595 | return config; |
|
0 commit comments