this repo has no description
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}