Skip to content

Commit 90f95d1

Browse files
theproducerjcesarmobilemarkemer
authored
feat(cli): add more configurations to build command (#7769)
Co-authored-by: jcesarmobile <jcesarmobile@gmail.com> Co-authored-by: Mark Anderson <mark@ionic.io> Co-authored-by: Mark Anderson <mark.anderson@outsystems.com>
1 parent 64a8bc4 commit 90f95d1

File tree

7 files changed

+175
-40
lines changed

7 files changed

+175
-40
lines changed

cli/src/config.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,12 @@ async function loadIOSConfig(rootDir: string, extConfig: ExternalConfig): Promis
267267
const podPath = lazy(() => determineGemfileOrCocoapodPath(rootDir, platformDirAbs, nativeProjectDirAbs));
268268
const webDirAbs = lazy(() => determineIOSWebDirAbs(nativeProjectDirAbs, nativeTargetDirAbs, nativeXcodeProjDirAbs));
269269
const cordovaPluginsDir = 'capacitor-cordova-ios-plugins';
270-
270+
const buildOptions = {
271+
xcodeExportMethod: extConfig.ios?.buildOptions?.exportMethod,
272+
xcodeSigningStyle: extConfig.ios?.buildOptions?.signingStyle,
273+
signingCertificate: extConfig.ios?.buildOptions?.signingCertificate,
274+
provisioningProfile: extConfig.ios?.buildOptions?.provisioningProfile,
275+
};
271276
return {
272277
name,
273278
minVersion: '14.0',
@@ -287,6 +292,7 @@ async function loadIOSConfig(rootDir: string, extConfig: ExternalConfig): Promis
287292
webDir: lazy(async () => relative(platformDirAbs, await webDirAbs)),
288293
webDirAbs,
289294
podPath,
295+
buildOptions,
290296
};
291297
}
292298

cli/src/declarations.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,35 @@ export interface CapacitorConfig {
478478
* @default true
479479
*/
480480
initialFocus?: boolean;
481+
482+
buildOptions?: {
483+
/**
484+
* The signing style to use when building the app for distribution.
485+
*
486+
* @since 7.0.0
487+
* @default 'automatic'
488+
*/
489+
signingStyle?: 'automatic' | 'manual';
490+
/**
491+
* The method used by xcodebuild to export the archive
492+
*
493+
* @since 7.0.0
494+
* @default 'app-store-connect'
495+
*/
496+
exportMethod?: string;
497+
/**
498+
* A certificate name, SHA-1 hash, or automatic selector to use for signing for iOS builds.
499+
*
500+
* @since 7.0.0
501+
*/
502+
signingCertificate?: string;
503+
/**
504+
* A provisioning profile name or UUID for iOS builds.
505+
*
506+
* @since 7.0.0
507+
*/
508+
provisioningProfile?: string;
509+
};
481510
};
482511

483512
server?: {

cli/src/definitions.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,16 @@ export interface AndroidConfig extends PlatformConfig {
101101
};
102102
}
103103

104+
export enum XcodeExportMethod {
105+
AppStoreConnect = 'app-store-connect',
106+
ReleaseTesting = 'release-testing',
107+
Enterprise = 'enterprise',
108+
Debugging = 'debugging',
109+
DeveloperID = 'developer-id',
110+
MacApplication = 'mac-application',
111+
Validation = 'validation',
112+
}
113+
104114
export interface IOSConfig extends PlatformConfig {
105115
readonly cordovaPluginsDir: string;
106116
readonly cordovaPluginsDirAbs: string;
@@ -117,6 +127,13 @@ export interface IOSConfig extends PlatformConfig {
117127
readonly nativeXcodeProjDirAbs: string;
118128
readonly nativeXcodeWorkspaceDir: Promise<string>;
119129
readonly nativeXcodeWorkspaceDirAbs: Promise<string>;
130+
readonly buildOptions: {
131+
teamId?: string;
132+
exportMethod?: XcodeExportMethod;
133+
xcodeSigningStyle?: 'automatic' | 'manual';
134+
signingCertificate?: string;
135+
provisioningProfile?: string;
136+
};
120137
}
121138

122139
export type WebConfig = PlatformConfig;

cli/src/index.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,41 @@ export function runProgram(config: Config): void {
147147
'jarsigner',
148148
]),
149149
)
150+
.addOption(
151+
new Option('--xcode-team-id <xcodeTeamID>', 'The Developer team to use for building and exporting the archive'),
152+
)
153+
.addOption(
154+
new Option(
155+
'--xcode-export-method <xcodeExportMethod>',
156+
'Describes how xcodebuild should export the archive (default: app-store-connect)',
157+
).choices([
158+
'app-store-connect',
159+
'release-testing',
160+
'enterprise',
161+
'debugging',
162+
'developer-id',
163+
'mac-application',
164+
'validation',
165+
]),
166+
)
167+
.addOption(
168+
new Option(
169+
'--xcode-signing-style <xcodeSigningStyle>',
170+
'The iOS signing style to use when building the app for distribution (default: automatic)',
171+
).choices(['automatic', 'manual']),
172+
)
173+
.addOption(
174+
new Option(
175+
'--xcode-signing-certificate <xcodeSigningCertificate>',
176+
'A certificate name, SHA-1 hash, or automatic selector to use for signing for iOS builds',
177+
),
178+
)
179+
.addOption(
180+
new Option(
181+
'--xcode-provisioning-profile <xcodeProvisioningProfile>',
182+
'A provisioning profile name or UUID for iOS builds',
183+
),
184+
)
150185
.action(
151186
wrapAction(
152187
telemetryAction(
@@ -163,6 +198,11 @@ export function runProgram(config: Config): void {
163198
androidreleasetype,
164199
signingType,
165200
configuration,
201+
xcodeTeamId,
202+
xcodeExportMethod,
203+
xcodeSigningStyle,
204+
xcodeSigningCertificate,
205+
xcodeProvisioningProfile,
166206
},
167207
) => {
168208
const { buildCommand } = await import('./tasks/build');
@@ -176,6 +216,11 @@ export function runProgram(config: Config): void {
176216
androidreleasetype,
177217
signingtype: signingType,
178218
configuration,
219+
xcodeTeamId,
220+
xcodeExportMethod,
221+
xcodeSigningType: xcodeSigningStyle,
222+
xcodeSigningCertificate,
223+
xcodeProvisioningProfile,
179224
});
180225
},
181226
),

cli/src/ios/build.ts

Lines changed: 64 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import { basename, join } from 'path';
33
import { rimraf } from 'rimraf';
44

55
import { runTask } from '../common';
6-
import type { Config } from '../definitions';
6+
import { XcodeExportMethod, type Config } from '../definitions';
77
import { logSuccess } from '../log';
8-
import type { BuildCommandOptions } from '../tasks/build';
8+
import { type BuildCommandOptions } from '../tasks/build';
99
import { checkPackageManager } from '../util/spm';
1010
import { runCommand } from '../util/subprocess';
1111

@@ -25,59 +25,84 @@ export async function buildiOS(config: Config, buildOptions: BuildCommandOptions
2525
projectName = basename(await config.ios.nativeXcodeProjDirAbs);
2626
}
2727

28+
if (
29+
buildOptions.xcodeSigningType == 'manual' &&
30+
(!buildOptions.xcodeSigningCertificate || !buildOptions.xcodeProvisioningProfile)
31+
) {
32+
throw 'Manually signed Xcode builds require a signing certificate and provisioning profile.';
33+
}
34+
35+
const buildArgs = [
36+
typeOfBuild,
37+
projectName,
38+
'-scheme',
39+
`${theScheme}`,
40+
'-destination',
41+
`generic/platform=iOS`,
42+
'-archivePath',
43+
`${theScheme}.xcarchive`,
44+
'archive',
45+
];
46+
47+
if (buildOptions.xcodeTeamId) {
48+
buildArgs.push(`DEVELOPMENT_TEAM=${buildOptions.xcodeTeamId}`);
49+
}
50+
51+
if (buildOptions.xcodeSigningType == 'manual') {
52+
buildArgs.push(`PROVISIONING_PROFILE_SPECIFIER=${buildOptions.xcodeProvisioningProfile}`);
53+
}
54+
2855
await runTask('Building xArchive', async () =>
29-
runCommand(
30-
'xcodebuild',
31-
[
32-
typeOfBuild,
33-
projectName,
34-
'-scheme',
35-
`${theScheme}`,
36-
'-destination',
37-
`generic/platform=iOS`,
38-
'-archivePath',
39-
`${theScheme}.xcarchive`,
40-
'archive',
41-
],
42-
{
43-
cwd: config.ios.nativeProjectDirAbs,
44-
},
45-
),
56+
runCommand('xcodebuild', buildArgs, {
57+
cwd: config.ios.nativeProjectDirAbs,
58+
}),
4659
);
4760

61+
const manualSigningContents = `<key>provisioningProfiles</key>
62+
<dict>
63+
<key>${config.app.appId}</key>
64+
<string>${buildOptions.xcodeProvisioningProfile ?? ''}</string>
65+
</dict>
66+
<key>signingCertificate</key>
67+
<string>${buildOptions.xcodeSigningCertificate ?? ''}</string>`;
68+
4869
const archivePlistContents = `<?xml version="1.0" encoding="UTF-8"?>
4970
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
5071
<plist version="1.0">
5172
<dict>
5273
<key>method</key>
53-
<string>app-store-connect</string>
74+
<string>${buildOptions.xcodeExportMethod ?? XcodeExportMethod.AppStoreConnect}</string>
75+
<key>signingStyle</key>
76+
<string>${buildOptions.xcodeSigningType}</string>
77+
${buildOptions.xcodeSigningType == 'manual' ? manualSigningContents : ''}
5478
</dict>
5579
</plist>`;
5680

5781
const archivePlistPath = join(`${config.ios.nativeProjectDirAbs}`, 'archive.plist');
5882

5983
writeFileSync(archivePlistPath, archivePlistContents);
6084

85+
const archiveArgs = [
86+
'archive',
87+
'-archivePath',
88+
`${theScheme}.xcarchive`,
89+
'-exportArchive',
90+
'-exportOptionsPlist',
91+
'archive.plist',
92+
'-exportPath',
93+
'output',
94+
'-configuration',
95+
buildOptions.configuration,
96+
];
97+
98+
if (buildOptions.xcodeSigningType == 'automatic') {
99+
archiveArgs.push('-allowProvisioningUpdates');
100+
}
101+
61102
await runTask('Building IPA', async () =>
62-
runCommand(
63-
'xcodebuild',
64-
[
65-
'archive',
66-
'-archivePath',
67-
`${theScheme}.xcarchive`,
68-
'-exportArchive',
69-
'-exportOptionsPlist',
70-
'archive.plist',
71-
'-exportPath',
72-
'output',
73-
'-allowProvisioningUpdates',
74-
'-configuration',
75-
buildOptions.configuration,
76-
],
77-
{
78-
cwd: config.ios.nativeProjectDirAbs,
79-
},
80-
),
103+
runCommand('xcodebuild', archiveArgs, {
104+
cwd: config.ios.nativeProjectDirAbs,
105+
}),
81106
);
82107

83108
await runTask('Cleaning up', async () => {

cli/src/tasks/build.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { buildAndroid } from '../android/build';
22
import { selectPlatforms, promptForPlatform } from '../common';
33
import type { Config } from '../definitions';
4+
import { XcodeExportMethod } from '../definitions';
45
import { fatal, isFatal } from '../errors';
56
import { buildiOS } from '../ios/build';
67

@@ -14,6 +15,11 @@ export interface BuildCommandOptions {
1415
androidreleasetype?: 'AAB' | 'APK';
1516
signingtype?: 'apksigner' | 'jarsigner';
1617
configuration: string;
18+
xcodeTeamId?: string;
19+
xcodeExportMethod?: XcodeExportMethod;
20+
xcodeSigningType?: 'automatic' | 'manual';
21+
xcodeSigningCertificate?: string;
22+
xcodeProvisioningProfile?: string;
1723
}
1824

1925
export async function buildCommand(
@@ -42,6 +48,12 @@ export async function buildCommand(
4248
androidreleasetype: buildOptions.androidreleasetype || config.android.buildOptions.releaseType || 'AAB',
4349
signingtype: buildOptions.signingtype || config.android.buildOptions.signingType || 'jarsigner',
4450
configuration: buildOptions.configuration || 'Release',
51+
xcodeTeamId: buildOptions.xcodeTeamId || config.ios.buildOptions.teamId,
52+
xcodeExportMethod:
53+
buildOptions.xcodeExportMethod || config.ios.buildOptions.exportMethod || XcodeExportMethod.AppStoreConnect,
54+
xcodeSigningType: buildOptions.xcodeSigningType || config.ios.buildOptions.xcodeSigningStyle || 'automatic',
55+
xcodeSigningCertificate: buildOptions.xcodeSigningCertificate || config.ios.buildOptions.signingCertificate,
56+
xcodeProvisioningProfile: buildOptions.xcodeProvisioningProfile || config.ios.buildOptions.provisioningProfile,
4557
};
4658

4759
try {

cli/src/tasks/copy.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ async function copyCapacitorConfig(config: Config, nativeAbsDir: string) {
154154

155155
await runTask(`Creating ${c.strong(nativeConfigFile)} in ${nativeRelDir}`, async () => {
156156
delete (config.app.extConfig.android as any)?.buildOptions;
157+
delete (config.app.extConfig.ios as any)?.buildOptions;
157158
await writeJSON(nativeConfigFilePath, config.app.extConfig, {
158159
spaces: '\t',
159160
});

0 commit comments

Comments
 (0)