From 252cb50ee5c157e5a522f653536c3d19768a63ef Mon Sep 17 00:00:00 2001 From: Robert Brisita Date: Mon, 2 Aug 2021 12:16:11 -0400 Subject: [PATCH] Adding zip arguments to control compression level and inclusion of base folder and updating tests to support Node v16.0+. --- README.md | 13 ++++++-- index.js | 80 ++++++++++++++++++++++++++--------------------- package.json | 6 +++- test/unzip.js | 46 +++++++++++++++------------ test/zip.js | 86 +++++++++++++++++++++++++++++++++++++++++---------- 5 files changed, 156 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 484b578..fe64f93 100644 --- a/README.md +++ b/README.md @@ -30,12 +30,19 @@ zip.zipSync(inPath, outPath) ## api -### `zip.zip(inPath, outPath, [callback])` +### `zip.zip(inPath, outPath, [callback], [args])` Zip the folder at `inPath` and save it to a .zip file at `outPath`. If a `callback` -is passed, then it is called with an `Error` or `null`. +is passed, then it is called with an `Error` or `null`. The optional `args` object: +```js +{ + level: number, + incBase: boolean +} +``` +controls compression level (restricted to 0-9) and inclusion of the base folder of given inPath argument. Compression level 0 is no compression, 6 is default, and 9 is optimal compression; optimal is default for Windows. -### `zip.zipSync(inPath, outPath)` +### `zip.zipSync(inPath, outPath, [args])` Sync version of `zip.zip`. diff --git a/index.js b/index.js index 52fb9ad..4f31413 100644 --- a/index.js +++ b/index.js @@ -6,12 +6,12 @@ module.exports = { unzipSync: unzipSync } -var cp = require('child_process') -var fs = require('fs') -var os = require('os') -var path = require('path') +const cp = require('child_process') +const fs = require('fs') +const os = require('os') +const path = require('path') -function zip (inPath, outPath, cb) { +function zip (inPath, outPath, cb, args = { level: 6, incBase: false }) { if (!cb) cb = function () {} if (process.platform === 'win32') { fs.stat(inPath, function (err, stats) { @@ -19,11 +19,11 @@ function zip (inPath, outPath, cb) { if (stats.isFile()) { copyToTemp() } else { - doZip() + prepare() } }) } else { - doZip() + prepare() } // Windows zip command cannot zip files, only directories. So move the file into @@ -31,59 +31,58 @@ function zip (inPath, outPath, cb) { function copyToTemp () { fs.readFile(inPath, function (err, inFile) { if (err) return cb(err) - var tmpPath = path.join(os.tmpdir(), 'cross-zip-' + Date.now()) + const tmpPath = path.join(os.tmpdir(), 'cross-zip-' + Date.now()) fs.mkdir(tmpPath, function (err) { if (err) return cb(err) fs.writeFile(path.join(tmpPath, path.basename(inPath)), inFile, function (err) { if (err) return cb(err) inPath = tmpPath - doZip() + prepare() }) }) }) } // Windows zip command does not overwrite existing files. So do it manually first. - function doZip () { + function prepare () { if (process.platform === 'win32') { - fs.rmdir(outPath, { recursive: true, maxRetries: 3 }, doZip2) - } else { - doZip2() + fs.rmdirSync(outPath, { recursive: true, maxRetries: 3 }) } + execute(args) } - function doZip2 () { - var opts = { + function execute (args) { + const opts = { cwd: path.dirname(inPath), maxBuffer: Infinity } - cp.execFile(getZipCommand(), getZipArgs(inPath, outPath), opts, function (err) { + cp.execFile(getZipCommand(), getZipArgs(inPath, outPath, args), opts, function (err) { cb(err) }) } } -function zipSync (inPath, outPath) { +function zipSync (inPath, outPath, args) { if (process.platform === 'win32') { if (fs.statSync(inPath).isFile()) { - var inFile = fs.readFileSync(inPath) - var tmpPath = path.join(os.tmpdir(), 'cross-zip-' + Date.now()) + const inFile = fs.readFileSync(inPath) + const tmpPath = path.join(os.tmpdir(), 'cross-zip-' + Date.now()) fs.mkdirSync(tmpPath) fs.writeFileSync(path.join(tmpPath, path.basename(inPath)), inFile) inPath = tmpPath } fs.rmdirSync(outPath, { recursive: true, maxRetries: 3 }) } - var opts = { + const opts = { cwd: path.dirname(inPath), maxBuffer: Infinity } - cp.execFileSync(getZipCommand(), getZipArgs(inPath, outPath), opts) + cp.execFileSync(getZipCommand(), getZipArgs(inPath, outPath, args), opts) } function unzip (inPath, outPath, cb) { if (!cb) cb = function () {} - var opts = { + const opts = { maxBuffer: Infinity } cp.execFile(getUnzipCommand(), getUnzipArgs(inPath, outPath), opts, function (err) { @@ -92,7 +91,7 @@ function unzip (inPath, outPath, cb) { } function unzipSync (inPath, outPath) { - var opts = { + const opts = { maxBuffer: Infinity } cp.execFileSync(getUnzipCommand(), getUnzipArgs(inPath, outPath), opts) @@ -114,22 +113,33 @@ function getUnzipCommand () { } } -function quotePath (pathToTransform) { - return '"' + pathToTransform + '"' -} +function getZipArgs (inPath, outPath, args) { + let lvl = args?.level + lvl = parseInt(lvl) + lvl = isNaN(lvl) ? 6 : lvl + lvl = Math.max(0, Math.min(lvl, 9)) // Constrain to 0-9 range + + const incBase = args?.incBase ? 1 : 0 -function getZipArgs (inPath, outPath) { if (process.platform === 'win32') { + if (!lvl) { + lvl = 2 // NoCompression + } else if (lvl < 4) { + lvl = 1 // Fastest + } else { + lvl = 0 // Optimal (default) + } + return [ '-nologo', '-noprofile', - '-command', '& { param([String]$myInPath, [String]$myOutPath); Add-Type -A "System.IO.Compression.FileSystem"; [IO.Compression.ZipFile]::CreateFromDirectory($myInPath, $myOutPath); exit !$? }', - '-myInPath', quotePath(inPath), - '-myOutPath', quotePath(outPath) + '-command', `& { Add-Type -A "System.IO.Compression.FileSystem"; \` + [IO.Compression.ZipFile]::CreateFromDirectory("${inPath}", "${outPath}", ${lvl}, ${incBase}); \` + exit !$? }` ] } else { - var fileName = path.basename(inPath) - return ['-r', '-y', outPath, fileName] + const fileName = path.basename(inPath) + return [`-${lvl}`, '-r', '-y', outPath, fileName] } } @@ -138,9 +148,9 @@ function getUnzipArgs (inPath, outPath) { return [ '-nologo', '-noprofile', - '-command', '& { param([String]$myInPath, [String]$myOutPath); Add-Type -A "System.IO.Compression.FileSystem"; [IO.Compression.ZipFile]::ExtractToDirectory($myInPath, $myOutPath); exit !$? }', - '-myInPath', quotePath(inPath), - '-myOutPath', quotePath(outPath) + '-command', `& { Add-Type -A "System.IO.Compression.FileSystem"; \` + [IO.Compression.ZipFile]::ExtractToDirectory("${inPath}", "${outPath}"); \` + exit !$? }` ] } else { return ['-o', inPath, '-d', outPath] diff --git a/package.json b/package.json index e999701..46aa151 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,16 @@ { "name": "cross-zip", "description": "Cross-platform .zip file creation", - "version": "4.0.0", + "version": "4.1.0", "author": { "name": "Feross Aboukhadijeh", "email": "feross@feross.org", "url": "https://feross.org" }, + "contributors": [{ + "name": "Robert Brisita", + "url": "https://github.com/rbrisita" + }], "bugs": { "url": "https://github.com/feross/cross-zip/issues" }, diff --git a/test/unzip.js b/test/unzip.js index 5abaff3..8e48a70 100644 --- a/test/unzip.js +++ b/test/unzip.js @@ -1,21 +1,27 @@ -var fs = require('fs') -var path = require('path') -var test = require('tape') -var zip = require('../') +const fs = require('fs') +const path = require('path') +const test = require('tape') +const zip = require('../') -var filePath = path.join(__dirname, 'content', 'file.txt') -var fileZipPath = path.join(__dirname, 'content', 'file.txt.zip') -var tmpPath = path.join(__dirname, 'tmp') +const filePath = path.join(__dirname, 'content', 'file.txt') +const fileZipPath = path.join(__dirname, 'content', 'file.txt.zip') +const tmpPath = path.join(__dirname, 'tmp') fs.mkdirSync(tmpPath, { recursive: true }) test('unzipSync', function (t) { - var tmpFilePath = path.join(tmpPath, 'file.txt') - fs.rmdirSync(tmpFilePath, { recursive: true }) + const tmpFilePath = path.join(tmpPath, 'file.txt') + try { + fs.rmSync(tmpFilePath, { recursive: true }) + } catch (err) { + if (err.code !== 'ENOENT') { + t.error(err) + } + } zip.unzipSync(fileZipPath, tmpPath) - var tmpFile = fs.readFileSync(tmpFilePath) - var file = fs.readFileSync(filePath) + const tmpFile = fs.readFileSync(tmpFilePath) + const file = fs.readFileSync(filePath) t.deepEqual(tmpFile, file) t.end() @@ -24,15 +30,15 @@ test('unzipSync', function (t) { test('unzip', function (t) { t.plan(3) - var tmpFilePath = path.join(tmpPath, 'file.txt') - fs.rmdir(tmpFilePath, { recursive: true }, function (err) { + const tmpFilePath = path.join(tmpPath, 'file.txt') + fs.rm(tmpFilePath, { recursive: true }, function (err) { t.error(err) zip.unzip(fileZipPath, tmpPath, function (err) { t.error(err) - var tmpFile = fs.readFileSync(tmpFilePath) - var file = fs.readFileSync(filePath) + const tmpFile = fs.readFileSync(tmpFilePath) + const file = fs.readFileSync(filePath) t.deepEqual(tmpFile, file) }) @@ -42,20 +48,20 @@ test('unzip', function (t) { test('unzip from a folder with a space in it', function (t) { t.plan(4) - var zipSpacePath = path.join(tmpPath, 'folder space', path.basename(fileZipPath)) + const zipSpacePath = path.join(tmpPath, 'folder space', path.basename(fileZipPath)) fs.mkdirSync(path.dirname(zipSpacePath), { recursive: true }) fs.copyFileSync(fileZipPath, zipSpacePath) - var tmpFilePath = path.join(tmpPath, 'file.txt') - fs.rmdir(tmpFilePath, { recursive: true }, function (err) { + const tmpFilePath = path.join(tmpPath, 'file.txt') + fs.rm(tmpFilePath, { recursive: true }, function (err) { t.error(err) zip.unzip(zipSpacePath, tmpPath, function (err) { t.error(err) t.ok(fs.existsSync(tmpFilePath), 'extracted file should exist') - var tmpFile = fs.readFileSync(tmpFilePath) - var file = fs.readFileSync(filePath) + const tmpFile = fs.readFileSync(tmpFilePath) + const file = fs.readFileSync(filePath) t.deepEqual(tmpFile, file) }) diff --git a/test/zip.js b/test/zip.js index 0489bd5..5986351 100644 --- a/test/zip.js +++ b/test/zip.js @@ -1,47 +1,101 @@ -var fs = require('fs') -var path = require('path') -var test = require('tape') -var zip = require('../') +const fs = require('fs') +const path = require('path') +const test = require('tape') +const zip = require('../') -var filePath = path.join(__dirname, 'content', 'file.txt') -var tmpPath = path.join(__dirname, 'tmp') +const filePath = path.join(__dirname, 'content', 'file.txt') +const tmpPath = path.join(__dirname, 'tmp') fs.mkdirSync(tmpPath, { recursive: true }) test('zipSync', function (t) { - var tmpFileZipPath = path.join(tmpPath, 'file.zip') + const tmpFileZipPath = path.join(tmpPath, 'file.zip') zip.zipSync(filePath, tmpFileZipPath) - var tmpFilePath = path.join(tmpPath, 'file.txt') - fs.rmdirSync(tmpFilePath, { recursive: true }) + const tmpFilePath = path.join(tmpPath, 'file.txt') + fs.rmSync(tmpFilePath, { recursive: true }) zip.unzipSync(tmpFileZipPath, tmpPath) - var tmpFile = fs.readFileSync(tmpFilePath) - var file = fs.readFileSync(filePath) + const tmpFile = fs.readFileSync(tmpFilePath) + const file = fs.readFileSync(filePath) t.deepEqual(tmpFile, file) t.end() }) +test('zipSync base folder', function (t) { + const tmpFileZipPath = path.join(tmpPath, 'file.zip') + const folderPath = path.join(__dirname, 'content') + zip.zipSync(folderPath, tmpFileZipPath, { incBase: true }) + + const tmpFolderPath = path.join(tmpPath, 'content') + try { + fs.rmSync(tmpFolderPath, { recursive: true }) + } catch (err) { + if (err.code !== 'ENOENT') { + t.error(err) + } + } + zip.unzipSync(tmpFileZipPath, tmpPath) + + t.true(fs.existsSync(tmpFolderPath), 'extracted folder should exist') + + const tmpFilePath = path.join(tmpFolderPath, 'file.txt') + t.true(fs.existsSync(tmpFilePath), 'extracted file should exist') + + const tmpFile = fs.readFileSync(tmpFilePath) + const file = fs.readFileSync(filePath) + t.deepEqual(tmpFile, file, 'should be deeply equivalent') + t.end() +}) + test('zip', function (t) { t.plan(4) - var tmpFileZipPath = path.join(tmpPath, 'file.zip') + const tmpFileZipPath = path.join(tmpPath, 'file.zip') zip.zip(filePath, tmpFileZipPath, function (err) { t.error(err) - var tmpFilePath = path.join(tmpPath, 'file.txt') - fs.rmdir(tmpFilePath, { recursive: true }, function (err) { + const tmpFilePath = path.join(tmpPath, 'file.txt') + fs.rm(tmpFilePath, { recursive: true }, function (err) { t.error(err) zip.unzip(tmpFileZipPath, tmpPath, function (err) { t.error(err) - var tmpFile = fs.readFileSync(tmpFilePath) - var file = fs.readFileSync(filePath) + const tmpFile = fs.readFileSync(tmpFilePath) + const file = fs.readFileSync(filePath) t.deepEqual(tmpFile, file) }) }) }) }) + +test('zip base folder', function (t) { + t.plan(6) + + const tmpFileZipPath = path.join(tmpPath, 'file.zip') + const folderPath = path.join(__dirname, 'content') + zip.zip(folderPath, tmpFileZipPath, function (err) { + t.error(err) + + const tmpFolderPath = path.join(tmpPath, 'content') + fs.rm(tmpFolderPath, { recursive: true }, function (err) { + t.error(err) + + zip.unzip(tmpFileZipPath, tmpPath, function (err) { + t.error(err) + + t.true(fs.existsSync(tmpFolderPath), 'extracted folder should exist') + + const tmpFilePath = path.join(tmpFolderPath, 'file.txt') + t.true(fs.existsSync(tmpFilePath), 'extracted file should exist') + + const tmpFile = fs.readFileSync(tmpFilePath) + const file = fs.readFileSync(filePath) + t.deepEqual(tmpFile, file, 'should be deeply equivalent') + }) + }) + }, { incBase: true }) +})