diff --git a/cli/src/config.ts b/cli/src/config.ts index e504f2539..9a4fda517 100644 --- a/cli/src/config.ts +++ b/cli/src/config.ts @@ -267,7 +267,11 @@ async function loadIOSConfig(rootDir: string, extConfig: ExternalConfig): Promis const podPath = lazy(() => determineGemfileOrCocoapodPath(rootDir, platformDirAbs, nativeProjectDirAbs)); const webDirAbs = lazy(() => determineIOSWebDirAbs(nativeProjectDirAbs, nativeTargetDirAbs, nativeXcodeProjDirAbs)); const cordovaPluginsDir = 'capacitor-cordova-ios-plugins'; - + const buildOptions = { + xcodeSigningStyle: extConfig.ios?.buildOptions?.signingStyle, + signingCertificate: extConfig.ios?.buildOptions?.signingCertificate, + provisioningProfile: extConfig.ios?.buildOptions?.provisioningProfile, + }; return { name, minVersion: '14.0', @@ -287,6 +291,7 @@ async function loadIOSConfig(rootDir: string, extConfig: ExternalConfig): Promis webDir: lazy(async () => relative(platformDirAbs, await webDirAbs)), webDirAbs, podPath, + buildOptions, }; } diff --git a/cli/src/declarations.ts b/cli/src/declarations.ts index d12b832bf..5a1cefcee 100644 --- a/cli/src/declarations.ts +++ b/cli/src/declarations.ts @@ -478,6 +478,28 @@ export interface CapacitorConfig { * @default true */ initialFocus?: boolean; + + buildOptions?: { + /** + * The signing style to use when building the app for distribution. + * + * @since 7.0.0 + * @default 'automatic' + */ + signingStyle?: 'automatic' | 'manual'; + /** + * A certificate name, SHA-1 hash, or automatic selector to use for signing for iOS builds. + * + * @since 7.0.0 + */ + signingCertificate?: string; + /** + * A provisioning profile name or UUID for iOS builds. + * + * @since 7.0.0 + */ + provisioningProfile?: string; + }; }; server?: { diff --git a/cli/src/definitions.ts b/cli/src/definitions.ts index a81278120..3fb9bc317 100644 --- a/cli/src/definitions.ts +++ b/cli/src/definitions.ts @@ -117,6 +117,11 @@ export interface IOSConfig extends PlatformConfig { readonly nativeXcodeProjDirAbs: string; readonly nativeXcodeWorkspaceDir: Promise; readonly nativeXcodeWorkspaceDirAbs: Promise; + readonly buildOptions: { + xcodeSigningStyle?: 'automatic' | 'manual'; + signingCertificate?: string; + provisioningProfile?: string; + }; } export type WebConfig = PlatformConfig; diff --git a/cli/src/index.ts b/cli/src/index.ts index 7afede477..bb38348a4 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -147,6 +147,24 @@ export function runProgram(config: Config): void { 'jarsigner', ]), ) + .addOption( + new Option( + '--xcode-signing-style ', + 'The iOS signing style to use when building the app for distribution (default: automatic)', + ).choices(['automatic', 'manual']), + ) + .addOption( + new Option( + '--xcode-signing-certificate ', + 'A certificate name, SHA-1 hash, or automatic selector to use for signing for iOS builds', + ), + ) + .addOption( + new Option( + '--xcode-provisioning-profile ', + 'A provisioning profile name or UUID for iOS builds', + ), + ) .action( wrapAction( telemetryAction( @@ -163,6 +181,9 @@ export function runProgram(config: Config): void { androidreleasetype, signingType, configuration, + xcodeSigningStyle, + xcodeSigningCertificate, + xcodeProvisioningProfile, }, ) => { const { buildCommand } = await import('./tasks/build'); @@ -176,6 +197,9 @@ export function runProgram(config: Config): void { androidreleasetype, signingtype: signingType, configuration, + xcodeSigningType: xcodeSigningStyle, + xcodeSigningCertificate, + xcodeProvisioningProfile, }); }, ), diff --git a/cli/src/ios/build.ts b/cli/src/ios/build.ts index 6c88421d3..55fc7870a 100644 --- a/cli/src/ios/build.ts +++ b/cli/src/ios/build.ts @@ -25,6 +25,13 @@ export async function buildiOS(config: Config, buildOptions: BuildCommandOptions projectName = basename(await config.ios.nativeXcodeProjDirAbs); } + if ( + buildOptions.xcodeSigningType == 'manual' && + (!buildOptions.xcodeSigningCertificate || !buildOptions.xcodeProvisioningProfile) + ) { + throw 'Manually signed Xcode builds require a signing certificate and provisioning profile.'; + } + await runTask('Building xArchive', async () => runCommand( 'xcodebuild', @@ -45,12 +52,23 @@ export async function buildiOS(config: Config, buildOptions: BuildCommandOptions ), ); + const manualSigningContents = `provisioningProfiles + +${config.app.appId} +${buildOptions.xcodeProvisioningProfile ?? ''} + +signingCertificate +${buildOptions.xcodeSigningCertificate ?? ''}`; + const archivePlistContents = ` method app-store-connect +signingStyle +${buildOptions.xcodeSigningType} +${buildOptions.xcodeSigningType == 'manual' ? manualSigningContents : ''} `; diff --git a/cli/src/tasks/build.ts b/cli/src/tasks/build.ts index 783600624..a5c16a51b 100644 --- a/cli/src/tasks/build.ts +++ b/cli/src/tasks/build.ts @@ -14,6 +14,9 @@ export interface BuildCommandOptions { androidreleasetype?: 'AAB' | 'APK'; signingtype?: 'apksigner' | 'jarsigner'; configuration: string; + xcodeSigningType?: 'automatic' | 'manual'; + xcodeSigningCertificate?: string; + xcodeProvisioningProfile?: string; } export async function buildCommand( @@ -42,6 +45,9 @@ export async function buildCommand( androidreleasetype: buildOptions.androidreleasetype || config.android.buildOptions.releaseType || 'AAB', signingtype: buildOptions.signingtype || config.android.buildOptions.signingType || 'jarsigner', configuration: buildOptions.configuration || 'Release', + xcodeSigningType: buildOptions.xcodeSigningType || config.ios.buildOptions.xcodeSigningStyle || 'automatic', + xcodeSigningCertificate: buildOptions.xcodeSigningCertificate || config.ios.buildOptions.signingCertificate, + xcodeProvisioningProfile: buildOptions.xcodeProvisioningProfile || config.ios.buildOptions.provisioningProfile, }; try {