A fast, dependency-free ZIP/TAR/TGZ creation library using native Node.js compression (zlib), supporting:
statConcurrency)high, medium, uncompressed)Everything is implemented natively without JS zip/tar libraries.
Added support for comma-separated glob lists.
This may change the behavior for cases previously interpreted as “folder only”.
Dual-module support (CJS + ESM).
Added support for destPath to control the internal path layout of created archives.
A major rewrite using:
statConcurrency)globnpm install zip-a-folder
import { zip } from 'zip-a-folder';
await zip('/path/to/folder', '/path/to/archive.zip');
.tgz)import { tar } from 'zip-a-folder';
await tar('/path/to/folder', '/path/to/archive.tgz');
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
});
| Option | Type | Description |
|---|---|---|
comment | string | ZIP file comment |
forceLocalTime | boolean | Use local timestamps instead of UTC |
forceZip64 | boolean | Always include ZIP64 headers |
namePrependSlash | boolean | Prefix all ZIP entry names with / |
store | boolean | Force STORE method (no compression) |
zlib | ZlibOptions | Passed directly to zlib.deflateRaw |
statConcurrency | number | Parallel stat workers (default: 4) |
destPath | string | Prefix inside the archive (>=3.1) |
customWriteStream | WriteStream | Manually 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 }
});
| Option | Type | Description |
|---|---|---|
gzip | boolean | Enable gzip compression |
gzipOptions | ZlibOptions | Passed to zlib.createGzip |
statConcurrency | number | Parallel stat workers |
Example:
await tar('/dir', '/archive.tgz', {
gzip: true,
gzipOptions: { level: 6 }
});
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:
The first parameter may be:
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
destPath)Adds a prefix inside the archive:
await zip('data/', '/archive.zip', { destPath: 'data/' });
Resulting ZIP layout:
data/file1.txt
data/subdir/file2.txt
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).
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
destPath: '<dirname>/'This applies equally to tar().
zlib, raw buffering)Tests are written in Jest:
npm test
A coverage report is included:
npm test -- --coverage
Special thanks to contributors:
Additional thanks to everyone helping shape the native rewrite.