From 62456fb6907da8d237bc2faad5e30db45a7dd15d Mon Sep 17 00:00:00 2001 From: evanbacon Date: Mon, 12 Jan 2026 12:18:16 -0800 Subject: [PATCH 1/6] Create 009-missing-app-clip-target.pbxproj --- .../009-missing-app-clip-target.pbxproj | 891 ++++++++++++++++++ 1 file changed, 891 insertions(+) create mode 100644 src/json/__tests__/fixtures/009-missing-app-clip-target.pbxproj diff --git a/src/json/__tests__/fixtures/009-missing-app-clip-target.pbxproj b/src/json/__tests__/fixtures/009-missing-app-clip-target.pbxproj new file mode 100644 index 0000000..b589fd5 --- /dev/null +++ b/src/json/__tests__/fixtures/009-missing-app-clip-target.pbxproj @@ -0,0 +1,891 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; + 1F505F55D1AB2FA454EF23E0 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 383B558F2C5EB000423F3EB1 /* ExpoModulesProvider.swift */; }; + 3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; }; + 51E3E9FE79456EA073CF94B3 /* libPods-clip.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C4837B4BE04A66B27D9FA1DE /* libPods-clip.a */; }; + 6F4F104A191D77E81212CE40 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5719A504594503835A0A27EA /* ExpoModulesProvider.swift */; }; + 8965B4308A7ADC7760CAC08A /* libPods-testlaunchappclip.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 627F1AD377C57C8807FC93D5 /* libPods-testlaunchappclip.a */; }; + 8FC969009A8B086195D8BB4F /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 3CA40D6FEAD5B10A330D788E /* PrivacyInfo.xcprivacy */; }; + BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; }; + F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11748412D0307B40044C1D9 /* AppDelegate.swift */; }; + XX46893AD05B5C662745D7XX /* clip.app in Embed App Clips */ = {isa = PBXBuildFile; fileRef = XX16DEE6A4EC6372F32A0DXX /* clip.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + XX0A11D0DF5B266A2E8584XX /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; + proxyType = 1; + remoteGlobalIDString = XX42A75F8F031FED491C16XX; + remoteInfo = clip; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + XXF738C40EC3AA7C79D472XX /* Embed App Clips */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(CONTENTS_FOLDER_PATH)/AppClips"; + dstSubfolderSpec = 16; + files = ( + XX46893AD05B5C662745D7XX /* clip.app in Embed App Clips */, + ); + name = "Embed App Clips"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 13B07F961A680F5B00A75B9A /* testlaunchappclip.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; fileEncoding = 4; includeInIndex = 0; path = testlaunchappclip.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = testlaunchappclip/Images.xcassets; sourceTree = ""; }; + 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = text.plist.xml; name = Info.plist; path = testlaunchappclip/Info.plist; sourceTree = ""; }; + 383B558F2C5EB000423F3EB1 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-testlaunchappclip/ExpoModulesProvider.swift"; sourceTree = ""; }; + 3CA40D6FEAD5B10A330D788E /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = testlaunchappclip/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 4A91F8F17480A2ED2EB44E30 /* Pods-testlaunchappclip.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-testlaunchappclip.release.xcconfig"; path = "Target Support Files/Pods-testlaunchappclip/Pods-testlaunchappclip.release.xcconfig"; sourceTree = ""; }; + 5719A504594503835A0A27EA /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-clip/ExpoModulesProvider.swift"; sourceTree = ""; }; + 627F1AD377C57C8807FC93D5 /* libPods-testlaunchappclip.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-testlaunchappclip.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 9D66CFBCDFE067021151EB10 /* Pods-testlaunchappclip.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-testlaunchappclip.debug.xcconfig"; path = "Target Support Files/Pods-testlaunchappclip/Pods-testlaunchappclip.debug.xcconfig"; sourceTree = ""; }; + A1872CFB89057C06AC8F6B29 /* Pods-clip.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-clip.debug.xcconfig"; path = "Target Support Files/Pods-clip/Pods-clip.debug.xcconfig"; sourceTree = ""; }; + AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = testlaunchappclip/SplashScreen.storyboard; sourceTree = ""; }; + BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = ""; }; + C4837B4BE04A66B27D9FA1DE /* libPods-clip.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-clip.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; fileEncoding = 4; includeInIndex = undefined; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; + F060C6BB562C6E96DF398986 /* Pods-clip.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-clip.release.xcconfig"; path = "Target Support Files/Pods-clip/Pods-clip.release.xcconfig"; sourceTree = ""; }; + F11748412D0307B40044C1D9 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = testlaunchappclip/AppDelegate.swift; sourceTree = ""; }; + F11748442D0722820044C1D9 /* testlaunchappclip-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.c.h; name = "testlaunchappclip-Bridging-Header.h"; path = "testlaunchappclip/testlaunchappclip-Bridging-Header.h"; sourceTree = ""; }; + XX16DEE6A4EC6372F32A0DXX /* clip.app */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; fileEncoding = 4; includeInIndex = 0; path = clip.app; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + XX01298A2D70B7E6C164BFXX /* Exceptions for "clip" folder in "clip" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + "expo-target.config.js", + ); + target = XX42A75F8F031FED491C16XX /* clip */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + XXE9EB3009A447AC179CABXX /* clip */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + XX01298A2D70B7E6C164BFXX /* Exceptions for "clip" folder in "clip" target */, + ); + explicitFileTypes = { + }; + explicitFolders = ( + ); + path = clip; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 08C8870EC430FAB40B7CD53B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 51E3E9FE79456EA073CF94B3 /* libPods-clip.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8965B4308A7ADC7760CAC08A /* libPods-testlaunchappclip.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 13B07FAE1A68108700A75B9A /* testlaunchappclip */ = { + isa = PBXGroup; + children = ( + F11748412D0307B40044C1D9 /* AppDelegate.swift */, + F11748442D0722820044C1D9 /* testlaunchappclip-Bridging-Header.h */, + BB2F792B24A3F905000567C9 /* Supporting */, + 13B07FB51A68108700A75B9A /* Images.xcassets */, + 13B07FB61A68108700A75B9A /* Info.plist */, + AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */, + 3CA40D6FEAD5B10A330D788E /* PrivacyInfo.xcprivacy */, + ); + name = testlaunchappclip; + sourceTree = ""; + }; + 2D16E6871FA4F8E400B85C8A /* Frameworks */ = { + isa = PBXGroup; + children = ( + ED297162215061F000B7C4FE /* JavaScriptCore.framework */, + C4837B4BE04A66B27D9FA1DE /* libPods-clip.a */, + 627F1AD377C57C8807FC93D5 /* libPods-testlaunchappclip.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 4880524257C719F2327D9068 /* Pods */ = { + isa = PBXGroup; + children = ( + A1872CFB89057C06AC8F6B29 /* Pods-clip.debug.xcconfig */, + F060C6BB562C6E96DF398986 /* Pods-clip.release.xcconfig */, + 9D66CFBCDFE067021151EB10 /* Pods-testlaunchappclip.debug.xcconfig */, + 4A91F8F17480A2ED2EB44E30 /* Pods-testlaunchappclip.release.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 63FB1BF3D51023E07AA2BF7E /* testlaunchappclip */ = { + isa = PBXGroup; + children = ( + 383B558F2C5EB000423F3EB1 /* ExpoModulesProvider.swift */, + ); + name = testlaunchappclip; + sourceTree = ""; + }; + 832341AE1AAA6A7D00B99B32 /* Libraries */ = { + isa = PBXGroup; + children = ( + ); + name = Libraries; + sourceTree = ""; + }; + 83CBB9F61A601CBA00E9B192 = { + isa = PBXGroup; + children = ( + XX3E41C1850246162C0061XX /* expo:targets */, + 13B07FAE1A68108700A75B9A /* testlaunchappclip */, + 832341AE1AAA6A7D00B99B32 /* Libraries */, + 83CBBA001A601CBA00E9B192 /* Products */, + 2D16E6871FA4F8E400B85C8A /* Frameworks */, + 4880524257C719F2327D9068 /* Pods */, + CE661DC1F9D072A704954D6A /* ExpoModulesProviders */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 83CBBA001A601CBA00E9B192 /* Products */ = { + isa = PBXGroup; + children = ( + 13B07F961A680F5B00A75B9A /* testlaunchappclip.app */, + XX16DEE6A4EC6372F32A0DXX /* clip.app */, + ); + name = Products; + sourceTree = ""; + }; + BB2F792B24A3F905000567C9 /* Supporting */ = { + isa = PBXGroup; + children = ( + BB2F792C24A3F905000567C9 /* Expo.plist */, + ); + name = Supporting; + path = testlaunchappclip/Supporting; + sourceTree = ""; + }; + CE661DC1F9D072A704954D6A /* ExpoModulesProviders */ = { + isa = PBXGroup; + children = ( + E93EF5529E2267F4DDB58B8C /* clip */, + 63FB1BF3D51023E07AA2BF7E /* testlaunchappclip */, + ); + name = ExpoModulesProviders; + sourceTree = ""; + }; + E93EF5529E2267F4DDB58B8C /* clip */ = { + isa = PBXGroup; + children = ( + 5719A504594503835A0A27EA /* ExpoModulesProvider.swift */, + ); + name = clip; + sourceTree = ""; + }; + XX3E41C1850246162C0061XX /* expo:targets */ = { + isa = PBXGroup; + children = ( + XXE9EB3009A447AC179CABXX /* clip */, + ); + name = "expo:targets"; + path = ../targets; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 13B07F861A680F5B00A75B9A /* testlaunchappclip */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "testlaunchappclip" */; + buildPhases = ( + 08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */, + 122F78F0FB9C5D3A7A5420E7 /* [Expo] Configure project */, + 13B07F871A680F5B00A75B9A /* Sources */, + 13B07F8C1A680F5B00A75B9A /* Frameworks */, + 13B07F8E1A680F5B00A75B9A /* Resources */, + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, + 800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */, + XXF738C40EC3AA7C79D472XX /* Embed App Clips */, + 10F18625A3A740374D8B5C76 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + XX595E81A3653FA9053882XX /* PBXTargetDependency */, + ); + name = testlaunchappclip; + productName = testlaunchappclip; + productReference = 13B07F961A680F5B00A75B9A /* testlaunchappclip.app */; + productType = "com.apple.product-type.application"; + }; + XX42A75F8F031FED491C16XX /* clip */ = { + isa = PBXNativeTarget; + buildConfigurationList = XX32BFEE75D9DD55518519XX /* Build configuration list for PBXNativeTarget "clip" */; + buildPhases = ( + 54286F440CC5987372C24687 /* [CP] Check Pods Manifest.lock */, + 595000FDDB9A362174045455 /* [Expo] Configure project */, + XXE45443DC38DCFE1DB622XX /* Sources */, + XXFA5A8CCDD805DF7AEF9CXX /* Resources */, + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, + 08C8870EC430FAB40B7CD53B /* Frameworks */, + ECEFC8E257A935CEC75DE4BE /* [CP] Embed Pods Frameworks */, + 6C0AD37A73ED663A1983A596 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + XXE9EB3009A447AC179CABXX /* clip */, + ); + name = clip; + productName = clip; + productReference = XX16DEE6A4EC6372F32A0DXX /* clip.app */; + productType = "com.apple.product-type.application.on-demand-install-capable"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 83CBB9F71A601CBA00E9B192 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1130; + TargetAttributes = { + 13B07F861A680F5B00A75B9A = { + LastSwiftMigration = 1250; + }; + XX42A75F8F031FED491C16XX = { + CreatedOnToolsVersion = 14.3; + DevelopmentTeam = undefined; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "testlaunchappclip" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 83CBB9F61A601CBA00E9B192; + productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 13B07F861A680F5B00A75B9A /* testlaunchappclip */, + XX42A75F8F031FED491C16XX /* clip */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 13B07F8E1A680F5B00A75B9A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BB2F792D24A3F905000567C9 /* Expo.plist in Resources */, + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, + 3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */, + 8FC969009A8B086195D8BB4F /* PrivacyInfo.xcprivacy in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + XXFA5A8CCDD805DF7AEF9CXX /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "$(SRCROOT)/.xcode.env", + "$(SRCROOT)/.xcode.env.local", + ); + name = "Bundle React Native code and images"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios absolute | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n# Source .xcode.env.updates if it exists to allow\n# SKIP_BUNDLING to be unset if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.updates\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.updates\"\nfi\n# Source local changes to allow overrides\n# if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n\n"; + }; + 08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-testlaunchappclip-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 10F18625A3A740374D8B5C76 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-testlaunchappclip/Pods-testlaunchappclip-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/React-Core-prebuilt/React.framework/React", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/ReactNativeDependencies/ReactNativeDependencies.framework/ReactNativeDependencies", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/React.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReactNativeDependencies.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-testlaunchappclip/Pods-testlaunchappclip-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 122F78F0FB9C5D3A7A5420E7 /* [Expo] Configure project */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "$(SRCROOT)/.xcode.env", + "$(SRCROOT)/.xcode.env.local", + "$(SRCROOT)/testlaunchappclip/testlaunchappclip.entitlements", + "$(SRCROOT)/Pods/Target Support Files/Pods-testlaunchappclip/expo-configure-project.sh", + ); + name = "[Expo] Configure project"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(SRCROOT)/Pods/Target Support Files/Pods-testlaunchappclip/ExpoModulesProvider.swift", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-testlaunchappclip/expo-configure-project.sh\"\n"; + }; + 54286F440CC5987372C24687 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-clip-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 595000FDDB9A362174045455 /* [Expo] Configure project */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "$(SRCROOT)/.xcode.env", + "$(SRCROOT)/.xcode.env.local", + "$(SRCROOT)/../targets/clip/generated.entitlements", + "$(SRCROOT)/Pods/Target Support Files/Pods-clip/expo-configure-project.sh", + ); + name = "[Expo] Configure project"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(SRCROOT)/Pods/Target Support Files/Pods-clip/ExpoModulesProvider.swift", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-clip/expo-configure-project.sh\"\n"; + }; + 6C0AD37A73ED663A1983A596 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-clip/Pods-clip-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/ExpoConstants_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/ExpoSystemUI/ExpoSystemUI_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/React-Core_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact/React-cxxreact_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoConstants_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoFileSystem_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoSystemUI_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-Core_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-cxxreact_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-clip/Pods-clip-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-testlaunchappclip/Pods-testlaunchappclip-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/ExpoConstants_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/ExpoSystemUI/ExpoSystemUI_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/React-Core_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact/React-cxxreact_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoConstants_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoFileSystem_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoSystemUI_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-Core_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-cxxreact_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-testlaunchappclip/Pods-testlaunchappclip-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + ECEFC8E257A935CEC75DE4BE /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-clip/Pods-clip-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/React-Core-prebuilt/React.framework/React", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/ReactNativeDependencies/ReactNativeDependencies.framework/ReactNativeDependencies", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/React.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReactNativeDependencies.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-clip/Pods-clip-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 13B07F871A680F5B00A75B9A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */, + 1F505F55D1AB2FA454EF23E0 /* ExpoModulesProvider.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + XXE45443DC38DCFE1DB622XX /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6F4F104A191D77E81212CE40 /* ExpoModulesProvider.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + XX595E81A3653FA9053882XX /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = XX42A75F8F031FED491C16XX /* clip */; + targetProxy = XX0A11D0DF5B266A2E8584XX /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 13B07F941A680F5B00A75B9A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9D66CFBCDFE067021151EB10 /* Pods-testlaunchappclip.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = testlaunchappclip/testlaunchappclip.entitlements; + CURRENT_PROJECT_VERSION = 1; + ENABLE_BITCODE = NO; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "FB_SONARKIT_ENABLED=1", + ); + INFOPLIST_FILE = testlaunchappclip/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.0; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; + PRODUCT_BUNDLE_IDENTIFIER = "com.bycedric.test-launch-appclip"; + PRODUCT_NAME = testlaunchappclip; + SWIFT_OBJC_BRIDGING_HEADER = "testlaunchappclip/testlaunchappclip-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 13B07F951A680F5B00A75B9A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4A91F8F17480A2ED2EB44E30 /* Pods-testlaunchappclip.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = testlaunchappclip/testlaunchappclip.entitlements; + CURRENT_PROJECT_VERSION = 1; + INFOPLIST_FILE = testlaunchappclip/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.0; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; + PRODUCT_BUNDLE_IDENTIFIER = "com.bycedric.test-launch-appclip"; + PRODUCT_NAME = testlaunchappclip; + SWIFT_OBJC_BRIDGING_HEADER = "testlaunchappclip/testlaunchappclip-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; + 83CBBA201A601CBA00E9B192 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "c++20"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD_RUNPATH_SEARCH_PATHS = ( + /usr/lib/swift, + "$(inherited)", + ); + LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; + SWIFT_ENABLE_EXPLICIT_MODULES = NO; + USE_HERMES = true; + }; + name = Debug; + }; + 83CBBA211A601CBA00E9B192 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "c++20"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD_RUNPATH_SEARCH_PATHS = ( + /usr/lib/swift, + "$(inherited)", + ); + LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; + MTL_ENABLE_DEBUG_INFO = NO; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + SDKROOT = iphoneos; + SWIFT_ENABLE_EXPLICIT_MODULES = NO; + USE_HERMES = true; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + XX05EDFCA1BE5DD0B51096XX /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F060C6BB562C6E96DF398986 /* Pods-clip.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = ../targets/clip/generated.entitlements; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ../targets/clip/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = clip; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.0; + MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; + PRODUCT_BUNDLE_IDENTIFIER = "com.bycedric.test-launch-appclip.clip"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + XX4C1310F2FF225EDE850BXX /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A1872CFB89057C06AC8F6B29 /* Pods-clip.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = ../targets/clip/generated.entitlements; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ../targets/clip/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = clip; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; + PRODUCT_BUNDLE_IDENTIFIER = "com.bycedric.test-launch-appclip.clip"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "testlaunchappclip" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13B07F941A680F5B00A75B9A /* Debug */, + 13B07F951A680F5B00A75B9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "testlaunchappclip" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 83CBBA201A601CBA00E9B192 /* Debug */, + 83CBBA211A601CBA00E9B192 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + XX32BFEE75D9DD55518519XX /* Build configuration list for PBXNativeTarget "clip" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + XX4C1310F2FF225EDE850BXX /* Debug */, + XX05EDFCA1BE5DD0B51096XX /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; +} From e5c8edebf7ab97b875bd9906986ca27079a46c24 Mon Sep 17 00:00:00 2001 From: evanbacon Date: Mon, 12 Jan 2026 12:20:59 -0800 Subject: [PATCH 2/6] Update json.test.ts --- src/json/__tests__/json.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/json/__tests__/json.test.ts b/src/json/__tests__/json.test.ts index 8b6f434..dc2e814 100644 --- a/src/json/__tests__/json.test.ts +++ b/src/json/__tests__/json.test.ts @@ -32,6 +32,7 @@ describe(parse, () => { "006-spm.pbxproj", "007-xcode16.pbxproj", "008-out-of-order-orphans.pbxproj", + "009-missing-app-clip-target.pbxproj", "shopify-tophat.pbxproj", "AFNetworking.pbxproj", "project.pbxproj", From e787dc9264425878e73cfb77f9f9a37b8a101e2e Mon Sep 17 00:00:00 2001 From: evanbacon Date: Mon, 12 Jan 2026 12:42:03 -0800 Subject: [PATCH 3/6] Implement robust target and reference removal logic Adds removeReference and removeFromProject methods to key project model classes, ensuring that targets and their exclusively-owned children (such as build phases, dependencies, configuration lists, and file references) are properly removed from the project. Updates PBXNativeTarget, PBXProject, PBXFileSystemSynchronizedRootGroup, PBXTargetDependency, and XCConfigurationList to support safe cascading deletion and reference cleanup. Extends tests to verify correct removal behavior and preservation of shared resources. --- src/api/AbstractTarget.ts | 18 ++ src/api/PBXFileSystemSynchronizedRootGroup.ts | 26 ++ src/api/PBXNativeTarget.ts | 133 ++++++++- src/api/PBXProject.ts | 14 + src/api/PBXTargetDependency.ts | 17 ++ src/api/XCConfigurationList.ts | 15 + src/api/__tests__/PBXNativeTarget.test.ts | 273 +++++++++++++++++- 7 files changed, 493 insertions(+), 3 deletions(-) diff --git a/src/api/AbstractTarget.ts b/src/api/AbstractTarget.ts index 61c29b5..fea4d85 100644 --- a/src/api/AbstractTarget.ts +++ b/src/api/AbstractTarget.ts @@ -128,6 +128,24 @@ export class AbstractTarget< return false; } + removeReference(uuid: string) { + // Remove from dependencies array + const depIndex = this.props.dependencies.findIndex( + (dep) => dep.uuid === uuid + ); + if (depIndex !== -1) { + this.props.dependencies.splice(depIndex, 1); + } + + // Remove from buildPhases array + const phaseIndex = this.props.buildPhases.findIndex( + (phase) => phase.uuid === uuid + ); + if (phaseIndex !== -1) { + this.props.buildPhases.splice(phaseIndex, 1); + } + } + protected getObjectProps(): any { return { buildConfigurationList: String, diff --git a/src/api/PBXFileSystemSynchronizedRootGroup.ts b/src/api/PBXFileSystemSynchronizedRootGroup.ts index 77ebc8e..b407c90 100644 --- a/src/api/PBXFileSystemSynchronizedRootGroup.ts +++ b/src/api/PBXFileSystemSynchronizedRootGroup.ts @@ -35,4 +35,30 @@ export class PBXFileSystemSynchronizedRootGroup extends AbstractObject exc.uuid === uuid); + } + + removeReference(uuid: string) { + if (this.props.exceptions) { + const index = this.props.exceptions.findIndex((exc) => exc.uuid === uuid); + if (index !== -1) { + this.props.exceptions.splice(index, 1); + } + } + } + + removeFromProject() { + // Remove all exceptions that are only referenced by this group + if (this.props.exceptions) { + for (const exception of [...this.props.exceptions]) { + const referrers = exception.getReferrers(); + if (referrers.length === 1 && referrers[0].uuid === this.uuid) { + exception.removeFromProject(); + } + } + } + return super.removeFromProject(); + } } diff --git a/src/api/PBXNativeTarget.ts b/src/api/PBXNativeTarget.ts index fbadc46..0302296 100644 --- a/src/api/PBXNativeTarget.ts +++ b/src/api/PBXNativeTarget.ts @@ -18,7 +18,6 @@ import type { XCConfigurationList } from "./XCConfigurationList"; import type { XCSwiftPackageProductDependency } from "./XCSwiftPackageProductDependency"; import type { PBXFileSystemSynchronizedRootGroup } from "./PBXFileSystemSynchronizedRootGroup"; import { PBXContainerItemProxy } from "./PBXContainerItemProxy"; -import type { PBXFileSystemSynchronizedBuildFileExceptionSet } from "./PBXFileSystemSynchronizedBuildFileExceptionSet"; export type PBXNativeTargetModel = json.PBXNativeTarget< XCConfigurationList, @@ -68,6 +67,43 @@ export class PBXNativeTarget extends AbstractTarget { return super.isReferencing(uuid); } + removeReference(uuid: string) { + // Remove from buildRules array + const ruleIndex = this.props.buildRules.findIndex( + (rule) => rule.uuid === uuid + ); + if (ruleIndex !== -1) { + this.props.buildRules.splice(ruleIndex, 1); + } + + // Remove from packageProductDependencies array + if (this.props.packageProductDependencies) { + const pkgIndex = this.props.packageProductDependencies.findIndex( + (dep) => dep.uuid === uuid + ); + if (pkgIndex !== -1) { + this.props.packageProductDependencies.splice(pkgIndex, 1); + } + } + + // Remove from fileSystemSynchronizedGroups array + if (this.props.fileSystemSynchronizedGroups) { + const fsIndex = this.props.fileSystemSynchronizedGroups.findIndex( + (group) => group.uuid === uuid + ); + if (fsIndex !== -1) { + this.props.fileSystemSynchronizedGroups.splice(fsIndex, 1); + } + } + + // Clear productReference if it matches + if (this.props.productReference?.uuid === uuid) { + this.props.productReference = undefined; + } + + super.removeReference(uuid); + } + /** @returns the `PBXFrameworksBuildPhase` or creates one if there is none. Only one can exist. */ getFrameworksBuildPhase() { return ( @@ -267,4 +303,99 @@ export class PBXNativeTarget extends AbstractTarget { fileSystemSynchronizedGroups: [String], }; } + + /** + * Removes this target from the project along with all of its exclusively-owned children. + * Children that are shared with other targets (e.g., shared build phases) are preserved. + */ + removeFromProject() { + const project = this.getXcodeProject(); + + // Helper to check if an object is only referenced by this target + const isExclusivelyOwnedByThisTarget = (obj: { getReferrers(): { uuid: string }[] }) => { + const referrers = obj.getReferrers(); + return referrers.length === 1 && referrers[0].uuid === this.uuid; + }; + + // Remove build phases that are only referenced by this target + for (const phase of [...this.props.buildPhases]) { + if (isExclusivelyOwnedByThisTarget(phase)) { + phase.removeFromProject(); + } + } + + // Remove build rules that are only referenced by this target + for (const rule of [...this.props.buildRules]) { + if (isExclusivelyOwnedByThisTarget(rule)) { + rule.removeFromProject(); + } + } + + // Remove the build configuration list (it will cascade to configurations) + if (isExclusivelyOwnedByThisTarget(this.props.buildConfigurationList)) { + this.props.buildConfigurationList.removeFromProject(); + } + + // Remove dependencies (PBXTargetDependency objects that this target depends on) + for (const dep of [...this.props.dependencies]) { + if (isExclusivelyOwnedByThisTarget(dep)) { + dep.removeFromProject(); + } + } + + // Remove file system synchronized groups + // Check if any OTHER target uses this group (not just any referrer, since groups can be in PBXGroups too) + if (this.props.fileSystemSynchronizedGroups) { + for (const group of [...this.props.fileSystemSynchronizedGroups]) { + const groupUsedByOtherTarget = [...project.values()].some( + (obj) => + PBXNativeTarget.is(obj) && + obj.uuid !== this.uuid && + obj.props.fileSystemSynchronizedGroups?.some( + (g) => g.uuid === group.uuid + ) + ); + if (!groupUsedByOtherTarget) { + group.removeFromProject(); + } + } + } + + // Remove package product dependencies + if (this.props.packageProductDependencies) { + for (const dep of [...this.props.packageProductDependencies]) { + if (isExclusivelyOwnedByThisTarget(dep)) { + dep.removeFromProject(); + } + } + } + + // Remove the product reference (the .app, .framework, etc. file reference) + // Check if any OTHER target uses this as their productReference + if (this.props.productReference) { + const productRefUsedByOtherTarget = [...project.values()].some( + (obj) => + PBXNativeTarget.is(obj) && + obj.uuid !== this.uuid && + obj.props.productReference?.uuid === this.props.productReference?.uuid + ); + if (!productRefUsedByOtherTarget) { + this.props.productReference.removeFromProject(); + } + } + + // Find and remove any PBXTargetDependency objects from OTHER targets that depend on THIS target + for (const [, obj] of project.entries()) { + if ( + PBXTargetDependency.is(obj) && + (obj.props.target?.uuid === this.uuid || + obj.props.targetProxy?.props.remoteGlobalIDString === this.uuid) + ) { + obj.removeFromProject(); + } + } + + // Call parent which handles removing from PBXProject.targets array + return super.removeFromProject(); + } } diff --git a/src/api/PBXProject.ts b/src/api/PBXProject.ts index 2bb30db..7ebf0d6 100644 --- a/src/api/PBXProject.ts +++ b/src/api/PBXProject.ts @@ -238,4 +238,18 @@ export class PBXProject extends AbstractObject { } return !!this.props.targets.find((target) => target.uuid === uuid); } + + removeReference(uuid: string) { + const index = this.props.targets.findIndex( + (target) => target.uuid === uuid + ); + if (index !== -1) { + this.props.targets.splice(index, 1); + } + + // Also remove from TargetAttributes if present + if (this.props.attributes?.TargetAttributes?.[uuid]) { + delete this.props.attributes.TargetAttributes[uuid]; + } + } } diff --git a/src/api/PBXTargetDependency.ts b/src/api/PBXTargetDependency.ts index 28a4bbf..2a970b1 100644 --- a/src/api/PBXTargetDependency.ts +++ b/src/api/PBXTargetDependency.ts @@ -41,6 +41,12 @@ export class PBXTargetDependency extends AbstractObject { describe("basic functionality", () => { it("gets referrers", () => { @@ -31,8 +37,9 @@ describe("PBXNativeTarget", () => { "299522761BBF136400859F49" ) as PBXNativeTarget; - expect(obj.getReferrers().map((o) => o.uuid)).toEqual([ - "299522301BBF104D00859F49", + expect(obj.getReferrers().map((o) => o.uuid).sort()).toEqual([ + "298D7C511BC2C7B200FD3B3E", // PBXTargetDependency that references this target + "299522301BBF104D00859F49", // PBXProject that contains this target ]); }); @@ -585,4 +592,266 @@ describe("PBXNativeTarget", () => { }); }); }); + + describe("removeFromProject", () => { + it("should remove app clip target and its exclusive children from project", () => { + const xcproj = XcodeProject.open(APP_CLIP_FIXTURE); + const project = xcproj.rootObject; + + // Get the app clip target + const appClipTarget = xcproj.getObject( + "XX42A75F8F031FED491C16XX" + ) as PBXNativeTarget; + expect(appClipTarget).toBeDefined(); + expect(appClipTarget.props.productType).toBe( + "com.apple.product-type.application.on-demand-install-capable" + ); + + // Record UUIDs of objects that should be removed + const appClipBuildConfigList = appClipTarget.props.buildConfigurationList; + const appClipBuildConfigs = [...appClipBuildConfigList.props.buildConfigurations]; + const appClipProductRef = appClipTarget.props.productReference; + const appClipFileSystemGroups = appClipTarget.props.fileSystemSynchronizedGroups || []; + + // Get the main target to verify it's not affected + const mainTarget = project.props.targets.find( + (t) => PBXNativeTarget.is(t) && t.uuid !== appClipTarget.uuid + ) as PBXNativeTarget; + expect(mainTarget).toBeDefined(); + const mainTargetUuid = mainTarget.uuid; + + // Count initial objects + const initialTargetCount = project.props.targets.length; + const initialObjectCount = xcproj.size; + + // Remove the app clip target + appClipTarget.removeFromProject(); + + // Verify target was removed from project + expect(project.props.targets.length).toBe(initialTargetCount - 1); + expect( + project.props.targets.find((t) => t.uuid === "XX42A75F8F031FED491C16XX") + ).toBeUndefined(); + + // Verify target is no longer in the project map + expect(xcproj.has("XX42A75F8F031FED491C16XX")).toBe(false); + + // Verify build configuration list was removed + expect(xcproj.has(appClipBuildConfigList.uuid)).toBe(false); + + // Verify build configurations were removed + for (const config of appClipBuildConfigs) { + expect(xcproj.has(config.uuid)).toBe(false); + } + + // Verify product reference was removed + if (appClipProductRef) { + expect(xcproj.has(appClipProductRef.uuid)).toBe(false); + } + + // Verify file system synchronized groups were removed + for (const group of appClipFileSystemGroups) { + expect(xcproj.has(group.uuid)).toBe(false); + } + + // Verify objects were actually removed (not just from arrays) + expect(xcproj.size).toBeLessThan(initialObjectCount); + + // Verify main target still exists and is functional + expect(xcproj.has(mainTargetUuid)).toBe(true); + const stillExistingMainTarget = xcproj.getObject(mainTargetUuid) as PBXNativeTarget; + expect(stillExistingMainTarget.props.name).toBe("testlaunchappclip"); + }); + + it("should remove target dependency from main target when app clip is removed", () => { + const xcproj = XcodeProject.open(APP_CLIP_FIXTURE); + const project = xcproj.rootObject; + + // Get the main target + const mainTarget = project.props.targets.find( + (t) => + PBXNativeTarget.is(t) && + t.props.productType === "com.apple.product-type.application" + ) as PBXNativeTarget; + expect(mainTarget).toBeDefined(); + + // Verify main target has a dependency on the clip target + const initialDependencyCount = mainTarget.props.dependencies.length; + expect(initialDependencyCount).toBeGreaterThan(0); + + // Find the dependency on the clip target + const clipDependency = mainTarget.props.dependencies.find( + (dep) => + dep.props.target?.uuid === "XX42A75F8F031FED491C16XX" || + dep.props.targetProxy?.props.remoteGlobalIDString === "XX42A75F8F031FED491C16XX" + ); + expect(clipDependency).toBeDefined(); + + // Get the app clip target and remove it + const appClipTarget = xcproj.getObject( + "XX42A75F8F031FED491C16XX" + ) as PBXNativeTarget; + appClipTarget.removeFromProject(); + + // Verify the dependency was removed from main target + expect(mainTarget.props.dependencies.length).toBe( + initialDependencyCount - 1 + ); + + // Verify no dependency references the removed target + const remainingClipDependency = mainTarget.props.dependencies.find( + (dep) => + dep.props.target?.uuid === "XX42A75F8F031FED491C16XX" || + dep.props.targetProxy?.props.remoteGlobalIDString === "XX42A75F8F031FED491C16XX" + ); + expect(remainingClipDependency).toBeUndefined(); + }); + + it("should preserve shared build phases when removing target", () => { + const xcproj = XcodeProject.open(APP_CLIP_FIXTURE); + + // Get both targets + const appClipTarget = xcproj.getObject( + "XX42A75F8F031FED491C16XX" + ) as PBXNativeTarget; + const mainTarget = xcproj.rootObject.props.targets.find( + (t) => PBXNativeTarget.is(t) && t.uuid !== appClipTarget.uuid + ) as PBXNativeTarget; + + // Find a shared build phase (Bundle React Native code and images) + const sharedBuildPhaseUuid = "00DD1BFF1BD5951E006B06BC"; + const sharedPhaseInAppClip = appClipTarget.props.buildPhases.find( + (p) => p.uuid === sharedBuildPhaseUuid + ); + const sharedPhaseInMain = mainTarget.props.buildPhases.find( + (p) => p.uuid === sharedBuildPhaseUuid + ); + + expect(sharedPhaseInAppClip).toBeDefined(); + expect(sharedPhaseInMain).toBeDefined(); + + // Remove the app clip target + appClipTarget.removeFromProject(); + + // Verify shared build phase still exists + expect(xcproj.has(sharedBuildPhaseUuid)).toBe(true); + + // Verify main target still has the shared build phase + expect( + mainTarget.props.buildPhases.find((p) => p.uuid === sharedBuildPhaseUuid) + ).toBeDefined(); + }); + + it("should remove TargetAttributes when target is removed", () => { + const xcproj = XcodeProject.open(APP_CLIP_FIXTURE); + const project = xcproj.rootObject; + + // Verify TargetAttributes exists for the app clip target + expect( + project.props.attributes?.TargetAttributes?.["XX42A75F8F031FED491C16XX"] + ).toBeDefined(); + + // Get and remove the app clip target + const appClipTarget = xcproj.getObject( + "XX42A75F8F031FED491C16XX" + ) as PBXNativeTarget; + appClipTarget.removeFromProject(); + + // Verify TargetAttributes was removed + expect( + project.props.attributes?.TargetAttributes?.["XX42A75F8F031FED491C16XX"] + ).toBeUndefined(); + }); + + it("should remove PBXFileSystemSynchronizedBuildFileExceptionSet when target is removed", () => { + const xcproj = XcodeProject.open(APP_CLIP_FIXTURE); + + // Verify the exception set exists + const exceptionSetUuid = "XX01298A2D70B7E6C164BFXX"; + expect(xcproj.has(exceptionSetUuid)).toBe(true); + + // Get and remove the app clip target + const appClipTarget = xcproj.getObject( + "XX42A75F8F031FED491C16XX" + ) as PBXNativeTarget; + appClipTarget.removeFromProject(); + + // Verify the exception set was removed (it was only referenced by the clip's file system group) + expect(xcproj.has(exceptionSetUuid)).toBe(false); + }); + + it("should remove container item proxy when target dependency is removed", () => { + const xcproj = XcodeProject.open(APP_CLIP_FIXTURE); + + // Verify the container item proxy exists + const containerProxyUuid = "XX0A11D0DF5B266A2E8584XX"; + expect(xcproj.has(containerProxyUuid)).toBe(true); + + // Get and remove the app clip target + const appClipTarget = xcproj.getObject( + "XX42A75F8F031FED491C16XX" + ) as PBXNativeTarget; + appClipTarget.removeFromProject(); + + // Verify the container item proxy was removed + expect(xcproj.has(containerProxyUuid)).toBe(false); + }); + + it("should handle removing a target with no dependencies", () => { + const xcproj = XcodeProject.open(MULTITARGET_FIXTURE); + const project = xcproj.rootObject; + + // Create a new standalone target + const mainTarget = project.getMainAppTarget()!; + const newTarget = project.createNativeTarget({ + name: "StandaloneTarget", + productType: "com.apple.product-type.framework", + buildConfigurationList: mainTarget.props.buildConfigurationList, + }); + + const newTargetUuid = newTarget.uuid; + expect(xcproj.has(newTargetUuid)).toBe(true); + + // Remove the standalone target + newTarget.removeFromProject(); + + // Verify target was removed + expect(xcproj.has(newTargetUuid)).toBe(false); + expect( + project.props.targets.find((t) => t.uuid === newTargetUuid) + ).toBeUndefined(); + }); + + it("should not remove build configuration list if shared", () => { + const xcproj = XcodeProject.open(MULTITARGET_FIXTURE); + const project = xcproj.rootObject; + + // Create two targets sharing the same build configuration list + const mainTarget = project.getMainAppTarget()!; + const sharedConfigList = mainTarget.props.buildConfigurationList; + + const target1 = project.createNativeTarget({ + name: "Target1", + productType: "com.apple.product-type.framework", + buildConfigurationList: sharedConfigList, + }); + + const target2 = project.createNativeTarget({ + name: "Target2", + productType: "com.apple.product-type.framework", + buildConfigurationList: sharedConfigList, + }); + + // Remove target1 + target1.removeFromProject(); + + // Verify shared config list still exists (used by mainTarget and target2) + expect(xcproj.has(sharedConfigList.uuid)).toBe(true); + + // Verify target2 still has the config list + expect(target2.props.buildConfigurationList.uuid).toBe( + sharedConfigList.uuid + ); + }); + }); }); From 9063b523c907a426912ef00042780e6378afe683 Mon Sep 17 00:00:00 2001 From: evanbacon Date: Mon, 12 Jan 2026 12:46:37 -0800 Subject: [PATCH 4/6] Refactor reference handling to AbstractObject base class Moved isReferencing and removeReference implementations from subclasses to AbstractObject, providing a generic mechanism for reference management. This reduces code duplication and centralizes reference logic for easier maintenance. --- src/api/AbstractGroup.ts | 11 ---- src/api/AbstractObject.ts | 27 +++++++++- src/api/AbstractTarget.ts | 27 ---------- src/api/PBXFileSystemSynchronizedRootGroup.ts | 13 ----- src/api/PBXNativeTarget.ts | 52 ------------------- src/api/PBXProject.ts | 20 +------ src/api/PBXTargetDependency.ts | 6 --- src/api/XCConfigurationList.ts | 13 ----- 8 files changed, 26 insertions(+), 143 deletions(-) diff --git a/src/api/AbstractGroup.ts b/src/api/AbstractGroup.ts index 1bcc0bc..28b3763 100644 --- a/src/api/AbstractGroup.ts +++ b/src/api/AbstractGroup.ts @@ -148,17 +148,6 @@ export class AbstractGroup< return getParents(this); } - isReferencing(uuid: string): boolean { - return !!this.props.children.find((child) => child.uuid === uuid); - } - - removeReference(uuid: string) { - const index = this.props.children.findIndex((child) => child.uuid === uuid); - if (index !== -1) { - this.props.children.splice(index, 1); - } - } - getPath(): string { throw new Error("TODO"); } diff --git a/src/api/AbstractObject.ts b/src/api/AbstractObject.ts index 7482b1a..9dfb5ca 100644 --- a/src/api/AbstractObject.ts +++ b/src/api/AbstractObject.ts @@ -158,6 +158,14 @@ export abstract class AbstractObject< /** @returns `true` if the provided UUID is used somewhere in the props. */ isReferencing(uuid: string): boolean { + for (const key of Object.keys(this.getObjectProps()) as (keyof TJSON)[]) { + const value = this.props[key]; + if (Array.isArray(value)) { + if (value.some((item: any) => item?.uuid === uuid)) return true; + } else if (value && typeof value === "object" && "uuid" in value) { + if ((value as any).uuid === uuid) return true; + } + } return false; } @@ -200,8 +208,23 @@ export abstract class AbstractObject< return json as TJSON; } - /** abstract method for removing a UUID from any props that might be referencing it. */ - removeReference(uuid: string) {} + /** Removes a UUID from any props that reference it. */ + removeReference(uuid: string) { + for (const key of Object.keys(this.getObjectProps()) as (keyof TJSON)[]) { + const value = this.props[key]; + if (Array.isArray(value)) { + const index = value.findIndex((item: any) => item?.uuid === uuid); + if (index !== -1) { + value.splice(index, 1); + } + } else if (value && typeof value === "object" && "uuid" in value) { + if ((value as any).uuid === uuid) { + // @ts-expect-error + this.props[key] = undefined; + } + } + } + } removeFromProject() { this.getXcodeProject().delete(this.uuid); diff --git a/src/api/AbstractTarget.ts b/src/api/AbstractTarget.ts index fea4d85..db15a49 100644 --- a/src/api/AbstractTarget.ts +++ b/src/api/AbstractTarget.ts @@ -119,33 +119,6 @@ export class AbstractTarget< return v as InstanceType | null; } - isReferencing(uuid: string): boolean { - if (this.props.buildConfigurationList.uuid === uuid) return true; - if (this.props.dependencies.some((dep) => dep.uuid === uuid)) return true; - if (this.props.buildPhases.some((phase) => phase.uuid === uuid)) - return true; - - return false; - } - - removeReference(uuid: string) { - // Remove from dependencies array - const depIndex = this.props.dependencies.findIndex( - (dep) => dep.uuid === uuid - ); - if (depIndex !== -1) { - this.props.dependencies.splice(depIndex, 1); - } - - // Remove from buildPhases array - const phaseIndex = this.props.buildPhases.findIndex( - (phase) => phase.uuid === uuid - ); - if (phaseIndex !== -1) { - this.props.buildPhases.splice(phaseIndex, 1); - } - } - protected getObjectProps(): any { return { buildConfigurationList: String, diff --git a/src/api/PBXFileSystemSynchronizedRootGroup.ts b/src/api/PBXFileSystemSynchronizedRootGroup.ts index b407c90..ff89704 100644 --- a/src/api/PBXFileSystemSynchronizedRootGroup.ts +++ b/src/api/PBXFileSystemSynchronizedRootGroup.ts @@ -36,19 +36,6 @@ export class PBXFileSystemSynchronizedRootGroup extends AbstractObject exc.uuid === uuid); - } - - removeReference(uuid: string) { - if (this.props.exceptions) { - const index = this.props.exceptions.findIndex((exc) => exc.uuid === uuid); - if (index !== -1) { - this.props.exceptions.splice(index, 1); - } - } - } - removeFromProject() { // Remove all exceptions that are only referenced by this group if (this.props.exceptions) { diff --git a/src/api/PBXNativeTarget.ts b/src/api/PBXNativeTarget.ts index 0302296..c94200b 100644 --- a/src/api/PBXNativeTarget.ts +++ b/src/api/PBXNativeTarget.ts @@ -52,58 +52,6 @@ export class PBXNativeTarget extends AbstractTarget { }) as PBXNativeTarget; } - isReferencing(uuid: string): boolean { - if (this.props.buildRules.some((rule) => rule.uuid === uuid)) return true; - if (this.props.packageProductDependencies?.some((dep) => dep.uuid === uuid)) - return true; - if (this.props.productReference?.uuid === uuid) return true; - if ( - this.props.fileSystemSynchronizedGroups?.some( - (group) => group.uuid === uuid - ) - ) - return true; - - return super.isReferencing(uuid); - } - - removeReference(uuid: string) { - // Remove from buildRules array - const ruleIndex = this.props.buildRules.findIndex( - (rule) => rule.uuid === uuid - ); - if (ruleIndex !== -1) { - this.props.buildRules.splice(ruleIndex, 1); - } - - // Remove from packageProductDependencies array - if (this.props.packageProductDependencies) { - const pkgIndex = this.props.packageProductDependencies.findIndex( - (dep) => dep.uuid === uuid - ); - if (pkgIndex !== -1) { - this.props.packageProductDependencies.splice(pkgIndex, 1); - } - } - - // Remove from fileSystemSynchronizedGroups array - if (this.props.fileSystemSynchronizedGroups) { - const fsIndex = this.props.fileSystemSynchronizedGroups.findIndex( - (group) => group.uuid === uuid - ); - if (fsIndex !== -1) { - this.props.fileSystemSynchronizedGroups.splice(fsIndex, 1); - } - } - - // Clear productReference if it matches - if (this.props.productReference?.uuid === uuid) { - this.props.productReference = undefined; - } - - super.removeReference(uuid); - } - /** @returns the `PBXFrameworksBuildPhase` or creates one if there is none. Only one can exist. */ getFrameworksBuildPhase() { return ( diff --git a/src/api/PBXProject.ts b/src/api/PBXProject.ts index 7ebf0d6..efe6244 100644 --- a/src/api/PBXProject.ts +++ b/src/api/PBXProject.ts @@ -226,26 +226,8 @@ export class PBXProject extends AbstractObject { return target; } - isReferencing(uuid: string): boolean { - if ( - [ - this.props.mainGroup.uuid, - this.props.buildConfigurationList.uuid, - this.props.productRefGroup?.uuid, - ].includes(uuid) - ) { - return true; - } - return !!this.props.targets.find((target) => target.uuid === uuid); - } - removeReference(uuid: string) { - const index = this.props.targets.findIndex( - (target) => target.uuid === uuid - ); - if (index !== -1) { - this.props.targets.splice(index, 1); - } + super.removeReference(uuid); // Also remove from TargetAttributes if present if (this.props.attributes?.TargetAttributes?.[uuid]) { diff --git a/src/api/PBXTargetDependency.ts b/src/api/PBXTargetDependency.ts index 2a970b1..d749e29 100644 --- a/src/api/PBXTargetDependency.ts +++ b/src/api/PBXTargetDependency.ts @@ -41,12 +41,6 @@ export class PBXTargetDependency extends AbstractObject child.uuid === uuid - ); - if (index !== -1) { - this.props.buildConfigurations.splice(index, 1); - } - } - - isReferencing(uuid: string): boolean { - return this.props.buildConfigurations.some((child) => child.uuid === uuid); - } - /** Set a build setting on all build configurations. */ setBuildSetting( key: TSetting, From 7e02f146127963c2792863ce15636ced41303ef9 Mon Sep 17 00:00:00 2001 From: evanbacon Date: Mon, 12 Jan 2026 12:47:56 -0800 Subject: [PATCH 5/6] Update App Clip fixture and test references Renamed the App Clip fixture file from 009-missing-app-clip-target.pbxproj to 009-expo-app-clip.pbxproj and updated all test references accordingly. Also reformatted some test code for improved readability. --- src/api/__tests__/PBXNativeTarget.test.ts | 30 ++++++++++++++----- ...rget.pbxproj => 009-expo-app-clip.pbxproj} | 0 src/json/__tests__/json.test.ts | 6 ++-- 3 files changed, 25 insertions(+), 11 deletions(-) rename src/json/__tests__/fixtures/{009-missing-app-clip-target.pbxproj => 009-expo-app-clip.pbxproj} (100%) diff --git a/src/api/__tests__/PBXNativeTarget.test.ts b/src/api/__tests__/PBXNativeTarget.test.ts index 53a73ec..2167b00 100644 --- a/src/api/__tests__/PBXNativeTarget.test.ts +++ b/src/api/__tests__/PBXNativeTarget.test.ts @@ -26,7 +26,7 @@ const WATCH_FIXTURE = path.join( const APP_CLIP_FIXTURE = path.join( __dirname, - "../../json/__tests__/fixtures/009-missing-app-clip-target.pbxproj" + "../../json/__tests__/fixtures/009-expo-app-clip.pbxproj" ); describe("PBXNativeTarget", () => { @@ -37,7 +37,12 @@ describe("PBXNativeTarget", () => { "299522761BBF136400859F49" ) as PBXNativeTarget; - expect(obj.getReferrers().map((o) => o.uuid).sort()).toEqual([ + expect( + obj + .getReferrers() + .map((o) => o.uuid) + .sort() + ).toEqual([ "298D7C511BC2C7B200FD3B3E", // PBXTargetDependency that references this target "299522301BBF104D00859F49", // PBXProject that contains this target ]); @@ -609,9 +614,12 @@ describe("PBXNativeTarget", () => { // Record UUIDs of objects that should be removed const appClipBuildConfigList = appClipTarget.props.buildConfigurationList; - const appClipBuildConfigs = [...appClipBuildConfigList.props.buildConfigurations]; + const appClipBuildConfigs = [ + ...appClipBuildConfigList.props.buildConfigurations, + ]; const appClipProductRef = appClipTarget.props.productReference; - const appClipFileSystemGroups = appClipTarget.props.fileSystemSynchronizedGroups || []; + const appClipFileSystemGroups = + appClipTarget.props.fileSystemSynchronizedGroups || []; // Get the main target to verify it's not affected const mainTarget = project.props.targets.find( @@ -659,7 +667,9 @@ describe("PBXNativeTarget", () => { // Verify main target still exists and is functional expect(xcproj.has(mainTargetUuid)).toBe(true); - const stillExistingMainTarget = xcproj.getObject(mainTargetUuid) as PBXNativeTarget; + const stillExistingMainTarget = xcproj.getObject( + mainTargetUuid + ) as PBXNativeTarget; expect(stillExistingMainTarget.props.name).toBe("testlaunchappclip"); }); @@ -683,7 +693,8 @@ describe("PBXNativeTarget", () => { const clipDependency = mainTarget.props.dependencies.find( (dep) => dep.props.target?.uuid === "XX42A75F8F031FED491C16XX" || - dep.props.targetProxy?.props.remoteGlobalIDString === "XX42A75F8F031FED491C16XX" + dep.props.targetProxy?.props.remoteGlobalIDString === + "XX42A75F8F031FED491C16XX" ); expect(clipDependency).toBeDefined(); @@ -702,7 +713,8 @@ describe("PBXNativeTarget", () => { const remainingClipDependency = mainTarget.props.dependencies.find( (dep) => dep.props.target?.uuid === "XX42A75F8F031FED491C16XX" || - dep.props.targetProxy?.props.remoteGlobalIDString === "XX42A75F8F031FED491C16XX" + dep.props.targetProxy?.props.remoteGlobalIDString === + "XX42A75F8F031FED491C16XX" ); expect(remainingClipDependency).toBeUndefined(); }); @@ -738,7 +750,9 @@ describe("PBXNativeTarget", () => { // Verify main target still has the shared build phase expect( - mainTarget.props.buildPhases.find((p) => p.uuid === sharedBuildPhaseUuid) + mainTarget.props.buildPhases.find( + (p) => p.uuid === sharedBuildPhaseUuid + ) ).toBeDefined(); }); diff --git a/src/json/__tests__/fixtures/009-missing-app-clip-target.pbxproj b/src/json/__tests__/fixtures/009-expo-app-clip.pbxproj similarity index 100% rename from src/json/__tests__/fixtures/009-missing-app-clip-target.pbxproj rename to src/json/__tests__/fixtures/009-expo-app-clip.pbxproj diff --git a/src/json/__tests__/json.test.ts b/src/json/__tests__/json.test.ts index dc2e814..9caaefe 100644 --- a/src/json/__tests__/json.test.ts +++ b/src/json/__tests__/json.test.ts @@ -17,8 +17,8 @@ describe(parse, () => { expect(result).toEqual({ "123": "abc", "456": { - "789": "def" - } + "789": "def", + }, }); // Verify keys are strings, not numbers @@ -32,7 +32,7 @@ describe(parse, () => { "006-spm.pbxproj", "007-xcode16.pbxproj", "008-out-of-order-orphans.pbxproj", - "009-missing-app-clip-target.pbxproj", + "009-expo-app-clip.pbxproj", "shopify-tophat.pbxproj", "AFNetworking.pbxproj", "project.pbxproj", From 11333cca0e08e54111b63aa89217c9b98158209c Mon Sep 17 00:00:00 2001 From: evanbacon Date: Mon, 12 Jan 2026 12:51:10 -0800 Subject: [PATCH 6/6] Update CLAUDE.md --- CLAUDE.md | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index d6a276e..a95ce02 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -42,6 +42,46 @@ The package offers two main APIs: 3. **Reference Resolution**: Objects maintain references to each other via UUIDs 4. **Type Safety**: Full TypeScript coverage with strict compiler options +### The `getObjectProps()` Pattern + +Each object class defines `getObjectProps()` to declare which properties contain UUID references: + +```typescript +protected getObjectProps() { + return { + buildConfigurationList: String, // Single object reference + dependencies: [String], // Array of object references + buildPhases: [String], + }; +} +``` + +This powers several automatic behaviors in `AbstractObject`: +- **Inflation**: UUIDs are automatically resolved to object instances +- **Serialization**: Objects are deflated back to UUIDs in `toJSON()` +- **Reference tracking**: `isReferencing(uuid)` and `removeReference(uuid)` work automatically + +When adding new object types or properties, define them in `getObjectProps()` to get this behavior for free. Only override `isReferencing`/`removeReference` if you need custom logic (e.g., `PBXProject` removes from `TargetAttributes`). + +### Implementing `removeFromProject()` + +When implementing cascade deletion (removing an object and its children): + +1. **Check exclusive ownership**: Before removing a child, verify no other object uses it +2. **Beware of display groups**: Objects like `productReference` or `fileSystemSynchronizedGroups` may be in a PBXGroup for display but still belong to one target +3. **For target-owned objects**: Check if any OTHER target references the object, not just if it has any referrers + +```typescript +// Good: Check if another target uses this product reference +const usedByOtherTarget = [...project.values()].some( + (obj) => PBXNativeTarget.is(obj) && obj.uuid !== this.uuid && + obj.props.productReference?.uuid === this.props.productReference?.uuid +); +if (!usedByOtherTarget) { + this.props.productReference.removeFromProject(); +} +``` + ### Entry Points - `src/index.ts` - Exports the high-level API @@ -55,4 +95,14 @@ Tests are located in `__tests__/` directories throughout the codebase: - `src/json/__tests__/` - JSON parser tests with various pbxproj formats - Fixtures in `src/json/__tests__/fixtures/` contain real-world project files -The test suite uses Jest with TypeScript support and includes extensive fixtures from various Xcode project types (React Native, Swift, CocoaPods, etc.). \ No newline at end of file +The test suite uses Jest with TypeScript support and includes extensive fixtures from various Xcode project types (React Native, Swift, CocoaPods, etc.). + +### Fixture Naming + +New fixtures should follow the pattern `NNN-description.pbxproj` (e.g., `009-expo-app-clip.pbxproj`). Add new fixtures to the `fixtures` array in `src/json/__tests__/json.test.ts` for parsing validation against `plutil`. + +### JSON Tests + +In `json.test.ts`, fixtures are added to two arrays: +- `fixtures` - Tests parsing correctness by comparing against macOS `plutil` output +- `inOutFixtures` - Tests round-trip (parse → build → should equal original). Only add here if the fixture round-trips perfectly; some fixtures have known formatting differences. \ No newline at end of file