/** * Expo Config Plugin to bundle TTS model files for iOS * Uses a Run Script build phase to copy models during build */ const { withXcodeProject } = require('@expo/config-plugins'); const path = require('path'); const fs = require('fs'); const withTTSModels = (config) => { return withXcodeProject(config, async (config) => { const projectRoot = config.modRequest.projectRoot; const xcodeProject = config.modResults; // Source model directory (relative to project root) const modelSrcDir = path.join(projectRoot, 'assets', 'tts-models'); if (!fs.existsSync(modelSrcDir)) { console.warn('[withTTSModels] ⚠️ Model directory not found:', modelSrcDir); return config; } console.log('[withTTSModels] Found models at:', modelSrcDir); // Get the first target const target = xcodeProject.getFirstTarget(); if (!target) { console.error('[withTTSModels] ❌ No target found'); return config; } // Path relative to ios directory (where Xcode project lives) const iosDir = path.join(projectRoot, 'ios'); const relativeModelPath = path.relative(iosDir, modelSrcDir); console.log('[withTTSModels] Relative path from ios/:', relativeModelPath); // Create a Run Script build phase to copy ONLY Lessac model during build const scriptName = '[TTS] Copy Model Files'; const scriptContent = [ '# Copy TTS model files to app bundle (ONLY Lessac voice)', 'echo "📦 Copying TTS model to app bundle..."', '', 'SOURCE_DIR="${SRCROOT}/' + relativeModelPath + '"', 'DEST_DIR="${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/assets/tts-models"', 'LESSAC_MODEL="vits-piper-en_US-lessac-medium"', '', '# Only copy Lessac model', 'if [ -d "$SOURCE_DIR/$LESSAC_MODEL" ]; then', ' mkdir -p "$DEST_DIR"', ' cp -R "$SOURCE_DIR/$LESSAC_MODEL" "$DEST_DIR/"', ' MODEL_SIZE=$(du -sh "$SOURCE_DIR/$LESSAC_MODEL" | cut -f1)', ' echo "✅ Lessac TTS model copied successfully ($MODEL_SIZE)"', ' echo " From: $SOURCE_DIR/$LESSAC_MODEL"', ' echo " To: $DEST_DIR/$LESSAC_MODEL"', 'else', ' echo "⚠️ Lessac model not found at: $SOURCE_DIR/$LESSAC_MODEL"', ' exit 1', 'fi' ].join('\n'); // Check if script already exists const buildPhases = xcodeProject.hash.project.objects.PBXShellScriptBuildPhase || {}; let scriptExists = false; for (const key in buildPhases) { if (buildPhases[key].name === scriptName) { scriptExists = true; console.log('[withTTSModels] Run Script phase already exists'); break; } } if (!scriptExists) { // Add the Run Script build phase xcodeProject.addBuildPhase( [], 'PBXShellScriptBuildPhase', scriptName, target.uuid, { shellPath: '/bin/sh', shellScript: scriptContent, // Run before Copy Bundle Resources phase runOnlyForDeploymentPostprocessing: 0 } ); console.log('[withTTSModels] ✅ Added Run Script build phase'); } console.log('[withTTSModels] ✅ Plugin configured successfully'); return config; }); }; module.exports = withTTSModels;