├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── index.js ├── package.json └── test ├── content ├── file.txt └── file.txt.zip ├── unzip.js └── zip.js /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: / 5 | schedule: 6 | interval: daily 7 | labels: 8 | - dependency 9 | versioning-strategy: increase-if-necessary 10 | - package-ecosystem: github-actions 11 | directory: / 12 | schedule: 13 | interval: daily 14 | labels: 15 | - dependency 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 'on': 3 | - push 4 | - pull_request 5 | jobs: 6 | test: 7 | name: Node ${{ matrix.node }} / ${{ matrix.os }} 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | os: 13 | - ubuntu-latest 14 | node: 15 | - '14' 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: actions/setup-node@v2 19 | with: 20 | node-version: ${{ matrix.node }} 21 | - run: npm install 22 | - run: npm run build --if-present 23 | - run: npm test 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | test/tmp 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Feross Aboukhadijeh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cross-zip [![ci][ci-image]][ci-url] [![npm][npm-image]][npm-url] [![downloads][downloads-image]][downloads-url] [![javascript style guide][standard-image]][standard-url] 2 | 3 | [ci-image]: https://img.shields.io/github/workflow/status/feross/cross-zip/ci/master 4 | [ci-url]: https://github.com/feross/cross-zip/actions 5 | [npm-image]: https://img.shields.io/npm/v/cross-zip.svg 6 | [npm-url]: https://npmjs.org/package/cross-zip 7 | [downloads-image]: https://img.shields.io/npm/dm/cross-zip.svg 8 | [downloads-url]: https://npmjs.org/package/cross-zip 9 | [standard-image]: https://img.shields.io/badge/code_style-standard-brightgreen.svg 10 | [standard-url]: https://standardjs.com 11 | 12 | ### Cross-platform .zip file creation 13 | 14 | ## install 15 | 16 | ``` 17 | npm install cross-zip 18 | ``` 19 | 20 | ## usage 21 | 22 | ```js 23 | var zip = require('cross-zip') 24 | 25 | var inPath = path.join(__dirname, 'myFolder') // folder to zip 26 | var outPath = path.join(__dirname, 'myFile.zip') // name of output zip file 27 | 28 | zip.zipSync(inPath, outPath) 29 | ``` 30 | 31 | ## api 32 | 33 | ### `zip.zip(inPath, outPath, [callback])` 34 | 35 | Zip the folder at `inPath` and save it to a .zip file at `outPath`. If a `callback` 36 | is passed, then it is called with an `Error` or `null`. 37 | 38 | ### `zip.zipSync(inPath, outPath)` 39 | 40 | Sync version of `zip.zip`. 41 | 42 | ### `zip.unzip(inPath, outPath, [callback])` 43 | 44 | Unzip the .zip file at `inPath` into the folder at `outPath`. If a `callback` is 45 | passed, then it is called with an `Error` or `null`. 46 | 47 | ### `zip.unzipSync(inPath, outPath)` 48 | 49 | Sync version of `zip.unzip`. 50 | 51 | ## Windows users 52 | 53 | This package requires [.NET Framework 4.5 or later](https://www.microsoft.com/net) 54 | and [Powershell 3](https://www.microsoft.com/en-us/download/details.aspx?id=34595). 55 | These come **pre-installed** on Windows 8 or later. 56 | 57 | On Windows 7 or earlier, you will need to install these manually in order for 58 | `cross-zip` to function correctly. 59 | 60 | ## reference 61 | 62 | - [Stack Overflow - zipping from command line in Windows](https://stackoverflow.com/questions/17546016/how-can-you-zip-or-unzip-from-the-command-prompt-using-only-windows-built-in-ca) 63 | 64 | ## related 65 | 66 | - [cross-zip-cli](https://github.com/jprichardson/cross-zip-cli): CLI version of cross-zip. 67 | 68 | ## license 69 | 70 | MIT. Copyright (c) [Feross Aboukhadijeh](http://feross.org). 71 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! cross-zip. MIT License. Feross Aboukhadijeh */ 2 | module.exports = { 3 | zip, 4 | zipSync, 5 | unzip, 6 | unzipSync 7 | } 8 | 9 | const cp = require('child_process') 10 | const fs = require('fs') 11 | const os = require('os') 12 | const path = require('path') 13 | 14 | function zip (inPath, outPath, cb) { 15 | if (!cb) cb = function () {} 16 | if (process.platform === 'win32') { 17 | fs.stat(inPath, function (err, stats) { 18 | if (err) return cb(err) 19 | if (stats.isFile()) { 20 | copyToTemp() 21 | } else { 22 | doZip() 23 | } 24 | }) 25 | } else { 26 | doZip() 27 | } 28 | 29 | // Windows zip command cannot zip files, only directories. So move the file into 30 | // a temporary directory before zipping. 31 | function copyToTemp () { 32 | fs.readFile(inPath, function (err, inFile) { 33 | if (err) return cb(err) 34 | const tmpPath = path.join(os.tmpdir(), 'cross-zip-' + Date.now()) 35 | fs.mkdir(tmpPath, function (err) { 36 | if (err) return cb(err) 37 | fs.writeFile(path.join(tmpPath, path.basename(inPath)), inFile, function (err) { 38 | if (err) return cb(err) 39 | inPath = tmpPath 40 | doZip() 41 | }) 42 | }) 43 | }) 44 | } 45 | 46 | // Windows zip command does not overwrite existing files. So do it manually first. 47 | function doZip () { 48 | if (process.platform === 'win32') { 49 | fs.rmdir(outPath, { recursive: true, maxRetries: 3 }, doZip2) 50 | } else { 51 | doZip2() 52 | } 53 | } 54 | 55 | function doZip2 () { 56 | const opts = { 57 | cwd: path.dirname(inPath), 58 | maxBuffer: Infinity 59 | } 60 | cp.execFile(getZipCommand(), getZipArgs(inPath, outPath), opts, function (err) { 61 | cb(err) 62 | }) 63 | } 64 | } 65 | 66 | function zipSync (inPath, outPath) { 67 | if (process.platform === 'win32') { 68 | if (fs.statSync(inPath).isFile()) { 69 | const inFile = fs.readFileSync(inPath) 70 | const tmpPath = path.join(os.tmpdir(), 'cross-zip-' + Date.now()) 71 | fs.mkdirSync(tmpPath) 72 | fs.writeFileSync(path.join(tmpPath, path.basename(inPath)), inFile) 73 | inPath = tmpPath 74 | } 75 | fs.rmdirSync(outPath, { recursive: true, maxRetries: 3 }) 76 | } 77 | const opts = { 78 | cwd: path.dirname(inPath), 79 | maxBuffer: Infinity 80 | } 81 | cp.execFileSync(getZipCommand(), getZipArgs(inPath, outPath), opts) 82 | } 83 | 84 | function unzip (inPath, outPath, cb) { 85 | if (!cb) cb = function () {} 86 | const opts = { 87 | maxBuffer: Infinity 88 | } 89 | cp.execFile(getUnzipCommand(), getUnzipArgs(inPath, outPath), opts, function (err) { 90 | cb(err) 91 | }) 92 | } 93 | 94 | function unzipSync (inPath, outPath) { 95 | const opts = { 96 | maxBuffer: Infinity 97 | } 98 | cp.execFileSync(getUnzipCommand(), getUnzipArgs(inPath, outPath), opts) 99 | } 100 | 101 | function getZipCommand () { 102 | if (process.platform === 'win32') { 103 | return 'powershell.exe' 104 | } else { 105 | return 'zip' 106 | } 107 | } 108 | 109 | function getUnzipCommand () { 110 | if (process.platform === 'win32') { 111 | return 'powershell.exe' 112 | } else { 113 | return 'unzip' 114 | } 115 | } 116 | 117 | function quotePath (pathToTransform) { 118 | return '"' + pathToTransform + '"' 119 | } 120 | 121 | function getZipArgs (inPath, outPath) { 122 | if (process.platform === 'win32') { 123 | return [ 124 | '-nologo', 125 | '-noprofile', 126 | '-command', '& { param([String]$myInPath, [String]$myOutPath); Add-Type -A "System.IO.Compression.FileSystem"; [IO.Compression.ZipFile]::CreateFromDirectory($myInPath, $myOutPath); exit !$? }', 127 | '-myInPath', quotePath(inPath), 128 | '-myOutPath', quotePath(outPath) 129 | ] 130 | } else { 131 | const fileName = path.basename(inPath) 132 | return ['-r', '-y', outPath, fileName] 133 | } 134 | } 135 | 136 | function getUnzipArgs (inPath, outPath) { 137 | if (process.platform === 'win32') { 138 | return [ 139 | '-nologo', 140 | '-noprofile', 141 | '-command', '& { param([String]$myInPath, [String]$myOutPath); Add-Type -A "System.IO.Compression.FileSystem"; [IO.Compression.ZipFile]::ExtractToDirectory($myInPath, $myOutPath); exit !$? }', 142 | '-myInPath', quotePath(inPath), 143 | '-myOutPath', quotePath(outPath) 144 | ] 145 | } else { 146 | return ['-o', inPath, '-d', outPath] 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cross-zip", 3 | "description": "Cross-platform .zip file creation", 4 | "version": "4.0.1", 5 | "author": { 6 | "name": "Feross Aboukhadijeh", 7 | "email": "feross@feross.org", 8 | "url": "https://feross.org" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/feross/cross-zip/issues" 12 | }, 13 | "devDependencies": { 14 | "standard": "*", 15 | "tape": "^5.0.0" 16 | }, 17 | "homepage": "https://github.com/feross/cross-zip", 18 | "keywords": [ 19 | "ZIP", 20 | "cross platform", 21 | "cross-platform", 22 | "linux", 23 | "mac", 24 | "os x", 25 | "windows", 26 | "zip" 27 | ], 28 | "license": "MIT", 29 | "main": "index.js", 30 | "repository": { 31 | "type": "git", 32 | "url": "git://github.com/feross/cross-zip.git" 33 | }, 34 | "scripts": { 35 | "test": "standard && tape test/*.js" 36 | }, 37 | "engines": { 38 | "node": ">=12.10" 39 | }, 40 | "funding": [ 41 | { 42 | "type": "github", 43 | "url": "https://github.com/sponsors/feross" 44 | }, 45 | { 46 | "type": "patreon", 47 | "url": "https://www.patreon.com/feross" 48 | }, 49 | { 50 | "type": "consulting", 51 | "url": "https://feross.org/support" 52 | } 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /test/content/file.txt: -------------------------------------------------------------------------------- 1 | 12345 2 | -------------------------------------------------------------------------------- /test/content/file.txt.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feross/cross-zip/eba335474e6142468bd8904f6456208db906d40d/test/content/file.txt.zip -------------------------------------------------------------------------------- /test/unzip.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const test = require('tape') 4 | const zip = require('../') 5 | 6 | const filePath = path.join(__dirname, 'content', 'file.txt') 7 | const fileZipPath = path.join(__dirname, 'content', 'file.txt.zip') 8 | const tmpPath = path.join(__dirname, 'tmp') 9 | 10 | fs.mkdirSync(tmpPath, { recursive: true }) 11 | 12 | test('unzipSync', function (t) { 13 | const tmpFilePath = path.join(tmpPath, 'file.txt') 14 | fs.rmSync(tmpFilePath, { recursive: true, force: true }) 15 | zip.unzipSync(fileZipPath, tmpPath) 16 | 17 | const tmpFile = fs.readFileSync(tmpFilePath) 18 | const file = fs.readFileSync(filePath) 19 | 20 | t.deepEqual(tmpFile, file) 21 | t.end() 22 | }) 23 | 24 | test('unzip', function (t) { 25 | t.plan(3) 26 | 27 | const tmpFilePath = path.join(tmpPath, 'file.txt') 28 | fs.rm(tmpFilePath, { recursive: true }, function (err) { 29 | t.error(err) 30 | 31 | zip.unzip(fileZipPath, tmpPath, function (err) { 32 | t.error(err) 33 | 34 | const tmpFile = fs.readFileSync(tmpFilePath) 35 | const file = fs.readFileSync(filePath) 36 | 37 | t.deepEqual(tmpFile, file) 38 | }) 39 | }) 40 | }) 41 | 42 | test('unzip from a folder with a space in it', function (t) { 43 | t.plan(4) 44 | 45 | const zipSpacePath = path.join( 46 | tmpPath, 47 | 'folder space', 48 | path.basename(fileZipPath) 49 | ) 50 | fs.mkdirSync(path.dirname(zipSpacePath), { recursive: true }) 51 | fs.copyFileSync(fileZipPath, zipSpacePath) 52 | 53 | const tmpFilePath = path.join(tmpPath, 'file.txt') 54 | fs.rm(tmpFilePath, { recursive: true }, function (err) { 55 | t.error(err) 56 | 57 | zip.unzip(zipSpacePath, tmpPath, function (err) { 58 | t.error(err) 59 | 60 | t.ok(fs.existsSync(tmpFilePath), 'extracted file should exist') 61 | const tmpFile = fs.readFileSync(tmpFilePath) 62 | const file = fs.readFileSync(filePath) 63 | 64 | t.deepEqual(tmpFile, file) 65 | }) 66 | }) 67 | }) 68 | -------------------------------------------------------------------------------- /test/zip.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const test = require('tape') 4 | const zip = require('../') 5 | 6 | const filePath = path.join(__dirname, 'content', 'file.txt') 7 | const tmpPath = path.join(__dirname, 'tmp') 8 | 9 | fs.mkdirSync(tmpPath, { recursive: true }) 10 | 11 | test('zipSync', function (t) { 12 | const tmpFileZipPath = path.join(tmpPath, 'file.zip') 13 | zip.zipSync(filePath, tmpFileZipPath) 14 | 15 | const tmpFilePath = path.join(tmpPath, 'file.txt') 16 | fs.rmSync(tmpFilePath, { recursive: true, force: true }) 17 | zip.unzipSync(tmpFileZipPath, tmpPath) 18 | 19 | const tmpFile = fs.readFileSync(tmpFilePath) 20 | const file = fs.readFileSync(filePath) 21 | 22 | t.deepEqual(tmpFile, file) 23 | t.end() 24 | }) 25 | 26 | test('zip', function (t) { 27 | t.plan(4) 28 | 29 | const tmpFileZipPath = path.join(tmpPath, 'file.zip') 30 | zip.zip(filePath, tmpFileZipPath, function (err) { 31 | t.error(err) 32 | 33 | const tmpFilePath = path.join(tmpPath, 'file.txt') 34 | fs.rm(tmpFilePath, { recursive: true }, function (err) { 35 | t.error(err) 36 | 37 | zip.unzip(tmpFileZipPath, tmpPath, function (err) { 38 | t.error(err) 39 | 40 | const tmpFile = fs.readFileSync(tmpFilePath) 41 | const file = fs.readFileSync(filePath) 42 | 43 | t.deepEqual(tmpFile, file) 44 | }) 45 | }) 46 | }) 47 | }) 48 | --------------------------------------------------------------------------------