diff --git a/.ios-release.env.example b/.ios-release.env.example index b0459ce..c2ebd6d 100644 --- a/.ios-release.env.example +++ b/.ios-release.env.example @@ -9,6 +9,9 @@ APPLE_TEAM_ID= # # If export fails with "profile doesn't include signing certificate", regenerate App Store # profiles at developer.apple.com for com.beenvoice.app and com.beenvoice.app.ExpoWidgetsTarget, +# Optional overrides when those App Store profile names differ from the bundle IDs: +# IOS_MAIN_APPSTORE_PROFILE_NAME=com.beenvoice.app +# IOS_WIDGET_APPSTORE_PROFILE_NAME=com.beenvoice.app.ExpoWidgetsTarget # then re-run the full release (not --export-only). # Production API baked into the JS bundle (App Store / TestFlight) diff --git a/plugins/withAppStoreSigning.js b/plugins/withAppStoreSigning.js index aa22971..716e98e 100644 --- a/plugins/withAppStoreSigning.js +++ b/plugins/withAppStoreSigning.js @@ -2,41 +2,63 @@ const { withXcodeProject } = require("@expo/config-plugins"); const RELEASE_SIGN_KEY = '"CODE_SIGN_IDENTITY[sdk=iphoneos*]"'; +const MAIN_BUNDLE_ID = "com.beenvoice.app"; +const WIDGET_BUNDLE_ID = "com.beenvoice.app.ExpoWidgetsTarget"; /** - * RN / Expo sets Release CODE_SIGN_IDENTITY to "iPhone Developer", which forces - * development-signed archives. Remove it so automatic signing picks Distribution - * for App Store archives. + * Keep App Store archives on distribution signing. Automatic signing can select + * an Apple Development identity for the widget target when archiving from the CLI, + * so both release targets use their explicit App Store profiles instead. */ +function configureReleaseSigning(project) { + const configurations = project.pbxXCBuildConfigurationSection(); + + for (const key of Object.keys(configurations)) { + const buildConfig = configurations[key]; + if ( + !buildConfig || + typeof buildConfig !== "object" || + !buildConfig.buildSettings + ) { + continue; + } + + if (buildConfig.name !== "Release") { + continue; + } + + const bundleId = String( + buildConfig.buildSettings.PRODUCT_BUNDLE_IDENTIFIER ?? "", + ).replaceAll('"', ""); + if (bundleId !== MAIN_BUNDLE_ID && bundleId !== WIDGET_BUNDLE_ID) { + continue; + } + + const profileName = + bundleId === WIDGET_BUNDLE_ID + ? (process.env.IOS_WIDGET_APPSTORE_PROFILE_NAME ?? WIDGET_BUNDLE_ID) + : (process.env.IOS_MAIN_APPSTORE_PROFILE_NAME ?? MAIN_BUNDLE_ID); + + buildConfig.buildSettings[RELEASE_SIGN_KEY] = '"Apple Distribution"'; + buildConfig.buildSettings.CODE_SIGN_STYLE = "Manual"; + buildConfig.buildSettings.PROVISIONING_PROFILE_SPECIFIER = `"${profileName}"`; + + if (process.env.APPLE_TEAM_ID) { + buildConfig.buildSettings.DEVELOPMENT_TEAM = process.env.APPLE_TEAM_ID; + } + } + + return project; +} + /** @type {import('@expo/config-plugins').ConfigPlugin} */ function withAppStoreSigning(config) { return withXcodeProject(config, (config) => { - const project = config.modResults; - const configurations = project.pbxXCBuildConfigurationSection(); - - for (const key of Object.keys(configurations)) { - const buildConfig = configurations[key]; - if (!buildConfig || typeof buildConfig !== "object" || !buildConfig.buildSettings) { - continue; - } - - if (buildConfig.name !== "Release") { - continue; - } - - const identity = buildConfig.buildSettings[RELEASE_SIGN_KEY]; - if ( - identity === "iPhone Developer" || - identity === '"iPhone Developer"' || - identity === "Apple Distribution" || - identity === '"Apple Distribution"' - ) { - delete buildConfig.buildSettings[RELEASE_SIGN_KEY]; - } - } + configureReleaseSigning(config.modResults); return config; }); } module.exports = withAppStoreSigning; +module.exports.configureReleaseSigning = configureReleaseSigning; diff --git a/scripts/configure-ios-signing.js b/scripts/configure-ios-signing.js new file mode 100644 index 0000000..0897fd5 --- /dev/null +++ b/scripts/configure-ios-signing.js @@ -0,0 +1,20 @@ +// @ts-check +const fs = require("fs"); +const path = require("path"); +const xcode = require("xcode"); +const { configureReleaseSigning } = require("../plugins/withAppStoreSigning"); + +const projectPath = process.argv[2]; +if (!projectPath) { + throw new Error( + "Usage: node scripts/configure-ios-signing.js ", + ); +} + +const resolvedPath = path.resolve(projectPath); +const project = xcode.project(resolvedPath); +project.parseSync(); +configureReleaseSigning(project); +fs.writeFileSync(resolvedPath, project.writeSync()); + +console.log("Configured manual App Store signing for iOS release targets."); diff --git a/scripts/ios-release.sh b/scripts/ios-release.sh index 46b9854..9eebf18 100755 --- a/scripts/ios-release.sh +++ b/scripts/ios-release.sh @@ -185,6 +185,10 @@ prepare_native_project() { fi ) + if [[ -f "$ROOT/$PROJECT/project.pbxproj" ]]; then + node "$ROOT/scripts/configure-ios-signing.js" "$ROOT/$PROJECT/project.pbxproj" + fi + resolve_xcode_workspace } @@ -241,7 +245,6 @@ archive_app() { -archivePath "$ARCHIVE_PATH" \ -destination "generic/platform=iOS" \ -allowProvisioningUpdates \ - CODE_SIGN_STYLE=Automatic \ DEVELOPMENT_TEAM="$APPLE_TEAM_ID" \ "${API_AUTH_ARGS[@]}" \ archive