this repo has no description
at main 95 lines 2.9 kB view raw
1import { spawn } from 'node:child_process' 2import { mkdir } from 'node:fs/promises' 3import { platform } from 'node:os' 4import { resolve } from 'node:path' 5 6export async function extractTarGz(archivePath: string, outputDir: string): Promise<void> { 7 await mkdir(outputDir, { recursive: true }) 8 9 const tarCmd = platform() === 'win32' ? 'tar.exe' : 'tar' 10 const tarArgs = ['-xzf', archivePath, '-C', outputDir] 11 12 await new Promise<void>((resolve, reject) => { 13 const proc = spawn(tarCmd, tarArgs, { stdio: ['ignore', 'ignore', 'ignore'] }) 14 15 proc.on('close', (code) => { 16 if (code === 0) { 17 resolve() 18 } else { 19 // Simple user-friendly error message regardless of platform 20 reject(new Error('Failed to extract tar.gz archive')) 21 } 22 }) 23 proc.on('error', reject) 24 }) 25} 26 27export async function extractZip(archivePath: string, outputDir: string): Promise<void> { 28 // Validate paths to prevent command injection 29 const resolvedArchivePath = resolve(archivePath) 30 const resolvedOutputDir = resolve(outputDir) 31 32 await mkdir(resolvedOutputDir, { recursive: true }) 33 34 // Try multiple zip extraction tools for better compatibility 35 const zipTools = [ 36 { 37 command: 'unzip', 38 args: ['-o', resolvedArchivePath, '-d', resolvedOutputDir], 39 }, 40 { 41 command: 'powershell', 42 args: [ 43 '-Command', 44 `Expand-Archive -Path '${resolvedArchivePath}' -DestinationPath '${resolvedOutputDir}' -Force`, 45 ], 46 }, 47 { 48 command: '7z', 49 args: ['x', resolvedArchivePath, `-o${resolvedOutputDir}`, '-y'], 50 }, 51 ] 52 53 for (const tool of zipTools) { 54 try { 55 await new Promise<void>((resolve, reject) => { 56 const proc = spawn(tool.command, tool.args, { stdio: ['ignore', 'pipe', 'pipe'] }) 57 let stderr = '' 58 59 proc.stderr?.on('data', (data) => { 60 stderr += data.toString() 61 }) 62 63 proc.on('close', (code) => { 64 if (code === 0) { 65 resolve() 66 } else { 67 reject(new Error(`${tool.command} failed with code ${code}: ${stderr}`)) 68 } 69 }) 70 71 proc.on('error', (error) => { 72 reject(new Error(`Failed to run ${tool.command}: ${error.message}`)) 73 }) 74 }) 75 76 // If we get here, extraction succeeded 77 return 78 } catch {} 79 } 80 81 // If all tools failed 82 throw new Error('Failed to extract zip file. Please ensure unzip, PowerShell, or 7z is available on your system.') 83} 84 85export async function extractArchive(archivePath: string, outputDir: string): Promise<void> { 86 await mkdir(outputDir, { recursive: true }) 87 88 if (archivePath.endsWith('.tar.gz') || archivePath.endsWith('.tgz')) { 89 await extractTarGz(archivePath, outputDir) 90 } else if (archivePath.endsWith('.zip')) { 91 await extractZip(archivePath, outputDir) 92 } else { 93 throw new Error(`Unsupported archive format: ${archivePath}`) 94 } 95}