@@ -38,6 +38,7 @@ internal struct CreateProvisioningProfileCommand: ParsableCommand {
38
38
)
39
39
case unableToCreateCSR( output: ShellOutput )
40
40
case unableToImportIntermediaryAppleCertificate( certificate: String , output: ShellOutput )
41
+ case profileNameMissing
41
42
42
43
var description : String {
43
44
switch self {
@@ -101,6 +102,8 @@ internal struct CreateProvisioningProfileCommand: ParsableCommand {
101
102
- Output: \( output. outputString)
102
103
- Error: \( output. errorString)
103
104
"""
105
+ case . profileNameMissing:
106
+ return " --auto-regenerate flag requires that you include a profile name using the argument --profile-name "
104
107
}
105
108
}
106
109
}
@@ -122,6 +125,7 @@ internal struct CreateProvisioningProfileCommand: ParsableCommand {
122
125
case intermediaryAppleCertificates = " intermediaryAppleCertificates "
123
126
case certificateSigningRequestSubject = " certificateSigningRequestSubject "
124
127
case profileName = " profileName "
128
+ case autoRegenerate = " autoRegenerate "
125
129
}
126
130
127
131
@Option ( help: " The key identifier of the private key (https://developer.apple.com/documentation/appstoreconnectapi/generating_tokens_for_api_requests) " )
@@ -182,6 +186,9 @@ internal struct CreateProvisioningProfileCommand: ParsableCommand {
182
186
""" )
183
187
internal var certificateSigningRequestSubject : String
184
188
189
+ @Flag ( help: " Defines if the profile should be regenerated in case it already exists (optional) " )
190
+ internal var autoRegenerate = false
191
+
185
192
private let files : Files
186
193
private let log : Log
187
194
private let shell : Shell
@@ -228,7 +235,8 @@ internal struct CreateProvisioningProfileCommand: ParsableCommand {
228
235
certificateSigningRequestSubject: String ,
229
236
bundleIdentifierName: String ? ,
230
237
platform: String ,
231
- profileName: String ?
238
+ profileName: String ? ,
239
+ autoRegenerate: Bool
232
240
) {
233
241
self . files = files
234
242
self . log = log
@@ -252,6 +260,7 @@ internal struct CreateProvisioningProfileCommand: ParsableCommand {
252
260
self . bundleIdentifierName = bundleIdentifierName
253
261
self . platform = platform
254
262
self . profileName = profileName
263
+ self . autoRegenerate = autoRegenerate
255
264
}
256
265
257
266
internal init ( from decoder: Decoder ) throws {
@@ -286,18 +295,36 @@ internal struct CreateProvisioningProfileCommand: ParsableCommand {
286
295
certificateSigningRequestSubject: try container. decode ( String . self, forKey: . certificateSigningRequestSubject) ,
287
296
bundleIdentifierName: try container. decodeIfPresent ( String . self, forKey: . bundleIdentifierName) ,
288
297
platform: try container. decode ( String . self, forKey: . platform) ,
289
- profileName: try container. decodeIfPresent ( String . self, forKey: . profileName)
298
+ profileName: try container. decodeIfPresent ( String . self, forKey: . profileName) ,
299
+ autoRegenerate: try container. decode ( Bool . self, forKey: . autoRegenerate)
290
300
)
291
301
}
292
302
293
303
internal func run( ) throws {
294
- let privateKey : Path = . init( privateKeyPath)
295
- let csr : Path = try createCSR ( privateKey: privateKey)
296
304
let jsonWebToken : String = try jsonWebTokenService. createToken (
297
305
keyIdentifier: keyIdentifier,
298
306
issuerID: issuerID,
299
307
secretKey: try files. read ( Path ( itunesConnectKeyPath) )
300
308
)
309
+ let deviceIDs : Set < String > = try iTunesConnectService. fetchITCDeviceIDs ( jsonWebToken: jsonWebToken)
310
+ guard let profileName, let profile = try ? fetchProvisioningProfile ( jsonWebToken: jsonWebToken, name: profileName)
311
+ else {
312
+ try createProvisioningProfile ( jsonWebToken: jsonWebToken, deviceIDs: deviceIDs)
313
+ return
314
+ }
315
+ guard autoRegenerate, shouldRegenerate ( profile: profile, with: deviceIDs)
316
+ else {
317
+ try save ( profile: profile)
318
+ log. append ( " The profile already exists " )
319
+ return
320
+ }
321
+ try deleteProvisioningProfile ( jsonWebToken: jsonWebToken, id: profile. id)
322
+ try createProvisioningProfile ( jsonWebToken: jsonWebToken, deviceIDs: deviceIDs)
323
+ }
324
+
325
+ private func createProvisioningProfile( jsonWebToken: String , deviceIDs: Set < String > ) throws {
326
+ let privateKey : Path = . init( privateKeyPath)
327
+ let csr : Path = try createCSR ( privateKey: privateKey)
301
328
let tuple : ( cer: Path , certificateId: String ) = try fetchOrCreateCertificate ( jsonWebToken: jsonWebToken, csr: csr)
302
329
let cer : Path = tuple. cer
303
330
let certificateId : String = tuple. certificateId
@@ -311,7 +338,6 @@ internal struct CreateProvisioningProfileCommand: ParsableCommand {
311
338
try importP12IdentityIntoKeychain ( p12Identity: p12Identity, identityPassword: identityPassword)
312
339
try importIntermediaryAppleCertificates ( )
313
340
try updateKeychainPartitionList ( )
314
- let deviceIDs : Set < String > = try iTunesConnectService. fetchITCDeviceIDs ( jsonWebToken: jsonWebToken)
315
341
let profileResponse : CreateProfileResponse = try iTunesConnectService. createProfile (
316
342
jsonWebToken: jsonWebToken,
317
343
bundleId: try iTunesConnectService. determineBundleIdITCId (
@@ -325,11 +351,7 @@ internal struct CreateProvisioningProfileCommand: ParsableCommand {
325
351
profileType: profileType,
326
352
profileName: profileName
327
353
)
328
- guard let profileData: Data = . init( base64Encoded: profileResponse. data. attributes. profileContent)
329
- else {
330
- throw Error . unableToBase64DecodeProfile ( name: profileResponse. data. attributes. name)
331
- }
332
- try files. write ( profileData, to: . init( outputPath) )
354
+ try save ( profile: profileResponse. data)
333
355
log. append ( profileResponse. data. id)
334
356
}
335
357
@@ -496,4 +518,44 @@ internal struct CreateProvisioningProfileCommand: ParsableCommand {
496
518
)
497
519
}
498
520
}
521
+
522
+ private func fetchProvisioningProfile( jsonWebToken: String , name: String ) throws -> ProfileResponseData ? {
523
+ try iTunesConnectService. fetchProvisioningProfile (
524
+ jsonWebToken: jsonWebToken,
525
+ name: name
526
+ ) . first ( where: { $0. attributes. name == name } )
527
+ }
528
+
529
+ private func deleteProvisioningProfile( jsonWebToken: String , id: String ) throws {
530
+ try iTunesConnectService. deleteProvisioningProfile (
531
+ jsonWebToken: jsonWebToken,
532
+ id: id
533
+ )
534
+ log. append ( " Deleted profile with id: \( id) " )
535
+ }
536
+
537
+ private func save( profile: ProfileResponseData ) throws {
538
+ guard let profileData: Data = . init( base64Encoded: profile. attributes. profileContent)
539
+ else {
540
+ throw Error . unableToBase64DecodeProfile ( name: profile. attributes. name)
541
+ }
542
+ try files. write ( profileData, to: . init( outputPath) )
543
+ }
544
+
545
+ private func shouldRegenerate( profile: ProfileResponseData , with deviceIDs: Set < String > ) -> Bool {
546
+ guard ProfileType ( rawValue: profileType) . usesDevices else { return false }
547
+ let profileDevices = Set ( profile. relationships. devices. data. map { $0. id } )
548
+ let shouldRegenerate = deviceIDs != profileDevices
549
+ if shouldRegenerate {
550
+ let missingDevices = deviceIDs. subtracting ( profileDevices)
551
+ log. append ( " The profile will be regenerated because it is missing the device(s): \( missingDevices. joined ( separator: " , " ) ) " )
552
+ }
553
+ return shouldRegenerate
554
+ }
555
+
556
+ mutating internal func validate( ) throws {
557
+ if autoRegenerate, profileName == nil {
558
+ throw Error . profileNameMissing
559
+ }
560
+ }
499
561
}
0 commit comments