/** * Expo Config Plugin: StoreKit Configuration for Sandbox Testing * * Adds TabataFit.storekit to the Xcode project and configures the scheme * to use it for StoreKit testing. This enables purchase testing in the * iOS simulator without real Apple Pay charges. */ const { withXcodeProject, withDangerousMod, } = require('@expo/config-plugins') const fs = require('fs') const path = require('path') const STOREKIT_FILENAME = 'TabataFit.storekit' /** * Step 1: Copy the .storekit file and add it to the Xcode project (project.pbxproj) */ function withStoreKitXcodeProject(config) { return withXcodeProject(config, (config) => { const project = config.modResults const projectName = config.modRequest.projectName const platformRoot = config.modRequest.platformProjectRoot // Copy .storekit file into the iOS app directory const sourceFile = path.resolve(__dirname, '..', 'storekit', STOREKIT_FILENAME) const destDir = path.join(platformRoot, projectName) const destFile = path.join(destDir, STOREKIT_FILENAME) fs.copyFileSync(sourceFile, destFile) // Add file to the Xcode project manually via pbxproj APIs // Find the app's PBXGroup const mainGroupKey = project.getFirstProject().firstProject.mainGroup const mainGroup = project.getPBXGroupByKey(mainGroupKey) // Find the app target group (e.g., "tabatago") let appGroupKey = null if (mainGroup && mainGroup.children) { for (const child of mainGroup.children) { if (child.comment === projectName) { appGroupKey = child.value break } } } if (!appGroupKey) { console.warn('[withStoreKitConfig] Could not find app group in Xcode project') return config } const appGroup = project.getPBXGroupByKey(appGroupKey) // Check if already added const alreadyExists = appGroup.children?.some( (child) => child.comment === STOREKIT_FILENAME ) if (!alreadyExists) { // Generate a unique UUID for the file reference const fileRefUuid = project.generateUuid() // Add PBXFileReference — NOT added to any build phase // .storekit files are testing configs, not app resources const pbxFileRef = project.hash.project.objects['PBXFileReference'] pbxFileRef[fileRefUuid] = { isa: 'PBXFileReference', lastKnownFileType: 'text.json', path: STOREKIT_FILENAME, sourceTree: '""', } pbxFileRef[`${fileRefUuid}_comment`] = STOREKIT_FILENAME // Add to the app group's children (visible in Xcode navigator) appGroup.children.push({ value: fileRefUuid, comment: STOREKIT_FILENAME, }) console.log('[withStoreKitConfig] Added', STOREKIT_FILENAME, 'to Xcode project') } return config }) } /** * Step 2: Configure the Xcode scheme to use the StoreKit configuration */ function withStoreKitScheme(config) { return withDangerousMod(config, [ 'ios', (config) => { const projectName = config.modRequest.projectName const schemePath = path.join( config.modRequest.platformProjectRoot, `${projectName}.xcodeproj`, 'xcshareddata', 'xcschemes', `${projectName}.xcscheme` ) if (!fs.existsSync(schemePath)) { console.warn('[withStoreKitConfig] Scheme not found:', schemePath) return config } let scheme = fs.readFileSync(schemePath, 'utf8') // Skip if already configured if (scheme.includes('storeKitConfigurationFileReference')) { console.log('[withStoreKitConfig] StoreKit config already in scheme') return config } // Insert StoreKitConfigurationFileReference as a child element of LaunchAction // The identifier path is relative to the workspace root (ios/ directory) const storeKitRef = ` ` // Insert before the closing tag scheme = scheme.replace( '', `${storeKitRef}\n ` ) fs.writeFileSync(schemePath, scheme, 'utf8') console.log('[withStoreKitConfig] Added StoreKit config to scheme') return config }, ]) } /** * Main plugin */ function withStoreKitConfig(config) { config = withStoreKitXcodeProject(config) config = withStoreKitScheme(config) return config } module.exports = withStoreKitConfig