@@ -36,13 +36,10 @@ extension SwiftPackageRegistryTool {
36
36
@OptionGroup ( visibility: . hidden)
37
37
var globalOptions : GlobalOptions
38
38
39
- @Option ( name : [ . customLong ( " id " ) , . customLong ( " package-id " ) ] , help : " The package identifier. " )
39
+ @Argument ( help : . init ( " The package identifier. " , valueName : " package-id " ) )
40
40
var packageIdentity : PackageIdentity
41
41
42
- @Option (
43
- name: [ . customLong( " version " ) , . customLong( " package-version " ) ] ,
44
- help: " The package release version being created. "
45
- )
42
+ @Argument ( help: . init( " The package release version being created. " , valueName: " package-version " ) )
46
43
var packageVersion : Version
47
44
48
45
@Option ( name: [ . customLong( " url " ) , . customLong( " registry-url " ) ] , help: " The registry URL. " )
@@ -74,7 +71,7 @@ extension SwiftPackageRegistryTool {
74
71
@Option ( help: " The path to the signing certificate (DER-encoded). " )
75
72
var certificatePath : AbsolutePath ?
76
73
77
- @Option ( help: " Dry run only; prepare the archive and sign it but do not publish to the registry. " )
74
+ @Flag ( help: " Dry run only; prepare the archive and sign it but do not publish to the registry. " )
78
75
var dryRun : Bool = false
79
76
80
77
func run( _ swiftTool: SwiftTool ) async throws {
@@ -133,39 +130,18 @@ extension SwiftPackageRegistryTool {
133
130
)
134
131
135
132
// step 1: publishing configuration
136
- let publishConfiguration = PublishConfiguration (
137
- metadataLocation: self . customMetadataPath
138
- . flatMap { . external( $0) } ??
139
- . sourceTree( packageDirectory. appending ( component: Self . metadataFilename) ) ,
140
- signing: . init(
141
- required: self . signingIdentity != nil || self . privateKeyPath != nil ,
142
- format: self . signatureFormat,
143
- signingIdentity: self . signingIdentity,
144
- privateKeyPath: self . privateKeyPath,
145
- certificatePath: self . certificatePath
146
- )
147
- )
133
+ let metadataLocation : MetadataLocation = self . customMetadataPath
134
+ . flatMap { . external( $0) } ??
135
+ . sourceTree( packageDirectory. appending ( component: Self . metadataFilename) )
136
+ let signingRequired = self . signingIdentity != nil || self . privateKeyPath != nil || self
137
+ . certificatePath != nil
148
138
149
- guard localFileSystem. exists ( publishConfiguration . metadataLocation. path) else {
139
+ guard localFileSystem. exists ( metadataLocation. path) else {
150
140
throw StringError (
151
- " Publishing to ' \( registryURL) ' requires metadata file but none was found at ' \( publishConfiguration . metadataLocation) '. "
141
+ " Publishing to ' \( registryURL) ' requires metadata file but none was found at ' \( metadataLocation) '. "
152
142
)
153
143
}
154
144
155
- if publishConfiguration. signing. privateKeyPath != nil {
156
- guard publishConfiguration. signing. certificatePath != nil else {
157
- throw StringError (
158
- " Both 'privateKeyPath' and 'certificatePath' are required when one of them is set. "
159
- )
160
- }
161
- } else {
162
- guard publishConfiguration. signing. certificatePath == nil else {
163
- throw StringError (
164
- " Both 'privateKeyPath' and 'certificatePath' are required when one of them is set. "
165
- )
166
- }
167
- }
168
-
169
145
// step 2: generate source archive for the package release
170
146
swiftTool. observabilityScope. emit ( info: " archiving the source at ' \( packageDirectory) ' " )
171
147
let archivePath = try self . archiveSource (
@@ -179,14 +155,41 @@ extension SwiftPackageRegistryTool {
179
155
180
156
// step 3: sign the source archive if needed
181
157
var signature : Data ? = . none
182
- if publishConfiguration. signing. required {
158
+ if signingRequired {
159
+ // compute signing mode
160
+ let signingMode : PackageArchiveSigner . SigningMode
161
+ switch (
162
+ self . signingIdentity,
163
+ self . certificatePath,
164
+ self . privateKeyPath
165
+ ) {
166
+ case ( . none, . some, . none) :
167
+ throw StringError (
168
+ " Both 'private-key-path' and 'certificate-path' are required when one of them is set. "
169
+ )
170
+ case ( . none, . none, . some) :
171
+ throw StringError (
172
+ " Both 'private-key-path' and 'certificate-path' are required when one of them is set. "
173
+ )
174
+ case ( . none, . some( let certificatePath) , . some( let privateKeyPath) ) :
175
+ signingMode = . certificate( certificate: certificatePath, privateKey: privateKeyPath)
176
+ case ( . some( let signingStoreLabel) , . none, . none) :
177
+ signingMode = . identityStore( signingStoreLabel)
178
+ default :
179
+ throw StringError (
180
+ " Either 'signing-identity' or 'private-key-path' (together with 'certificate-path') must be provided. "
181
+ )
182
+ }
183
+
183
184
swiftTool. observabilityScope. emit ( info: " signing the archive at ' \( archivePath) ' " )
184
- signature = try await self . sign (
185
- packageIdentity : self . packageIdentity,
186
- packageVersion : self . packageVersion ,
185
+ let signaturePath = workingDirectory
186
+ . appending ( component : " \( self . packageIdentity) - \( self . packageVersion ) .sig " )
187
+ signature = try await PackageArchiveSigner . sign (
187
188
archivePath: archivePath,
188
- configuration: publishConfiguration. signing,
189
- workingDirectory: workingDirectory,
189
+ signaturePath: signaturePath,
190
+ mode: signingMode,
191
+ signatureFormat: self . signatureFormat,
192
+ fileSystem: localFileSystem,
190
193
observabilityScope: swiftTool. observabilityScope
191
194
)
192
195
}
@@ -201,7 +204,6 @@ extension SwiftPackageRegistryTool {
201
204
202
205
swiftTool. observabilityScope
203
206
. emit ( info: " publishing ' \( self . packageIdentity) ' archive at ' \( archivePath) ' to ' \( registryURL) ' " )
204
- // TODO: handle signature
205
207
let result = try await registryClient. publish (
206
208
registryURL: registryURL,
207
209
packageIdentity: self . packageIdentity,
@@ -260,89 +262,21 @@ extension SwiftPackageRegistryTool {
260
262
261
263
return archivePath
262
264
}
263
-
264
- func sign(
265
- packageIdentity: PackageIdentity ,
266
- packageVersion: Version ,
267
- archivePath: AbsolutePath ,
268
- configuration: PublishConfiguration . Signing ,
269
- workingDirectory: AbsolutePath ,
270
- observabilityScope: ObservabilityScope
271
- ) async throws -> Data {
272
- let archiveData = try Data ( localFileSystem. readFileContents ( archivePath) . contents)
273
-
274
- var signingIdentity : SigningIdentity ?
275
- if let signingIdentityLabel = configuration. signingIdentity {
276
- let signingIdentityStore = SigningIdentityStore ( observabilityScope: observabilityScope)
277
- let matches = try await signingIdentityStore. find ( by: signingIdentityLabel)
278
- guard !matches. isEmpty else {
279
- throw StringError ( " ' \( signingIdentityLabel) ' not found in the system identity store. " )
280
- }
281
- // TODO: let user choose if there is more than one match?
282
- signingIdentity = matches. first
283
- } else if let privateKeyPath = configuration. privateKeyPath,
284
- let certificatePath = configuration. certificatePath
285
- {
286
- let certificateData = try Data ( localFileSystem. readFileContents ( certificatePath) . contents)
287
- let privateKeyData = try Data ( localFileSystem. readFileContents ( privateKeyPath) . contents)
288
- signingIdentity = SwiftSigningIdentity (
289
- certificate: Certificate ( derEncoded: certificateData) ,
290
- privateKey: try configuration. format. privateKey ( derRepresentation: privateKeyData)
291
- )
292
- }
293
-
294
- guard let signingIdentity = signingIdentity else {
295
- throw StringError ( " Cannot sign archive without signing identity. " )
296
- }
297
-
298
- let signature = try await SignatureProvider . sign (
299
- archiveData,
300
- with: signingIdentity,
301
- in: configuration. format,
302
- observabilityScope: observabilityScope
303
- )
304
-
305
- let signaturePath = workingDirectory. appending ( component: " \( packageIdentity) - \( packageVersion) .sig " )
306
- try localFileSystem. writeFileContents ( signaturePath) { stream in
307
- stream. write ( signature)
308
- }
309
-
310
- return signature
311
- }
312
265
}
313
266
}
314
267
315
- struct PublishConfiguration {
316
- let metadataLocation : MetadataLocation
317
- let signing : Signing
318
-
319
- enum MetadataLocation {
320
- case sourceTree( AbsolutePath )
321
- case external( AbsolutePath )
268
+ enum MetadataLocation {
269
+ case sourceTree( AbsolutePath )
270
+ case external( AbsolutePath )
322
271
323
- var path : AbsolutePath {
324
- switch self {
325
- case . sourceTree( let path) :
326
- return path
327
- case . external( let path) :
328
- return path
329
- }
272
+ var path : AbsolutePath {
273
+ switch self {
274
+ case . sourceTree( let path) :
275
+ return path
276
+ case . external( let path) :
277
+ return path
330
278
}
331
279
}
332
-
333
- struct Signing {
334
- let required : Bool
335
- let format : SignatureFormat
336
- var signingIdentity : String ?
337
- var privateKeyPath : AbsolutePath ?
338
- var certificatePath : AbsolutePath ?
339
- }
340
- }
341
-
342
- extension SignatureFormat : ExpressibleByArgument {
343
- public init ? ( argument: String ) {
344
- self . init ( rawValue: argument. lowercased ( ) )
345
- }
346
280
}
347
281
348
282
// TODO: migrate registry client to async
0 commit comments