// @ts-check const { withDangerousMod, withXcodeProject, IOSConfig, } = require("@expo/config-plugins"); const fs = require("fs"); const path = require("path"); const SWIFT_FILES = [ "BeenVoiceIntentHelpers.swift", "ClockInIntent.swift", "ClockOutIntent.swift", "OpenTimerIntent.swift", "BeenVoiceShortcuts.swift", ]; const SHORTCUT_REGISTRATION = `Task { await BeenVoiceShortcuts.updateAppShortcutParameters() }`; /** @type {import('@expo/config-plugins').ConfigPlugin} */ function withAppIntents(config) { const appIntentsSource = path.join( config._internal?.projectRoot ?? process.cwd(), "plugins", "app-intents", ); config = withDangerousMod(config, [ "ios", async (config) => { const projectRoot = config.modRequest.projectRoot; const platformRoot = config.modRequest.platformProjectRoot; const projectName = config.modRequest.projectName ?? IOSConfig.XcodeUtils.getProjectName(projectRoot); const targetDir = path.join(platformRoot, projectName); fs.mkdirSync(targetDir, { recursive: true }); for (const file of SWIFT_FILES) { fs.copyFileSync(path.join(appIntentsSource, file), path.join(targetDir, file)); } const appDelegatePath = path.join(targetDir, "AppDelegate.swift"); if (fs.existsSync(appDelegatePath)) { let appDelegate = fs.readFileSync(appDelegatePath, "utf8"); if (!appDelegate.includes("import AppIntents")) { appDelegate = appDelegate.replace( "internal import Expo", "import AppIntents\ninternal import Expo", ); } if (!appDelegate.includes("BeenVoiceShortcuts.updateAppShortcutParameters")) { appDelegate = appDelegate.replace( "return super.application(application, didFinishLaunchingWithOptions: launchOptions)", `${SHORTCUT_REGISTRATION} return super.application(application, didFinishLaunchingWithOptions: launchOptions)`, ); } else { appDelegate = appDelegate.replace( /if #available\(iOS 1[68]\.0, \*\) \{\s*Task \{\s*await BeenVoiceShortcuts\.updateAppShortcutParameters\(\)\s*\}\s*\}/g, SHORTCUT_REGISTRATION, ); } if (!appDelegate.includes("applicationDidBecomeActive")) { appDelegate = appDelegate.replace( " // Linking API", ` public override func applicationDidBecomeActive(_ application: UIApplication) { ${SHORTCUT_REGISTRATION} super.applicationDidBecomeActive(application) } // Linking API`, ); } fs.writeFileSync(appDelegatePath, appDelegate); } return config; }, ]); return withXcodeProject(config, (config) => { const project = config.modResults; const projectRoot = config.modRequest.projectRoot; const platformRoot = config.modRequest.platformProjectRoot; const projectName = config.modRequest.projectName ?? IOSConfig.XcodeUtils.getProjectName(projectRoot); for (const file of SWIFT_FILES) { const filepath = `${projectName}/${file}`; const absolutePath = path.join(platformRoot, filepath); if (!fs.existsSync(absolutePath)) continue; const fileRef = project.pbxFileReferenceSection(); const alreadyLinked = Object.values(fileRef).some( (entry) => entry && typeof entry === "object" && "path" in entry && (entry.path === file || entry.path === `${projectName}/${file}` || String(entry.path).endsWith(`/${file}`)), ); if (!alreadyLinked) { IOSConfig.XcodeUtils.addBuildSourceFileToGroup({ filepath, groupName: projectName, project, verbose: true, }); } } const configurations = project.pbxXCBuildConfigurationSection(); for (const key of Object.keys(configurations)) { const buildConfig = configurations[key]; if ( typeof buildConfig !== "object" || !buildConfig.buildSettings || buildConfig.buildSettings.PRODUCT_BUNDLE_IDENTIFIER !== "com.beenvoice.app" ) { continue; } buildConfig.buildSettings.EXTRACT_APP_INTENTS_METADATA = "YES"; } return config; }); } module.exports = withAppIntents;