vanta-auditor-tui
Version:
Beautiful terminal UI for exporting Vanta audit evidence with ZIP support and progress tracking
106 lines • 3.43 kB
JavaScript
import archiver from "archiver";
import fs from "node:fs";
import path from "node:path";
export async function createZipArchive(options, onProgress) {
const { sourceDir, outputPath, compressionLevel = 9, verbose } = options;
// Ensure output directory exists
const outputDir = path.dirname(outputPath);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// Count files first for progress tracking
const files = await countFilesRecursive(sourceDir);
const totalFiles = files.length;
let filesProcessed = 0;
let bytesProcessed = 0;
// Preparing ZIP archive
onProgress?.({
phase: "preparing",
bytesProcessed: 0,
filesProcessed: 0,
totalFiles,
message: "Preparing archive..."
});
return new Promise((resolve, reject) => {
const output = fs.createWriteStream(outputPath);
const archive = archiver("zip", {
zlib: { level: compressionLevel }
});
// Handle errors
archive.on("error", (err) => {
// Archive error occurred
reject(err);
});
// Track progress
archive.on("entry", (entry) => {
filesProcessed++;
if (entry.stats && entry.stats.size) {
bytesProcessed += entry.stats.size;
}
onProgress?.({
phase: "archiving",
bytesProcessed,
filesProcessed,
totalFiles,
message: `Archiving ${entry.name}`
});
// Archive progress update
});
// Handle completion
output.on("close", () => {
const stats = fs.statSync(outputPath);
onProgress?.({
phase: "complete",
bytesProcessed: stats.size,
filesProcessed: totalFiles,
totalFiles,
message: "Archive complete"
});
// Archive completed
resolve({
outputPath,
size: stats.size,
fileCount: totalFiles
});
});
// Pipe archive to output file
archive.pipe(output);
// Add entire directory to archive
archive.directory(sourceDir, false);
// Finalize the archive
onProgress?.({
phase: "finalizing",
bytesProcessed,
filesProcessed: totalFiles,
totalFiles,
message: "Finalizing archive..."
});
archive.finalize();
});
}
async function countFilesRecursive(dir) {
const files = [];
async function walk(currentPath) {
const entries = await fs.promises.readdir(currentPath, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(currentPath, entry.name);
if (entry.isDirectory()) {
await walk(fullPath);
}
else if (entry.isFile()) {
files.push(fullPath);
}
}
}
await walk(dir);
return files;
}
function formatBytes(bytes) {
if (bytes === 0)
return "0 B";
const k = 1024;
const sizes = ["B", "KB", "MB", "GB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`;
}
//# sourceMappingURL=archiver.js.map