diff --git a/.gitignore b/.gitignore index a72b52e..abe10fa 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ lib-cov *.out *.pid *.gz +*.idea pids logs diff --git a/.travis.yml b/.travis.yml index 20fd86b..0a50ff7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,3 @@ language: node_js node_js: - - 0.10 + - '4.0' diff --git a/README.md b/README.md index 398087b..4e5059a 100644 --- a/README.md +++ b/README.md @@ -56,10 +56,7 @@ been saved to disk. * `saveTo` A path to save the buffer to. * `filter` A function that is called for all items to determine whether or not they should be added to the zip buffer. Function is called with the `fullPath` and a `stats` object ([fs.Stats](http://nodejs.org/api/fs.html#fs_class_fs_stats)). Return true to add the item; false otherwise. To include files within directories, directories must also pass this filter. * `each` A function that is called everytime a file or directory is added to the zip. - -## TODO - -* Add an option to not add empty directories if there are no valid children inside +* `noEmptyDirectories` disallow empty directories from being zipped. If the root directory is empty an error will be thrown. ## license diff --git a/index.js b/index.js index 3fa84a0..f5a702d 100644 --- a/index.js +++ b/index.js @@ -11,6 +11,9 @@ var Zip = require('jszip'); // Limiting the number of files read at the same time var maxOpenFiles = 500; +//Error Message +var ROOTDIR_CANNOT_BE_EMPTY = 'Cannot have an empty root directory'; + module.exports = function zipWrite (rootDir, options, callback) { if (!callback) { callback = options; @@ -20,7 +23,9 @@ module.exports = function zipWrite (rootDir, options, callback) { options = options || {}; zipBuffer(rootDir, options, function (err, buffer) { - if (options.saveTo) { + if(err){ + callback(err, null); + } else if (options.saveTo) { fs.writeFile(options.saveTo, buffer, { encoding: 'binary' }, function (err) { callback(err, buffer); }); @@ -36,6 +41,8 @@ function zipBuffer (rootDir, options, callback) { // Resolve the path so we can remove trailing slash if provided rootDir = path.resolve(rootDir); + folders['rootDir'] = rootDir; + folders[rootDir] = zip; dive(rootDir, function (err) { @@ -50,7 +57,21 @@ function zipBuffer (rootDir, options, callback) { function dive (dir, callback) { fs.readdir(dir, function (err, files) { if (err) return callback(err); - if (!files.length) return callback(); + if(!files.length){ + if(folders.rootDir === dir && options.noEmptyDirectories){ + return callback(ROOTDIR_CANNOT_BE_EMPTY) + } else if(options.noEmptyDirectories){ + var rootDir = dir.substring(0, dir.lastIndexOf('/')); + var baseName = path.basename(dir); + var parentZip = folders[rootDir]; + if(parentZip){ + parentZip.remove(baseName); + } + return callback(); + } else { + return callback(); + } + } var count = files.length; files.forEach(function (file) { var fullPath = path.resolve(dir, file); diff --git a/package.json b/package.json index 74c692f..5ecd3fc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zip-dir", - "version": "1.0.2", + "version": "1.1.0", "description": "Zips up directories into buffers or saves zipped files to disk", "main": "index.js", "scripts": { diff --git a/test/fixtures/emptySubFolderSampleZip/file.txt b/test/fixtures/emptySubFolderSampleZip/file.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/zip-dir.test.js b/test/zip-dir.test.js index 6c927dc..bfe0d5e 100644 --- a/test/zip-dir.test.js +++ b/test/zip-dir.test.js @@ -8,6 +8,7 @@ var chai = require("chai"); var expect = chai.expect; var sampleZipPath = path.join(__dirname, "fixtures/sampleZip"); +var emptySubFolderSampleZipPath = path.join(__dirname, "fixtures/emptySubFolderSampleZip"); var xpiPath = path.join(__dirname, "my.xpi"); var outputPath = path.join(__dirname, "myxpi/"); var emptyDirPath = path.join(sampleZipPath, "emptyDir"); @@ -53,7 +54,7 @@ describe("zip-dir", function () { describe("writes a zip file", function () { beforeEach(function (done) { addEmpty(function () { - zipAndUnzip({ saveTo: xpiPath }, done); + zipAndUnzip(sampleZipPath, {saveTo: xpiPath}, done); }); }); afterEach(cleanUp); @@ -66,7 +67,9 @@ describe("zip-dir", function () { "dir/file3.json", "dir/deepDir/deeperDir/file4.json" ]; - files.forEach(compareFiles); + files.forEach(function(file){ + compareFiles(file, sampleZipPath); + }); done(); }); @@ -83,14 +86,16 @@ describe("zip-dir", function () { afterEach(cleanUp); it("filters out by file name, fs.Stat", function (done) { - zipAndUnzip({ saveTo: xpiPath, filter: jsonOnly }, function () { + zipAndUnzip(sampleZipPath, {saveTo: xpiPath, filter: jsonOnly}, function () { var files = [ "file1.json", "dir/file2.json", "dir/file3.json", "dir/deepDir/deeperDir/file4.json" ]; - files.forEach(compareFiles); + files.forEach(function(file){ + compareFiles(file, sampleZipPath); + }); fs.stat(path.join(outputPath, "tiny.gif"), function (err, stat) { expect(err).to.be.ok; @@ -98,18 +103,20 @@ describe("zip-dir", function () { }); }); - function jsonOnly (name, stat) { + function jsonOnly(name, stat) { return /\.json$/.test(name) || stat.isDirectory(); } }); it("filtering out directories keeps it shallow", function (done) { - zipAndUnzip({ saveTo: xpiPath, filter: noDirs }, function () { + zipAndUnzip(sampleZipPath, {saveTo: xpiPath, filter: noDirs}, function () { var files = [ "file1.json", "tiny.gif" ]; - files.forEach(compareFiles); + files.forEach(function(file){ + compareFiles(file, sampleZipPath); + }); fs.stat(path.join(outputPath, "dir"), function (err, stat) { expect(err).to.be.ok; @@ -117,7 +124,7 @@ describe("zip-dir", function () { }); }); - function noDirs (name, stat) { + function noDirs(name, stat) { return !stat.isDirectory(); } }); @@ -128,10 +135,12 @@ describe("zip-dir", function () { it("calls `each` with each path added to zip", function (done) { var paths = []; - function each (p) { + + function each(p) { paths.push(p); } - zipDir(sampleZipPath, { each: each }, function (err, buffer) { + + zipDir(sampleZipPath, {each: each}, function (err, buffer) { var files = [ "file1.json", "tiny.gif", @@ -141,7 +150,9 @@ describe("zip-dir", function () { "dir/deepDir", "dir/deepDir/deeperDir", "dir/deepDir/deeperDir/file4.json" - ].map(function (p) { return path.join.apply(path, [sampleZipPath].concat(p.split("/"))); }); + ].map(function (p) { + return path.join.apply(path, [sampleZipPath].concat(p.split("/"))); + }); files.forEach(function (p) { expect(paths.indexOf(p)).to.not.equal(-1); @@ -152,12 +163,19 @@ describe("zip-dir", function () { done(); }); }); - + it("calls `each`, ignoring unadded files", function (done) { var paths = []; - function each (p) { paths.push(p); } - function filter (p) { return /\.json$/.test(p) || fs.statSync(p).isDirectory(); } - zipDir(sampleZipPath, { each: each, filter: filter }, function (err, buffer) { + + function each(p) { + paths.push(p); + } + + function filter(p) { + return /\.json$/.test(p) || fs.statSync(p).isDirectory(); + } + + zipDir(sampleZipPath, {each: each, filter: filter}, function (err, buffer) { var files = [ "file1.json", "dir/file2.json", @@ -166,7 +184,9 @@ describe("zip-dir", function () { "dir/deepDir", "dir/deepDir/deeperDir", "dir/deepDir/deeperDir/file4.json" - ].map(function (p) { return path.join.apply(path, [sampleZipPath].concat(p.split("/"))); }); + ].map(function (p) { + return path.join.apply(path, [sampleZipPath].concat(p.split("/"))); + }); files.forEach(function (p) { expect(paths.indexOf(p)).to.not.equal(-1); @@ -178,16 +198,43 @@ describe("zip-dir", function () { }); }); }); + + describe("`noEmptyDirectories` option", function () { + afterEach(cleanUp); + it("calls `noEmptyDirectories` with an empty root directory", function (done) { + var ERROR_MSG = 'Cannot have an empty root directory'; + addEmpty(function() { + zipDir(emptyDirPath, {noEmptyDirectories: true}, function (err, buffer) { + expect(err).to.be.equal(ERROR_MSG); + expect(buffer).to.be.a('null'); + done(); + }); + }); + }); + + it("calls `noEmptyDirectories` with an empty sub directory", function (done) { + this.timeout(3000); + var files = [ + "file.txt" + ]; + zipAndUnzip(emptySubFolderSampleZipPath, {saveTo: xpiPath, noEmptyDirectories: true}, function(){ + files.forEach(function(file){ + compareFiles(file, emptySubFolderSampleZipPath); + done(); + }); + }) + }); + }); }); -function compareFiles (file) { - var zipBuffer = fs.readFileSync(path.join(sampleZipPath, file)); +function compareFiles (file, inputPath) { + var zipBuffer = fs.readFileSync(path.join(inputPath, file)); var fileBuffer = fs.readFileSync(path.join(outputPath, file)); expect(bufferEqual(zipBuffer, fileBuffer)).to.be.ok; } -function zipAndUnzip (options, done) { - zipDir(sampleZipPath, options, function (err, buffer) { +function zipAndUnzip (inputPath, options, done) { + zipDir(inputPath, options, function (err, buffer) { if (err) throw err; fs.createReadStream(xpiPath) .pipe(unzip.Extract({ path: outputPath }))