Files
ccs/src/cliproxy/binary/tar-extractor.ts
T
kaitranntt d3c94fe6a2 refactor(cliproxy): modularize binary-manager into binary/ directory
- extract types, version-checker, lifecycle, updater, installer

- extract downloader (verifier), extractors (tar, zip), version-cache

- slim binary-manager.ts from 1080 to 137 lines (87% reduction)

- add barrel export at binary/index.ts
2025-12-19 12:34:48 -05:00

99 lines
3.4 KiB
TypeScript

/**
* Tar.gz Archive Extractor
* Handles extraction of tar.gz archives using Node.js built-in modules.
*/
import * as fs from 'fs';
import * as path from 'path';
import * as zlib from 'zlib';
import { getExecutableName, getArchiveBinaryName } from '../platform-detector';
/**
* Extract tar.gz archive using Node.js built-in modules
*/
export function extractTarGz(archivePath: string, destDir: string, verbose = false): Promise<void> {
return new Promise((resolve, reject) => {
const execName = getExecutableName();
const archiveBinaryName = getArchiveBinaryName();
const gunzip = zlib.createGunzip();
const input = fs.createReadStream(archivePath);
let headerBuffer = Buffer.alloc(0);
let currentFile: { name: string; size: number } | null = null;
let bytesRead = 0;
let fileBuffer = Buffer.alloc(0);
const processData = (data: Buffer) => {
headerBuffer = Buffer.concat([headerBuffer, data]);
while (headerBuffer.length >= 512) {
if (!currentFile) {
const header = headerBuffer.subarray(0, 512);
headerBuffer = headerBuffer.subarray(512);
if (header.every((b) => b === 0)) return;
let name = '';
for (let i = 0; i < 100 && header[i] !== 0; i++) {
name += String.fromCharCode(header[i]);
}
let sizeStr = '';
for (let i = 124; i < 136 && header[i] !== 0; i++) {
sizeStr += String.fromCharCode(header[i]);
}
const size = parseInt(sizeStr.trim(), 8) || 0;
if (name && size > 0) {
const baseName = path.basename(name);
if (
baseName === execName ||
baseName === archiveBinaryName ||
baseName === 'cli-proxy-api'
) {
currentFile = { name: baseName, size };
fileBuffer = Buffer.alloc(0);
bytesRead = 0;
} else {
const paddedSize = Math.ceil(size / 512) * 512;
if (headerBuffer.length >= paddedSize) {
headerBuffer = headerBuffer.subarray(paddedSize);
} else {
currentFile = { name: '', size: paddedSize };
bytesRead = 0;
}
}
}
} else {
const remaining = currentFile.size - bytesRead;
const chunk = headerBuffer.subarray(0, Math.min(remaining, headerBuffer.length));
headerBuffer = headerBuffer.subarray(chunk.length);
if (currentFile.name) fileBuffer = Buffer.concat([fileBuffer, chunk]);
bytesRead += chunk.length;
if (bytesRead >= currentFile.size) {
if (currentFile.name) {
const destPath = path.join(destDir, execName);
fs.writeFileSync(destPath, fileBuffer);
if (verbose)
console.error(`[cliproxy] Extracted: ${currentFile.name} -> ${destPath}`);
}
const paddedSize = Math.ceil(currentFile.size / 512) * 512;
const padding = paddedSize - currentFile.size;
if (headerBuffer.length >= padding) headerBuffer = headerBuffer.subarray(padding);
currentFile = null;
fileBuffer = Buffer.alloc(0);
}
}
}
};
input.pipe(gunzip);
gunzip.on('data', processData);
gunzip.on('end', resolve);
gunzip.on('error', reject);
input.on('error', reject);
});
}