11const fs = require ( "fs" ) ;
22const path = require ( "path" ) ;
33const os = require ( "os" ) ;
4- const { execSync } = require ( "child_process " ) ;
4+ const { safeExecSync } = require ( "./utils " ) ;
55const { extractName } = require ( "./config" ) ;
66
77const getPlatformInfo = ( ) => {
@@ -53,7 +53,7 @@ const getPlatformInfo = () => {
5353const getInstallCapabilities = ( ) => {
5454 const ebool = ( cmd ) => {
5555 try {
56- return execSync ( cmd , { stdio : "ignore" } ) . length > 0 ;
56+ return safeExecSync ( cmd , [ ] , { stdio : "ignore" } ) . length > 0 ;
5757 } catch ( e ) {
5858 return false ;
5959 }
@@ -165,30 +165,19 @@ const selectBestAsset = (assets, platformInfo, capabilities) => {
165165const extractArchive = ( filePath , outputDir , extension ) => {
166166 switch ( extension ) {
167167 case "tar.gz" :
168- execSync (
169- `tar -xzf ${ JSON . stringify ( filePath ) } --directory ${ JSON . stringify (
170- outputDir
171- ) } `
172- ) ;
168+ safeExecSync ( "tar" , [ "-xzf" , filePath , "--directory" , outputDir ] ) ;
173169 break ;
174170 case "tar.xz" :
175- execSync (
176- `tar -xJf ${ JSON . stringify ( filePath ) } --directory ${ JSON . stringify (
177- outputDir
178- ) } `
179- ) ;
171+ safeExecSync ( "tar" , [ "-xJf" , filePath , "--directory" , outputDir ] ) ;
172+ break ;
173+ case "tar.bz2" :
174+ safeExecSync ( "tar" , [ "-xjf" , filePath , "--directory" , outputDir ] ) ;
180175 break ;
181176 case "zip" :
182- execSync (
183- `unzip ${ JSON . stringify ( filePath ) } -d ${ JSON . stringify ( outputDir ) } `
184- ) ;
177+ safeExecSync ( "unzip" , [ filePath , "-d" , outputDir ] ) ;
185178 break ;
186179 case "tar.zst" :
187- execSync (
188- `unzstd < ${ JSON . stringify ( filePath ) } | tar -xf - --directory ${ JSON . stringify (
189- outputDir
190- ) } `
191- ) ;
180+ safeExecSync ( "sh" , [ "-c" , `unzstd < ${ JSON . stringify ( filePath ) } | tar -xf - --directory ${ JSON . stringify ( outputDir ) } ` ] ) ;
192181 break ;
193182 default :
194183 // For files without known extensions, just copy them
@@ -201,10 +190,8 @@ const getBinaries = (dir) => {
201190 // First, find .app directories (these are app bundles on macOS)
202191 let appDirectories = [ ] ;
203192 try {
204- appDirectories = execSync (
205- `find ${ JSON . stringify (
206- path . resolve ( dir )
207- ) } -maxdepth 2 -type d -name "*.app"`
193+ appDirectories = safeExecSync (
194+ "find" , [ path . resolve ( dir ) , "-maxdepth" , "2" , "-type" , "d" , "-name" , "*.app" ]
208195 )
209196 . toString ( )
210197 . split ( "\n" )
@@ -217,8 +204,8 @@ const getBinaries = (dir) => {
217204 }
218205
219206 // Then find all files
220- const allFiles = execSync (
221- ` find ${ JSON . stringify ( path . resolve ( dir ) ) } -type f ! -size 0`
207+ const allFiles = safeExecSync (
208+ " find" , [ path . resolve ( dir ) , " -type" , "f" , "!" , " -size" , "0" ]
222209 )
223210 . toString ( )
224211 . split ( "\n" )
@@ -247,7 +234,7 @@ const getBinaries = (dir) => {
247234 }
248235
249236 const filePath = path . resolve ( dir , f ) ;
250- const fileOutput = execSync ( ` file ${ JSON . stringify ( filePath ) } ` ) . toString ( ) ;
237+ const fileOutput = safeExecSync ( " file" , [ filePath ] ) . toString ( ) ;
251238
252239 // Check if it's an executable binary or script
253240 return fileOutput . includes ( "executable" ) ||
@@ -274,11 +261,12 @@ const getBinaries = (dir) => {
274261 if ( filename . includes ( "readme" ) || filename . includes ( "license" ) ||
275262 filename . includes ( "changelog" ) || filename . includes ( "copying" ) ||
276263 filename . includes ( "install" ) || filename . includes ( "makefile" ) ||
277- filename . includes ( ".md" ) || filename . includes ( ".txt" ) ||
278- filename . includes ( ".1" ) || filename . includes ( ".json" ) ||
279- filename . includes ( ".yaml" ) || filename . includes ( ".yml" ) ||
280- filename . includes ( ".toml" ) || filename . includes ( ".cfg" ) ||
281- filename . includes ( ".conf" ) || filename . includes ( ".ini" ) ) {
264+ filename . includes ( "notice" ) || filename . includes ( ".md" ) ||
265+ filename . includes ( ".txt" ) || filename . includes ( ".1" ) ||
266+ filename . includes ( ".json" ) || filename . includes ( ".yaml" ) ||
267+ filename . includes ( ".yml" ) || filename . includes ( ".toml" ) ||
268+ filename . includes ( ".cfg" ) || filename . includes ( ".conf" ) ||
269+ filename . includes ( ".ini" ) ) {
282270 return false ;
283271 }
284272
@@ -319,6 +307,7 @@ const selectBinaries = async (binaries, packageName, logger = null, yesFlag = fa
319307 return ! filename . includes ( 'readme' ) &&
320308 ! filename . includes ( 'license' ) &&
321309 ! filename . includes ( 'changelog' ) &&
310+ ! filename . includes ( 'notice' ) &&
322311 ! filename . includes ( '.md' ) &&
323312 ! filename . includes ( '.txt' ) &&
324313 ! filename . includes ( '.1' ) && // man pages
@@ -487,13 +476,20 @@ const installApp = async (appPath, outputDir, checkPathFn, logger = null, yesFla
487476 const dest = path . join ( "/Applications" , `${ cleaned } .app` ) ;
488477 await checkPathFn ( dest ) ;
489478
490- fs . cpSync ( path . join ( outputDir , appPath ) , dest , { recursive : true } ) ;
479+ // Use rsync to preserve all file attributes, permissions, and symlinks
480+ try {
481+ safeExecSync ( "rsync" , [ "-a" , "--copy-links" , "--protect-args" , `${ path . join ( outputDir , appPath ) } /` , dest ] ) ;
482+ } catch ( e ) {
483+ // Fallback to fs.cpSync if rsync fails
484+ if ( logger ) {
485+ logger . warn ( `rsync failed, falling back to fs.cpSync: ${ e . message } ` ) ;
486+ }
487+ fs . cpSync ( path . join ( outputDir , appPath ) , dest , { recursive : true , preserveTimestamps : true } ) ;
488+ }
491489
492490 // Code sign
493491 try {
494- execSync (
495- `codesign --sign - --force --deep ${ JSON . stringify ( dest ) } 2> /dev/null`
496- ) ;
492+ safeExecSync ( "codesign" , [ "--sign" , "-" , "--force" , "--deep" , dest ] , { stdio : "pipe" } ) ;
497493 if ( logger ) {
498494 logger . debug ( `Successfully codesigned ${ dest } ` ) ;
499495 }
@@ -505,9 +501,7 @@ const installApp = async (appPath, outputDir, checkPathFn, logger = null, yesFla
505501
506502 // Remove quarantine
507503 try {
508- execSync (
509- `xattr -rd com.apple.quarantine ${ JSON . stringify ( dest ) } 2> /dev/null`
510- ) ;
504+ safeExecSync ( "xattr" , [ "-rd" , "com.apple.quarantine" , dest ] , { stdio : "pipe" } ) ;
511505 if ( logger ) {
512506 logger . debug ( `Removed quarantine from ${ dest } ` ) ;
513507 }
@@ -521,7 +515,7 @@ const installApp = async (appPath, outputDir, checkPathFn, logger = null, yesFla
521515} ;
522516
523517const installPkg = ( pkgPath ) => {
524- execSync ( ` sudo installer -pkg ${ JSON . stringify ( pkgPath ) } -target /` ) ;
518+ safeExecSync ( " sudo" , [ " installer" , " -pkg" , pkgPath , " -target" , "/" ] ) ;
525519 return [ "System-wide package installation" ] ;
526520} ;
527521
@@ -533,11 +527,7 @@ const mountDMG = (dmgPath, mountPoint, logger = null) => {
533527 }
534528 fs . mkdirSync ( mountPoint , { recursive : true } ) ;
535529
536- execSync (
537- `hdiutil attach ${ JSON . stringify (
538- dmgPath
539- ) } -nobrowse -mountpoint ${ JSON . stringify ( mountPoint ) } `
540- ) ;
530+ safeExecSync ( "hdiutil" , [ "attach" , dmgPath , "-nobrowse" , "-mountpoint" , mountPoint ] ) ;
541531
542532 if ( ! fs . existsSync ( mountPoint ) ) {
543533 throw new Error (
@@ -555,7 +545,7 @@ const mountDMG = (dmgPath, mountPoint, logger = null) => {
555545
556546const ejectDMG = ( mountPoint , logger = null ) => {
557547 try {
558- execSync ( ` hdiutil eject ${ JSON . stringify ( mountPoint ) } ` ) ;
548+ safeExecSync ( " hdiutil" , [ " eject" , mountPoint ] ) ;
559549 if ( logger ) {
560550 logger . debug ( `Successfully ejected DMG at: ${ mountPoint } ` ) ;
561551 }
@@ -567,7 +557,7 @@ const ejectDMG = (mountPoint, logger = null) => {
567557 }
568558 // Try force eject as fallback
569559 try {
570- execSync ( ` hdiutil eject ${ JSON . stringify ( mountPoint ) } -force` ) ;
560+ safeExecSync ( " hdiutil" , [ " eject" , mountPoint , " -force" ] ) ;
571561 if ( logger ) {
572562 logger . debug ( `Force ejected DMG at: ${ mountPoint } ` ) ;
573563 }
@@ -600,7 +590,7 @@ const installBinaries = async (
600590 // Don't try to chmod files on mounted volumes (like DMGs)
601591 if ( ! isMountedVolume ) {
602592 try {
603- execSync ( ` chmod +x ${ JSON . stringify ( binaryPath ) } ` ) ;
593+ safeExecSync ( " chmod" , [ "+x" , binaryPath ] ) ;
604594 } catch ( e ) {
605595 if ( logger ) {
606596 logger . warn ( `Failed to make ${ binary } executable: ${ e . message } ` ) ;
@@ -612,7 +602,7 @@ const installBinaries = async (
612602
613603 // Make sure the copied file is executable
614604 try {
615- execSync ( ` chmod +x ${ JSON . stringify ( dest ) } ` ) ;
605+ safeExecSync ( " chmod" , [ "+x" , dest ] ) ;
616606 } catch ( e ) {
617607 if ( logger ) {
618608 logger . warn ( `Failed to make copied binary executable: ${ e . message } ` ) ;
@@ -626,7 +616,7 @@ const installBinaries = async (
626616} ;
627617
628618const installDeb = ( debPath ) => {
629- execSync ( ` sudo dpkg -i ${ JSON . stringify ( debPath ) } ` ) ;
619+ safeExecSync ( " sudo" , [ " dpkg" , "-i" , debPath ] ) ;
630620 return [ "System-wide deb installation" ] ;
631621} ;
632622
0 commit comments