@@ -11,103 +11,102 @@ struct QuickPkg: AsyncParsableCommand {
1111 discussion: """
1212 Quickly build a package from an installed application, a disk image file,
1313 or zip/xip archive with an enclosed application bundle.
14-
14+
1515 The tool extracts the application name and version to name the resulting pkg file.
1616 """ ,
1717 version: quickpkgVersion
1818 )
19-
19+
2020 // MARK: - Arguments
21-
21+
2222 @Argument ( help: " Path to the installer item (.app, .dmg, .zip, or .xip) " )
2323 var itemPath : String
24-
24+
2525 // MARK: - Installation Scripts
26-
26+
2727 @Option ( help: " Path to a folder with scripts " )
2828 var scripts : String ?
29-
29+
3030 @Option ( name: [ . long, . customLong( " pre " ) ] , help: " Path to the preinstall script " )
3131 var preinstall : String ?
32-
32+
3333 @Option ( name: [ . long, . customLong( " post " ) ] , help: " Path to the postinstall script " )
3434 var postinstall : String ?
35-
35+
3636 // MARK: - Package Options
37-
37+
3838 @Option ( name: . customLong( " install-location " ) , help: " Install location (default: /Applications) " )
3939 var installLocation : String = " /Applications "
40-
40+
4141 @Option ( help: " Ownership setting: recommended, preserve, or preserve-other " )
4242 var ownership : Ownership ?
43-
43+
4444 @Option ( name: [ . customLong( " output " ) , . customLong( " out " ) , . short] ,
4545 help: " Output path (supports {name}, {version}, {identifier} placeholders) " )
4646 var output : String ?
47-
47+
4848 // MARK: - Flags
49-
49+
5050 @Flag ( inversion: . prefixedNo, help: " Clean up temp files (default: true) " )
5151 var clean : Bool = true
52-
52+
5353 @Flag ( inversion: . prefixedNo, help: " Make package relocatable (default: false) " )
5454 var relocatable : Bool = false
55-
55+
5656 // MARK: - Signing Options
57-
57+
5858 @Option ( help: " Signing identity for the package " )
5959 var sign : String ?
60-
60+
6161 @Option ( help: " Keychain to search for signing identity " )
6262 var keychain : String ?
63-
63+
6464 @Option ( help: " Intermediate certificate to embed " )
6565 var cert : String ?
66-
66+
6767 // MARK: - Verbosity
68-
68+
6969 @Flag ( name: . shortAndLong, help: " Increase verbosity (-v, -vv, or -vvv) " )
7070 var verbose : Int
71-
71+
7272 // MARK: - Run
73-
73+
7474 mutating func run( ) async throws {
7575 let logger = Logger ( verbosity: verbose)
76-
76+
7777 // Normalize path
7878 var path = itemPath
7979 if path. hasPrefix ( " ~ " ) {
8080 path = NSString ( string: path) . expandingTildeInPath
8181 }
8282 path = ( path as NSString ) . standardizingPath
83-
83+
8484 // Remove trailing slash
8585 if path. hasSuffix ( " / " ) {
8686 path = String ( path. dropLast ( ) )
8787 }
88-
88+
8989 let url = URL ( filePath: path)
90-
90+
9191 // Determine input type
9292 guard let inputType = InputType . from ( path: path) else {
9393 throw QuickPkgError . unsupportedExtension ( url. pathExtension)
9494 }
95-
95+
9696 logger. log ( " Processing \( inputType. rawValue) : \( path) " , level: 1 )
97-
97+
9898 // Create temp directory for working files
9999 let tempDir = try TempDirectory ( )
100100 defer {
101101 if clean {
102102 tempDir. cleanup ( )
103103 }
104104 }
105-
106-
105+
107106 let executor = ShellExecutor ( logger: logger)
108107 let dmgManager = DMGManager ( executor: executor, logger: logger)
109108 let archiveExtractor = ArchiveExtractor ( executor: executor, logger: logger)
110-
109+
111110 // Clean up mounted DMGs when done
112111 defer {
113112 if clean {
@@ -116,7 +115,7 @@ struct QuickPkg: AsyncParsableCommand {
116115 }
117116 }
118117 }
119-
118+
120119 // Find the application
121120 let appURL : URL
122121 switch inputType {
@@ -125,7 +124,7 @@ struct QuickPkg: AsyncParsableCommand {
125124 throw QuickPkgError . fileNotFound ( path)
126125 }
127126 appURL = url
128-
127+
129128 case . dmg:
130129 guard FileManager . default. fileExists ( atPath: path) else {
131130 throw QuickPkgError . fileNotFound ( path)
@@ -139,7 +138,7 @@ struct QuickPkg: AsyncParsableCommand {
139138 throw QuickPkgError . multipleApplicationsFound ( apps. map ( \. path) )
140139 }
141140 appURL = apps [ 0 ]
142-
141+
143142 case . zip:
144143 guard FileManager . default. fileExists ( atPath: path) else {
145144 throw QuickPkgError . fileNotFound ( path)
@@ -155,7 +154,7 @@ struct QuickPkg: AsyncParsableCommand {
155154 throw QuickPkgError . multipleApplicationsFound ( apps. map ( \. path) )
156155 }
157156 appURL = apps [ 0 ]
158-
157+
159158 case . xip:
160159 guard FileManager . default. fileExists ( atPath: path) else {
161160 throw QuickPkgError . fileNotFound ( path)
@@ -172,40 +171,40 @@ struct QuickPkg: AsyncParsableCommand {
172171 }
173172 appURL = apps [ 0 ]
174173 }
175-
174+
176175 logger. log ( " Found application: \( appURL. path) " , level: 1 )
177-
176+
178177 // Copy app to payload directory (needed for dmg/zip/xip, and for apps to avoid modifying original)
179178 let payloadDir = tempDir. path. appendingPathComponent ( " payload " )
180179 try FileManager . default. createDirectory ( at: payloadDir, withIntermediateDirectories: true )
181180 let payloadAppURL = payloadDir. appendingPathComponent ( appURL. lastPathComponent)
182181 try FileManager . default. copyItem ( at: appURL, to: payloadAppURL)
183-
182+
184183 // Extract metadata from app
185184 let metadata = try AppMetadata ( from: payloadAppURL)
186185 logger. log ( " Name: \( metadata. name) , ID: \( metadata. identifier) , Version: \( metadata. version) " , level: 1 )
187-
186+
188187 // Prepare scripts if needed
189- var scriptsDir : URL ? = nil
188+ var scriptsDir : URL ?
190189 if let scriptsPath = scripts {
191190 let scriptsURL = URL ( fileURLWithPath: scriptsPath)
192191 guard FileManager . default. fileExists ( atPath: scriptsPath) else {
193192 throw QuickPkgError . scriptNotFound ( scriptsPath)
194193 }
195194 scriptsDir = scriptsURL
196195 }
197-
196+
198197 if preinstall != nil || postinstall != nil {
199198 let tmpScriptsDir = tempDir. path. appendingPathComponent ( " scripts " )
200199 try FileManager . default. createDirectory ( at: tmpScriptsDir, withIntermediateDirectories: true )
201-
200+
202201 // Copy existing scripts folder if provided
203202 if let existingScripts = scriptsDir {
204203 for item in try FileManager . default. contentsOfDirectory ( at: existingScripts, includingPropertiesForKeys: nil ) {
205204 try FileManager . default. copyItem ( at: item, to: tmpScriptsDir. appendingPathComponent ( item. lastPathComponent) )
206205 }
207206 }
208-
207+
209208 // Add preinstall script
210209 if let preinstallPath = preinstall {
211210 let preinstallURL = URL ( fileURLWithPath: preinstallPath)
@@ -220,7 +219,7 @@ struct QuickPkg: AsyncParsableCommand {
220219 try FileManager . default. setAttributes ( [ . posixPermissions: 0o755 ] , ofItemAtPath: destURL. path)
221220 logger. log ( " Copied preinstall script to \( destURL. path) " , level: 1 )
222221 }
223-
222+
224223 // Add postinstall script
225224 if let postinstallPath = postinstall {
226225 let postinstallURL = URL ( fileURLWithPath: postinstallPath)
@@ -235,21 +234,21 @@ struct QuickPkg: AsyncParsableCommand {
235234 try FileManager . default. setAttributes ( [ . posixPermissions: 0o755 ] , ofItemAtPath: destURL. path)
236235 logger. log ( " Copied postinstall script to \( destURL. path) " , level: 1 )
237236 }
238-
237+
239238 scriptsDir = tmpScriptsDir
240239 }
241-
240+
242241 // Build the package
243242 let packageBuilder = PackageBuilder ( executor: executor, logger: logger)
244-
243+
245244 // Determine output path
246245 let outputPath = determineOutputPath (
247246 output: output,
248247 name: metadata. name,
249248 version: metadata. version,
250249 identifier: metadata. identifier
251250 )
252-
251+
253252 try await packageBuilder. build (
254253 payloadDir: payloadDir,
255254 outputPath: outputPath,
@@ -264,16 +263,16 @@ struct QuickPkg: AsyncParsableCommand {
264263 cert: cert,
265264 tempDir: tempDir. path
266265 )
267-
266+
268267 print ( outputPath)
269268 }
270-
269+
271270 // MARK: - Helpers
272-
271+
273272 private func findApplications( in directories: [ URL ] ) -> [ URL ] {
274273 var apps : [ URL ] = [ ]
275274 let fm = FileManager . default
276-
275+
277276 for dir in directories {
278277 guard let contents = try ? fm. contentsOfDirectory ( at: dir, includingPropertiesForKeys: nil ) else {
279278 continue
@@ -284,14 +283,14 @@ struct QuickPkg: AsyncParsableCommand {
284283 }
285284 }
286285 }
287-
286+
288287 return apps
289288 }
290-
289+
291290 private func determineOutputPath( output: String ? , name: String , version: String , identifier: String ) -> String {
292291 let defaultName = " {name}-{version}.pkg "
293292 var path : String
294-
293+
295294 if let output = output {
296295 if FileManager . default. fileExists ( atPath: output) ,
297296 ( try ? FileManager . default. attributesOfItem ( atPath: output) [ . type] as? FileAttributeType ) == . typeDirectory {
@@ -302,18 +301,18 @@ struct QuickPkg: AsyncParsableCommand {
302301 } else {
303302 path = defaultName
304303 }
305-
304+
306305 // Replace placeholders
307306 let noSpaceName = name. replacingOccurrences ( of: " " , with: " " )
308307 path = path. replacingOccurrences ( of: " {name} " , with: noSpaceName)
309308 path = path. replacingOccurrences ( of: " {version} " , with: version)
310309 path = path. replacingOccurrences ( of: " {identifier} " , with: identifier)
311-
310+
312311 // Ensure .pkg extension
313312 if !path. hasSuffix ( " .pkg " ) {
314313 path += " .pkg "
315314 }
316-
315+
317316 return path
318317 }
319318}
0 commit comments