Need to ZIP an entire directory using Node.js

node.jsZip

node.js Problem Overview


I need to zip an entire directory using Node.js. I'm currently using node-zip and each time the process runs it generates an invalid ZIP file (as you can see from this Github issue).

Is there another, better, Node.js option that will allow me to ZIP up a directory?

EDIT: I ended up using archiver

writeZip = function(dir,name) {
var zip = new JSZip(),
    code = zip.folder(dir),
    output = zip.generate(),
    filename = ['jsd-',name,'.zip'].join('');

fs.writeFileSync(baseDir + filename, output);
console.log('creating ' + filename);
};

sample value for parameters:

dir = /tmp/jsd-<randomstring>/
name = <randomstring>

UPDATE: For those asking about the implementation I used, here's a link to my downloader:

node.js Solutions


Solution 1 - node.js

I ended up using archiver lib. Works great.

Example

var file_system = require('fs');
var archiver = require('archiver');

var output = file_system.createWriteStream('target.zip');
var archive = archiver('zip');

output.on('close', function () {
	console.log(archive.pointer() + ' total bytes');
	console.log('archiver has been finalized and the output file descriptor has closed.');
});

archive.on('error', function(err){
	throw err;
});

archive.pipe(output);

// append files from a sub-directory, putting its contents at the root of archive
archive.directory(source_dir, false);

// append files from a sub-directory and naming it `new-subdir` within the archive
archive.directory('subdir/', 'new-subdir');

archive.finalize();

Solution 2 - node.js

I'm not going to show something new, just wanted to summarise the solutions above for those who like Promises as much as I do .

const archiver = require('archiver');

/**
 * @param {String} sourceDir: /some/folder/to/compress
 * @param {String} outPath: /path/to/created.zip
 * @returns {Promise}
 */
function zipDirectory(sourceDir, outPath) {
  const archive = archiver('zip', { zlib: { level: 9 }});
  const stream = fs.createWriteStream(outPath);

  return new Promise((resolve, reject) => {
    archive
      .directory(sourceDir, false)
      .on('error', err => reject(err))
      .pipe(stream)
    ;

    stream.on('close', () => resolve());
    archive.finalize();
  });
}

Hope it will help someone 爛

Solution 3 - node.js

Use Node's native child_process api to accomplish this.

No need for third party libs. Two lines of code.

const child_process = require("child_process");
child_process.execSync(`zip -r <DESIRED_NAME_OF_ZIP_FILE_HERE> *`, {
  cwd: <PATH_TO_FOLDER_YOU_WANT_ZIPPED_HERE>
});

The example above showcases the synchronous API. You can also use child_process.exec(path, options, callback) if you want async behavior. There are a lot more options you can specify other than cwd to further fine-tune your request.


Read this section only if you don't have the ZIP utility:

This question is specifically asks about the zip utility for archiving/compression purposes. Therefore, this example assumes you have the zip utility installed on your system. For completeness sakes, some operating systems may not have utility installed by default. In that case you have at least three options:

  1. Work with the archiving/compression utility that is native to your platform

    Replace the shell command in the above node.js code with code from your system. For example, linux distros usually come with tar/gzip utilities:

    tar -cfz <DESIRED_NAME_OF_ZIP_FILE_HERE> <PATH_TO_FOLDER_YOU_WANT_ZIPPED_HERE>.

    This is a nice option as you don't need to install anything new onto your operating system or manage another dependency (kind of the whole point for this answer).

  2. Obtain the zip binary for your OS/distribution (zip is fairly ubiquitous).

    Do a quick google search or go to the creator, Info-ZIP's, website for downloadable binaries.

    The ZIP utility is tried and tested for decades, it's fairly ubiquitous and it's a safe choice.

  3. Use a third party module (of which there are plenty on NPM).

    I don't prefer this option. However, if you don't really care to understand the native methods and introducing a new dependency is a non-issue, this is also a valid option.

Solution 4 - node.js

This is another library which zips the folder in one line : zip-local

var zipper = require('zip-local');

zipper.sync.zip("./hello/world/").compress().save("pack.zip");

Solution 5 - node.js

Archive.bulk is now deprecated, the new method to be used for this is glob:

var fileName =   'zipOutput.zip'
var fileOutput = fs.createWriteStream(fileName);

fileOutput.on('close', function () {
    console.log(archive.pointer() + ' total bytes');
    console.log('archiver has been finalized and the output file descriptor has closed.');
});

archive.pipe(fileOutput);
archive.glob("../dist/**/*"); //some glob pattern here
archive.glob("../dist/.htaccess"); //another glob pattern
// add as many as you like
archive.on('error', function(err){
    throw err;
});
archive.finalize();

Solution 6 - node.js

To include all files and directories:

archive.bulk([
  {
    expand: true,
    cwd: "temp/freewheel-bvi-120",
    src: ["**/*"],
    dot: true
  }
]);

It uses node-glob(https://github.com/isaacs/node-glob) underneath, so any matching expression compatible with that will work.

Solution 7 - node.js

To pipe the result to the response object (scenarios where there is a need to download the zip rather than store locally)

 archive.pipe(res);

Sam's hints for accessing the content of the directory worked for me.

src: ["**/*"]

Solution 8 - node.js

I have found this small library that encapsulates what you need.

npm install zip-a-folder

const zip-a-folder = require('zip-a-folder');
await zip-a-folder.zip('/path/to/the/folder', '/path/to/archive.zip');

https://www.npmjs.com/package/zip-a-folder

Solution 9 - node.js

Adm-zip has problems just compressing an existing archive https://github.com/cthackers/adm-zip/issues/64 as well as corruption with compressing binary files.

I've also ran into compression corruption issues with node-zip https://github.com/daraosn/node-zip/issues/4

node-archiver is the only one that seems to work well to compress but it doesn't have any uncompress functionality.

Solution 10 - node.js

Since archiver is not compatible with the new version of webpack for a long time, I recommend using zip-lib.

var zl = require("zip-lib");
 
zl.archiveFolder("path/to/folder", "path/to/target.zip").then(function () {
    console.log("done");
}, function (err) {
    console.log(err);
});

Solution 11 - node.js

As today, I'm using AdmZip and works great:

import AdmZip = require('adm-zip');
export async function archiveFile() {
  try {
    const zip = new AdmZip();
    const outputDir = "/output_file_dir.zip";
    zip.addLocalFolder("./yourFolder")
    zip.writeZip(outputDir);
  } catch (e) {
    console.log(`Something went wrong ${e}`);
  }
}

Solution 12 - node.js

You can try in a simple way:

Install zip-dir :

npm install zip-dir

and use it

var zipdir = require('zip-dir');

let foldername =  src_path.split('/').pop() 
    zipdir(<<src_path>>, { saveTo: 'demo.zip' }, function (err, buffer) {

    });

Solution 13 - node.js

I ended up wrapping archiver to emulate JSZip, as refactoring through my project woult take too much effort. I understand Archiver might not be the best choice, but here you go.

// USAGE:
const zip=JSZipStream.to(myFileLocation)
    .onDone(()=>{})
    .onError(()=>{});

zip.file('something.txt','My content');
zip.folder('myfolder').file('something-inFolder.txt','My content');
zip.finalize();

// NodeJS file content:
    var fs = require('fs');
    var path = require('path');
    var archiver = require('archiver');

  function zipper(archive, settings) {
	return {
		output: null,
		streamToFile(dir) {
			const output = fs.createWriteStream(dir);
			this.output = output;
			archive.pipe(output);

			return this;
		},
		file(location, content) {
			if (settings.location) {
				location = path.join(settings.location, location);
			}
			archive.append(content, { name: location });
			return this;
		},
		folder(location) {
			if (settings.location) {
				location = path.join(settings.location, location);
			}
			return zipper(archive, { location: location });
		},
		finalize() {
			archive.finalize();
			return this;
		},
		onDone(method) {
			this.output.on('close', method);
			return this;
		},
		onError(method) {
			this.output.on('error', method);
			return this;
		}
	};
}

exports.JSzipStream = {
	to(destination) {
		console.log('stream to',destination)
		const archive = archiver('zip', {
			zlib: { level: 9 } // Sets the compression level.
		});
		return zipper(archive, {}).streamToFile(destination);
	}
};

Solution 14 - node.js

import ... from answer based on https://stackoverflow.com/a/51518100

To zip single directory

import archiver from 'archiver';
import fs from 'fs';

export default zipDirectory;

/**
 * From: https://stackoverflow.com/a/51518100
 * @param {String} sourceDir: /some/folder/to/compress
 * @param {String} outPath: /path/to/created.zip
 * @returns {Promise}
 */
function zipDirectory(sourceDir, outPath) {
  const archive = archiver('zip', { zlib: { level: 9 }});
  const stream = fs.createWriteStream(outPath);

  return new Promise((resolve, reject) => {
    archive
      .directory(sourceDir, false)
      .on('error', err => reject(err))
      .pipe(stream)
    ;

    stream.on('close', () => resolve());
    archive.finalize();
  });
}

To zip multiple directories:

import archiver from 'archiver';
import fs from 'fs';

export default zipDirectories;

/**
 * Adapted from: https://stackoverflow.com/a/51518100
 * @param {String} sourceDir: /some/folder/to/compress
 * @param {String} outPath: /path/to/created.zip
 * @returns {Promise}
 */
function zipDirectories(sourceDirs, outPath) {
  const archive = archiver('zip', { zlib: { level: 9 }});
  const stream = fs.createWriteStream(outPath);

  return new Promise((resolve, reject) => {
    var result = archive;
    sourceDirs.forEach(sourceDir => {
      result = result.directory(sourceDir, false);
    });
    result
      .on('error', err => reject(err))
      .pipe(stream)
    ;

    stream.on('close', () => resolve());
    archive.finalize();
  });
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestioncommadelimitedView Question on Stackoverflow
Solution 1 - node.jscommadelimitedView Answer on Stackoverflow
Solution 2 - node.jsD.DimitriogloView Answer on Stackoverflow
Solution 3 - node.jsGovind RaiView Answer on Stackoverflow
Solution 4 - node.jsDreamsView Answer on Stackoverflow
Solution 5 - node.jscaiocpricci2View Answer on Stackoverflow
Solution 6 - node.jsSam GhaderyanView Answer on Stackoverflow
Solution 7 - node.jsRafView Answer on Stackoverflow
Solution 8 - node.jsOndrej KvasnovskyView Answer on Stackoverflow
Solution 9 - node.jsXiaoxinView Answer on Stackoverflow
Solution 10 - node.jstaoView Answer on Stackoverflow
Solution 11 - node.jshfcView Answer on Stackoverflow
Solution 12 - node.jsHarsha BiyaniView Answer on Stackoverflow
Solution 13 - node.jsuser672770View Answer on Stackoverflow
Solution 14 - node.jsJesus is LordView Answer on Stackoverflow