ایجاد و باز کردن فایل Zip در Node.js

ایجاد و باز کردن فایل Zip در Node.js

انتشار:

 

 

برای نمونه کد‌های این مطلب از TypeScript و ESM استفاده کردم و نسخه کامل کد‌ها داخل گیت‌هاب در دسترسه.

 

فشرده‌سازی و ایجاد فایل Zip

برای فشرده‌سازی فایل‌ها از پکیج archiver استفاده می‌کنیم که پکیج معروفی برای ایجاد فایل Zip تو Node.js هستش.

پکیج رو ایمپورت می‌کنیم و یه نمونه (instance) با کانفیگ پیش فرض و فرمت zip ازش میسازیم. حالا تنها کاری که نیازه انجام بدیم مشخص کردن فایل‌ها است که میشه مستقیم آدرس یک فایل رو بدیم، آدرس یه پوشه بدیم و کل فایل‌های پوشه رو برامون اضافه کنه یا اینکه از glob استفاده کنیم تا یه پترن و الگو رو برای آدرس و نوع فایل‌ها مشخص کنیم.

نمونه کد:

import archiver from "archiver";

const archive = archiver('zip');

archive.file("./exampleDir/example.png", { name: "example.png" });

archive.directory('dirPath/', 'inZipDirPath');
archive.directory('dirPath/', false); // add to root level of archived file

archive.glob('file*.txt');

await archive.finalize(); // شروع عملیات زیپ کردن

 

تا اینجا، فایل زیپ رو درست میکنه اما جایی ذخیره نمیکنه. برای اینکه فایلش ذخیره بشه از file stream خود node استفاده می‌کنیم و با دستور pipe خروجی stream آرشیو رو به file system وصل می‌کنیم:

import fs from "node:fs";

const outputFileStream = fs.createWriteStream("./temp/compressed.zip");
const archive = archiver('zip');

archive.pipe(outputFileStream);

archive.file("./exampleDir/example.png", { name: "example.png" });

archive.on("finish", () => {
    console.info("Zip process finished"); // اینجا پروسه زیپ کردن تموم شده و کافیه استریم رو ببندیم
    archive.end(); // بستن استریم
});

outputFileStream.on("finish", () => {
    console.info("File saved"); // اینجا استریم بسته و فایل ذخیره شده
});

await archive.finalize();

 

ایجاد فایل Zip با پسورد

برای اینکه به فایلمون رمز اضافه کنیم باید از یه متد رمزنگاری استفاده کنیم. پکیج archiver توسعه پذیره و با استفاده از پکیج archiver-zip-encrypted میتونیم بهش فرمت جدید اضافه کنیم تا بتونیم از رمزنگاری استفاده کنیم.

متاسفانه برای این پکیج data-typeها تعریف نشدن و هرچند بهترین راه حل نیست اما با اضافه کردن declare module 'archiver-zip-encrypted'; میشه ارورهای تایپ اسکریپت رو برطرف کرد.

بعد از نصب پکیج کافیه فرمت جدید رو به archiver معرفی کنیم و موقع ساختن instance بهش بگیم که از چه فرمتی میخوایم:

import archiverZipEncrypted from "archiver-zip-encrypted";
import archiver, { type ArchiverOptions, type Format } from "archiver";

const format = "zip-encrypted" as Format; // نام فرمت جدیدی که میخوایم اضافه کنیم

// ثبت پکیج جدید با نام فرمت - به ازای هر پروسس نود یکبار این ثبت رو انجام بدید
archiver.registerFormat(format, archiverZipEncrypted);

const archive = archiver(format, { // بجای زیپ از فرمت جدید استفاده می‌کنیم
    encryptionMethod: "zip20", // نوع رمزگذاری - این نوع مرسومه اما امن نیست
    password: "t3st" // رمز
} as ArchiverOptions);
/*
ادامه
*/

 

خواندن فایل Zip

برای خوندن فایل زیپ و فشرده شده از پکیج unzipper استفاده می‌کنیم. این ماژول امکان خوندن لیست و مقدار فایل‌های داخل zip رو از طریق آدرس یا بافر فایل فشرده شده به ما میده. برای مثال اگر بخوایم فقط یک فایل رو بخونیم:

import unzipper from "unzipper";

const rootDirectory = await unzipper.Open.file("./file.zip"); // خوندن لیست کامل فایل‌های داخل زیپ

const file = rootDirectory.files.find(file => file.path === 'package.json'); // پیدا کردن فایل مورد نظر

const data = await file.buffer("password"); // مقدار رمز - اگر رمز نداشته باشه میتونید پاس ندید

 

اگر بخوایم کل فایل رو اکسترکت کنیم و رمز نداشته باشه:

import fs from "node:fs";
import unzipper from "unzipper";

const outputFileStream = fs.createReadStream("./temp/compressed.zip");

outputFileStream.on('close', () => {
    console.info('Done'); // اتمام عملیات
});

outputFileStream.pipe(unzipper.Extract({
    path: "./temp/extracted",
}));

 

ولی اگر رمزگذاری شده باشن باید کد رو شخصی‌سازی کنیم. با فرض اینکه همه‌ی رمز‌ها یکی باشن، ۱- اطلاعات کل فایل‌ها و مسیرها رو میخونیم، ۲- پوشه‌ها و فایل‌ها رو فیلتر می‌کنیم و به ترتیب، اول ۳- پوشه‌ها رو ایجاد می‌کنیم، ۴- بعدش فایل‌ها رو تک تک می‌خونیم و تو مسیر مورد نظر ذخیره می‌کنیم:

import fs from "node:fs/promises";
import path from "node:path";
import unzipper from "unzipper";

const password = "t3st";
const outputDirectory = "./temp/extracted";
const rootDirectory = await unzipper.Open.file("./temp/compressed-encrypted.zip");

const directories = rootDirectory.files.filter(item => item.type === 'Directory');
const files = rootDirectory.files.filter(item => item.type === 'File');

for (const directory of directories) {
    await fs.mkdir(path.join(outputDirectory, directory.path), { recursive: true });
}

for (const file of files) {
    const data = await file.buffer(password);
    await fs.writeFile(path.join(outputDirectory, file.path), data);
}