zip-a-folder

Zip/Tar a complete folder or a glob list into a zip/tgz file

zip-a-folder downloads zip-a-folder version zip-a-folder license

zip-a-folderSimilar Packages:
Npm Package Weekly Downloads Trend
3 Years
🌟 Show real-time usage chart on zip-a-folder's README.md, just copy the code below.
## Usage Trend
[![Usage Trend of zip-a-folder](https://npm-compare.com/img/npm-trend/THREE_YEARS/zip-a-folder.png)](https://npm-compare.com/zip-a-folder#timeRange=THREE_YEARS)
Cumulative GitHub Star Trend
🌟 Show GitHub stars trend chart on zip-a-folder's README.md, just copy the code below.
## GitHub Stars Trend
[![GitHub Stars Trend of zip-a-folder](https://npm-compare.com/img/github-trend/zip-a-folder.png)](https://npm-compare.com/zip-a-folder)
Stat Detail
Package
Downloads
Stars
Size
Issues
Publish
License
zip-a-folder189,8707563 kB53 months agoMIT
README for zip-a-folder

NPM

CircleCI Codacy Badge Codacy Coverage Known Vulnerabilities

zip-a-folder

A fast, dependency-free ZIP/TAR/TGZ creation library using native Node.js compression (zlib), supporting:

  • ZIP archives (with optional ZIP64)
  • TAR archives (optionally gzipped)
  • Globs (single or comma-separated)
  • Parallel directory scanning (statConcurrency)
  • Custom write streams
  • Compression presets (high, medium, uncompressed)
  • Fine-grained zlib/gzip control

Everything is implemented natively without JS zip/tar libraries.


⚠️ Incompatible Changes

Version 2

Added support for comma-separated glob lists.
This may change the behavior for cases previously interpreted as “folder only”.

Version 3

Dual-module support (CJS + ESM).

Version 3.1

Added support for destPath to control the internal path layout of created archives.

Version 4 (current)

A major rewrite using:

  • Fully native ZIP writer (no dependencies)
  • Native TAR + gzip writer
  • ZIP64 support for large archives
  • Parallel statting (statConcurrency)
  • Strict internal path normalization mirroring classic zip-a-folder behavior
  • Native glob handling via glob

📦 Installation

npm install zip-a-folder

🚀 Usage

Create a ZIP file

import { zip } from 'zip-a-folder';

await zip('/path/to/folder', '/path/to/archive.zip');

Create a GZIP-compressed TAR file (.tgz)

import { tar } from 'zip-a-folder';

await tar('/path/to/folder', '/path/to/archive.tgz');

🎚 Compression Handling

Supported compression levels:

COMPRESSION_LEVEL.high          // highest compression (default)
COMPRESSION_LEVEL.medium        // balanced
COMPRESSION_LEVEL.uncompressed  // STORE for zip, no-gzip for tar

Example:

import { zip, COMPRESSION_LEVEL } from 'zip-a-folder';

await zip('/path/to/folder', '/path/to/archive.zip', {
    compression: COMPRESSION_LEVEL.medium
});

✨ ZIP Options

OptionTypeDescription
commentstringZIP file comment
forceLocalTimebooleanUse local timestamps instead of UTC
forceZip64booleanAlways include ZIP64 headers
namePrependSlashbooleanPrefix all ZIP entry names with /
storebooleanForce STORE method (no compression)
zlibZlibOptionsPassed directly to zlib.deflateRaw
statConcurrencynumberParallel stat workers (default: 4)
destPathstringPrefix inside the archive (>=3.1)
customWriteStreamWriteStreamManually handle output

Example:

await zip('/dir', '/archive.zip', {
    comment: "Created by zip-a-folder",
    forceZip64: true,
    namePrependSlash: true,
    store: false,
    statConcurrency: 16,
    zlib: { level: 9 }
});

📦 TAR / TGZ Options

OptionTypeDescription
gzipbooleanEnable gzip compression
gzipOptionsZlibOptionsPassed to zlib.createGzip
statConcurrencynumberParallel stat workers

Example:

await tar('/dir', '/archive.tgz', {
    gzip: true,
    gzipOptions: { level: 6 }
});

🔧 Custom Write Streams

ZIP and TAR can be written into any stream. If customWriteStream is used, the targetFilePath can be empty or undefined.

import fs from 'fs';
import { zip } from 'zip-a-folder';

const ws = fs.createWriteStream('/tmp/output.zip');
await zip('/path/to/folder', undefined, { customWriteStream: ws });

Important: zip-a-folder does not validate custom streams. You must ensure:

  • parent directory exists
  • you’re not writing into the source directory (to avoid recursion)

🔍 Glob Handling

The first parameter may be:

  • A path to a directory
  • A single glob
  • A comma-separated list of globs

Example:

await zip('**/*.json', '/archive.zip');
await zip('**/*.json, **/*.txt', '/archive2.zip');

If no files match, zip-a-folder throws:

Error: No glob match found

🗂 Destination Path Handling (destPath)

Adds a prefix inside the archive:

await zip('data/', '/archive.zip', { destPath: 'data/' });

Resulting ZIP layout:

data/file1.txt
data/subdir/file2.txt

Directory Root Inclusion Semantics

When passing a directory path as the first argument (e.g. zip('/path/to/folder', '/archive.zip')), the archive by default contains the contents of that directory at the archive root (i.e. you will see the files inside folder/, not a top-level folder/ directory itself).

Include the directory itself

If you want the archive to unpack into the named folder (so the top level of the archive contains folder/), set destPath to that folder name plus a trailing slash:

await zip('/path/to/folder', '/archive.zip', {
  destPath: 'folder/'
});

Result layout:

folder/file1.txt
folder/sub/file2.txt

Summary

  • Default: directory contents only (no enclosing folder)
  • To include the folder: use destPath: '<dirname>/'

This applies equally to tar().


🎯 Native Implementation Notes (New in v4)

  • ZIP and TAR are written using pure Node.js (zlib, raw buffering)
  • ZIP64 support included
  • File system scanning performed with a parallel stat queue
  • Globs handled via the standardized glob package
  • Archive layout matches the original zip-a-folder for compatibility
  • ZIP writer supports dependency-free deflate and manual header construction
  • TAR writer produces POSIX ustar format with proper 512-byte block alignment

🧪 Running Tests

Tests are written in Jest:

npm test

A coverage report is included:

npm test -- --coverage

❤️ Thanks

Special thanks to contributors:

  • @sole – initial work
  • @YOONBYEONGIN
  • @Wunschik
  • @ratbeard
  • @Xotabu4
  • @dallenbaldwin
  • @wiralegawa
  • @karan-gaur
  • @malthe

Additional thanks to everyone helping shape the native rewrite.