├── .eslintrc.json ├── .github └── workflows │ └── nodejs.yml ├── .gitignore ├── .madrun.js ├── .npmignore ├── .putout.json ├── .travis.yml ├── .typos.yml ├── ChangeLog ├── LICENSE ├── README.md ├── bin └── inly.js ├── help.json ├── lib ├── bzip2.js ├── gzip.js └── inly.js ├── package.json └── test ├── bzip2.js ├── fixture ├── empty.bz2 ├── empty.gz ├── empty.tar ├── empty.zip ├── fixture.tar ├── fixture.tar.bz2 ├── fixture.tar.gz ├── fixture.txt ├── fixture.txt.bz2 ├── fixture.txt.gz └── fixture.zip ├── gzip.js ├── inly.js ├── tar.js └── zip.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "node/no-unsupported-features/node-builtins": "off" 4 | }, 5 | "overrides": [{ 6 | "files": ["bin/**"], 7 | "rules": { 8 | "no-process-exit": "off" 9 | } 10 | }], 11 | "extends": [ 12 | "plugin:n/recommended", 13 | "plugin:putout/recommended" 14 | ], 15 | "plugins": [ 16 | "putout", 17 | "n" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | on: 3 | push: 4 | branches: master 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | env: 9 | NAME: inly 10 | strategy: 11 | matrix: 12 | node-version: 13 | - 18.x 14 | - 20.x 15 | - 21.x 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: oven-sh/setup-bun@v1 19 | with: 20 | bun-version: latest 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | - name: Install Redrun 26 | run: bun install redrun -g --no-save 27 | - name: Install 28 | run: yarn --no-lockfile 29 | - name: Lint 30 | run: redrun fix:lint 31 | - name: Install Rust 32 | run: rustup update 33 | - uses: actions/cache@v3 34 | with: 35 | path: | 36 | ~/.cargo/bin/ 37 | ~/.cargo/registry/index/ 38 | ~/.cargo/registry/cache/ 39 | ~/.cargo/git/db/ 40 | target/ 41 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 42 | - name: Typos Install 43 | run: which typos || cargo install typos-cli 44 | - name: Typos 45 | run: typos --write-changes 46 | - name: Commit fixes 47 | continue-on-error: true 48 | uses: EndBug/add-and-commit@v9 49 | with: 50 | fetch: --force 51 | message: "chore: ${{ env.NAME }}: actions: lint ☘️" 52 | pull: --rebase --autostash 53 | - name: Coverage 54 | run: redrun coverage report 55 | - name: Coveralls 56 | continue-on-error: true 57 | uses: coverallsapp/github-action@v2 58 | with: 59 | github-token: ${{ secrets.GITHUB_TOKEN }} 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | legacy 2 | node_modules 3 | npm-debug.log 4 | .nyc_output 5 | *.swp 6 | yarn-error.log 7 | 8 | .idea 9 | coverage 10 | -------------------------------------------------------------------------------- /.madrun.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {run} = require('madrun'); 4 | 5 | module.exports = { 6 | 'lint': () => 'putout .', 7 | 'fix:lint': () => run('lint', '--fix'), 8 | 'test': () => `tape 'test/**/*.js'`, 9 | 'coverage': () => 'c8 npm test', 10 | 'report': () => 'c8 report --reporter=text-lcov | coveralls', 11 | 'watcher': () => 'nodemon -w test -w lib --exec', 12 | 'watch:test': () => run('watcher', 'npm test'), 13 | 'watch:coverage': () => run('watcher', 'npm run coverage'), 14 | }; 15 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | test 3 | *.swp 4 | 5 | yarn-error.log 6 | 7 | coverage 8 | -------------------------------------------------------------------------------- /.putout.json: -------------------------------------------------------------------------------- 1 | { 2 | "match": { 3 | "test": { 4 | "remove-console": "off" 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 14 4 | - 12 5 | - 10 6 | script: 7 | - npm run lint 8 | - npm run coverage && npm run report 9 | notifications: 10 | email: 11 | on_success: never 12 | on_failure: change 13 | -------------------------------------------------------------------------------- /.typos.yml: -------------------------------------------------------------------------------- 1 | [files] 2 | extend-exclude= ["ChangeLog"] 3 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2024.02.01, v5.0.1 2 | 3 | fix: 4 | - 05b6c38 badges 5 | 6 | 2024.02.01, v5.0.0 7 | 8 | feature: 9 | - bb1bdb3 inly: try-catch v3.0.1 10 | - 43abe97 inly: nodemon v3.0.3 11 | - a40e0f7 inly: eslint v8.56.0 12 | - d405759 inly: onezip v6.0.2 13 | - 6962ba5 drop support of node < 18 14 | - 694f24f inly: eslint-plugin-n v16.6.2 15 | - 194d268 inly: madrun v10.0.1 16 | - 3d028d8 inly: eslint-plugin-putout v22.3.1 17 | - 6ea7ce9 inly: supertape v10.0.0 18 | - a1c2c34 inly: putout v35.0.1 19 | - 31fcc93 inly: yargs-parser v21.1.1 20 | - dd0b97d inly: glob v10.3.10 21 | 22 | 2020.09.11, v4.0.8 23 | 24 | feature: 25 | - (package) yargs-parser v20.0.0 26 | 27 | 28 | 2020.08.11, v4.0.7 29 | 30 | feature: 31 | - (package) yargs-parser v19.0.1 32 | 33 | 34 | 2020.08.06, v4.0.6 35 | 36 | feature: 37 | - (package) eslint-plugin-putout v5.0.1 38 | - (package) through2 v4.0.2 39 | - (package) putout v9.11.0 40 | - (package) eslint-plugin-node v11.1.0 41 | - (package) eslint v7.6.0 42 | - (package) madrun v7.0.1 43 | - (package) supertape v2.0.1 44 | - (package) yargs-parser v18.1.3 45 | 46 | 47 | 2020.02.26, v4.0.5 48 | 49 | feature: 50 | - (package) eslint-plugin-node v11.0.0 51 | - (package) nyc v15.0.0 52 | - (package) yargs-parser v17.0.0 53 | - (package) try-to-catch v3.0.0 54 | 55 | 56 | 2019.12.19, v4.0.4 57 | 58 | feature: 59 | - (package) putout v7.3.1 60 | - (package) madrun v5.0.1 61 | - (package) jaguar v6.0.0 62 | - (package) eslint-plugin-putout v3.0.0 63 | - (package) nodemon v2.0.2 64 | 65 | 66 | 2019.10.28, v4.0.3 67 | 68 | feature: 69 | - (package) yargs-parser v16.0.0 70 | - (package) madrun v4.1.1 71 | 72 | 73 | 2019.10.16, v4.0.2 74 | 75 | feature: 76 | - (package) try-to-catch v2.0.0 77 | 78 | 79 | 2019.10.16, v4.0.1 80 | 81 | feature: 82 | - (package) onezip v4.0.0 83 | 84 | 85 | 2019.10.16, v4.0.0 86 | 87 | feature: 88 | - (inly) drop support of node < 10 89 | - (package) yargs-parser v15.0.0 90 | 91 | 92 | 2019.09.18, v3.0.4 93 | 94 | feature: 95 | - (inly) madrun 96 | - (package) rimraf v3.0.0 97 | - (package) pipe-io v4.0.0 98 | - (package) nyc v14.1.1 99 | - (package) eslint-plugin-node v10.0.0 100 | - (package) eslint v6.4.0 101 | - (package) yargs-parser v14.0.0 102 | 103 | 104 | 2018.11.29, v3.0.3 105 | 106 | feature: 107 | - (package) eslint-plugin-node v8.0.0 108 | - (package) through2 v3.0.0 109 | - (package) yargs-parser v11.1.1 110 | 111 | 112 | 2018.09.28, v3.0.2 113 | 114 | feature: 115 | - (package) bizzy v3.0.0 116 | 117 | 118 | 2018.09.28, v3.0.1 119 | 120 | feature: 121 | - (package) onezip v3.0.0 122 | 123 | 124 | 2018.09.28, v3.0.0 125 | 126 | feature: 127 | - (package) nyc v13.0.1 128 | - (package) jaguar v5.0.0 129 | - (package) redrun v7.0.0 130 | - (package) eslint-plugin-node v7.0.1 131 | - (package) eslint v5.6.0 132 | - (package) drop support of node < 8 133 | - (travis) add node v10 134 | 135 | 136 | 2018.05.02, v2.0.2 137 | 138 | fix: 139 | - (inly) rm legacy 140 | 141 | 142 | 2018.05.02, v2.0.1 143 | 144 | feature: 145 | - (package) onezip v2.0.0 146 | - (package) jaguar v4.0.0 147 | - (package) bizzy v2.0.1 148 | 149 | 150 | 2018.05.02, v2.0.0 151 | 152 | feature: 153 | - (package) add eslint-plugin-node 154 | - (package) pipe-io v3.0.0 155 | - (inly) drop support of node < 4 156 | - (package) rm jshin, jscs 157 | 158 | 159 | 2018.04.20, v1.2.5 160 | 161 | feature: 162 | - (package) yargs-parser v10.0.0 163 | - (package) redrun v6.0.0 164 | 165 | 166 | 2018.03.05, v1.2.4 167 | 168 | feature: 169 | - (package) yargs-parser v9.0.2 170 | - (package) preset: es2015 -> env 171 | 172 | 173 | 2017.10.05, v1.2.3 174 | 175 | feature: 176 | - (package) yargs-parser v8.0.0 177 | - (package) coveralls v3.0.0 178 | - (package) nyc v11.0.2 179 | - (package) eslint v4.0.0 180 | 181 | 182 | 2017.05.10, v1.2.2 183 | 184 | feature: 185 | - (package) yargs-parser v7.0.0 186 | 187 | 188 | 2017.04.24, v1.2.1 189 | 190 | fix: 191 | - (inly) tbz -> tbz2 192 | - test(fixture) .tar.bz2 193 | 194 | 195 | 2017.04.21, v1.2.0 196 | 197 | feature: 198 | - (inly) add support of .tar.bz2 199 | 200 | 201 | 2017.04.21, v1.1.0 202 | 203 | feature: 204 | - (inly) add bzip2 support 205 | 206 | 207 | 2017.02.21, v1.0.5 208 | 209 | fix: 210 | - (gzip) pipe-io -> pipe-io/legacy: support of node < 4 211 | 212 | 213 | 2017.02.21, v1.0.4 214 | 215 | fix: 216 | - (package) through2 v2.0.3 217 | 218 | 219 | 2017.02.20, v1.0.3 220 | 221 | fix: 222 | - (package) add pipe-io (closes #1) 223 | 224 | feature: 225 | - (package) yargs-parser v5.0.0 226 | 227 | 228 | 2017.01.27, v1.0.2 229 | 230 | feature: 231 | - (package) rm unused async 232 | 233 | 234 | 2017.01.26, v1.0.1 235 | 236 | feature: 237 | - (package) rm umkdirp 238 | 239 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) coderaiser 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Inly [![License][LicenseIMGURL]][LicenseURL] [![NPM version][NPMIMGURL]][NPMURL] [![Build Status][BuildStatusIMGURL]][BuildStatusURL] [![Coverage Status][CoverageIMGURL]][CoverageURL] 2 | 3 | [NPMURL]: https://npmjs.org/package/inly "npm" 4 | [NPMIMGURL]: https://img.shields.io/npm/v/inly.svg?style=flat 5 | [BuildStatusURL]: https://github.com/coderaiser/node-inly/actions/workflows/nodejs.yml "Build Status" 6 | [BuildStatusIMGURL]: https://github.com/coderaiser/node-inly/actions/workflows/nodejs.yml/badge.svg 7 | [LicenseURL]: https://tldrlegal.com/license/mit-license "MIT License" 8 | [LicenseIMGURL]: https://img.shields.io/badge/license-MIT-317BF9.svg?style=flat 9 | [CoverageURL]: https://coveralls.io/github/coderaiser/node-inly?branch=master 10 | [CoverageIMGURL]: https://coveralls.io/repos/coderaiser/node-inly/badge.svg?branch=master&service=github 11 | 12 | Extract .zip, .gz, .bz2, .tar, tar.gz, tar.bz2, .tgz, .tbz2 archives with emitter. 13 | 14 | ## Global 15 | 16 | `inly` could be installed global with 17 | 18 | ``` 19 | npm i inly -g 20 | ``` 21 | 22 | And used this way: 23 | 24 | ``` 25 | Usage: inly [filename] 26 | Options: 27 | -h, --help display this help and exit 28 | -v, --version output version information and exit 29 | ``` 30 | 31 | ## Local 32 | 33 | `inly` could be used localy. It will emit event on every packed/extracted file. 34 | Good for making progress bars. 35 | 36 | ## Install 37 | 38 | ``` 39 | npm i inly --save 40 | ``` 41 | 42 | ## How to use? 43 | 44 | ### inly(from, to) 45 | 46 | - `from` - path to archive 47 | - `to` - path to directory where files would be stored. 48 | 49 | ```js 50 | const inly = require('inly'); 51 | const path = require('path'); 52 | const cwd = process.cwd(); 53 | const name = 'pipe.zip'; 54 | const to = `${cwd}/pipe-io`; 55 | const from = path.join(cwd, name); 56 | 57 | const extract = inly(from, to); 58 | 59 | extract.on('file', (name) => { 60 | console.log(name); 61 | }); 62 | 63 | extract.on('progress', (percent) => { 64 | console.log(`${percent}%`); 65 | }); 66 | 67 | extract.on('error', (error) => { 68 | console.error(error); 69 | }); 70 | 71 | extract.on('end', () => { 72 | console.log('done'); 73 | }); 74 | ``` 75 | 76 | In case of starting example output should be similar to (but with additional events): 77 | 78 | ``` 79 | 33% 80 | 67% 81 | 100% 82 | done 83 | ``` 84 | 85 | ## Related 86 | 87 | - [OneZip](https://github.com/coderaiser/node-onezip "OneZip") - Pack and extract .zip archives with emitter. 88 | - [Bizzy](https://github.com/coderaiser/node-bizzy "Bizzy") - Pack and extract .tar.bz2 archives with emitter. 89 | - [Jaguar](https://github.com/coderaiser/node-jaguar "Jaguar") - Pack and extract .tar.gz archives with emitter. 90 | - [Jag](https://github.com/coderaiser/node-jag "Jag") - Pack files and folders with tar and gzip. 91 | - [Tar-to-zip](https://github.com/coderaiser/node-tar-to-zip "Tar-to-zip") - Convert tar and tar.gz archives to zip. 92 | - [Copymitter](https://github.com/coderaiser/node-copymitter "Copymitter") - Copy files with emitter. 93 | - [Remy](https://github.com/coderaiser/node-remy "Remy") - Remove files with emitter. 94 | 95 | ## License 96 | 97 | MIT 98 | -------------------------------------------------------------------------------- /bin/inly.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | const process = require('node:process'); 6 | const inly = require('..'); 7 | const glob = require('glob'); 8 | const {argv} = process; 9 | 10 | const args = require('yargs-parser')(argv.slice(2), { 11 | alias: { 12 | v: 'version', 13 | h: 'help', 14 | }, 15 | }); 16 | 17 | validate(args); 18 | 19 | if (args.version) 20 | version(); 21 | else if (args.help) 22 | help(); 23 | else if (args._.length) 24 | getName(args._.pop(), extract); 25 | else 26 | help(); 27 | 28 | function extract(file) { 29 | const cwd = process.cwd(); 30 | const packer = inly(file, cwd); 31 | 32 | packer.on('error', (error) => { 33 | console.error(error.message); 34 | }); 35 | 36 | packer.on('progress', (percent) => { 37 | process.stdout.write('\r' + percent + '%'); 38 | }); 39 | 40 | packer.on('end', () => { 41 | process.stdout.write('\n'); 42 | }); 43 | } 44 | 45 | function getName(str, fn) { 46 | glob(str, (error, files) => { 47 | if (error) 48 | return console.error(error.message); 49 | 50 | if (!files.length) 51 | return console.error('file not found'); 52 | 53 | fn(files[0]); 54 | }); 55 | } 56 | 57 | function version() { 58 | console.log('v' + info().version); 59 | } 60 | 61 | function info() { 62 | return require('../package'); 63 | } 64 | 65 | function help() { 66 | const bin = require('../help'); 67 | const usage = `Usage: ${info().name} [path]`; 68 | 69 | console.log(usage); 70 | console.log('Options:'); 71 | 72 | for (const name of Object.keys(bin)) { 73 | console.log(` ${name} ${bin[name]}`); 74 | } 75 | } 76 | 77 | function validate(args) { 78 | const cmdReg = /^(_|v(ersion)?|h(elp)?)$/; 79 | 80 | for (const cmd of Object.keys(args)) { 81 | if (!cmdReg.test(cmd)) { 82 | const {name} = info(); 83 | console.error(`'${cmd}' is not a ${name} option. See '${name} --help'.`); 84 | process.exit(-1); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /help.json: -------------------------------------------------------------------------------- 1 | { 2 | "-h, --help ": "display this help and exit", 3 | "-v, --version ": "output version information and exit" 4 | } 5 | -------------------------------------------------------------------------------- /lib/bzip2.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const process = require('node:process'); 4 | const fs = require('fs'); 5 | 6 | const path = require('path'); 7 | const bzip2 = require('unbzip2-stream'); 8 | const {inherits} = require('util'); 9 | const {EventEmitter} = require('events'); 10 | 11 | const pipe = require('pipe-io'); 12 | const through = require('through2'); 13 | const tryToCatch = require('try-to-catch'); 14 | const {stat} = fs.promises; 15 | 16 | inherits(Bzip2, EventEmitter); 17 | 18 | module.exports = (from, to) => { 19 | const emitter = new Bzip2(from, to); 20 | 21 | process.nextTick(() => { 22 | emitter._start(); 23 | }); 24 | 25 | return emitter; 26 | }; 27 | 28 | function Bzip2(from, to) { 29 | const name = path.basename(from.replace('.bz2', '')); 30 | 31 | this._from = from; 32 | this._to = path.join(to, name); 33 | this._i = 0; 34 | this._n = 0; 35 | this._percent = 0; 36 | this._percentPrev = 0; 37 | } 38 | 39 | Bzip2.prototype._start = async function() { 40 | const from = this._from; 41 | const to = this._to; 42 | 43 | const [statError, statData] = await tryToCatch(stat, from); 44 | 45 | if (statError) 46 | return this.emit('error', statError); 47 | 48 | if (!statData.size) 49 | return this.emit('error', Error('archive is empty')); 50 | 51 | this._n = statData.size; 52 | 53 | const unbzip2Stream = bzip2(); 54 | const readStream = fs.createReadStream(from); 55 | const writeStream = fs.createWriteStream(to); 56 | const progressStream = this.getProgressStream(); 57 | 58 | const [e] = await tryToCatch(pipe, [ 59 | readStream, 60 | progressStream, 61 | unbzip2Stream, 62 | writeStream, 63 | ]); 64 | 65 | if (e) 66 | return this.emit('error', e); 67 | 68 | this.emit('file', path.basename(to)); 69 | this.emit('end'); 70 | }; 71 | 72 | Bzip2.prototype.getProgressStream = function() { 73 | return through((chunk, enc, callback) => { 74 | this._i += chunk.length; 75 | this._progress(); 76 | callback(null, chunk); 77 | }); 78 | }; 79 | 80 | Bzip2.prototype._progress = function() { 81 | const value = Math.round(this._i * 100 / this._n); 82 | 83 | this._percent = value; 84 | 85 | if (value !== this._percentPrev) { 86 | this._percentPrev = value; 87 | this.emit('progress', value); 88 | } 89 | }; 90 | -------------------------------------------------------------------------------- /lib/gzip.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const process = require('node:process'); 4 | const fs = require('fs'); 5 | 6 | const path = require('path'); 7 | const zlib = require('zlib'); 8 | const {inherits} = require('util'); 9 | const {EventEmitter} = require('events'); 10 | 11 | const pipe = require('pipe-io'); 12 | const through = require('through2'); 13 | const tryToCatch = require('try-to-catch'); 14 | const {stat} = fs.promises; 15 | 16 | inherits(Gzip, EventEmitter); 17 | 18 | module.exports = (from, to) => { 19 | const emitter = new Gzip(from, to); 20 | 21 | process.nextTick(() => { 22 | emitter._start(); 23 | }); 24 | 25 | return emitter; 26 | }; 27 | 28 | function Gzip(from, to) { 29 | EventEmitter.call(this); 30 | 31 | const name = path.basename(from.replace('.gz', '')); 32 | 33 | this._from = from; 34 | this._to = path.join(to, name); 35 | this._i = 0; 36 | this._n = 0; 37 | this._percent = 0; 38 | this._percentPrev = 0; 39 | } 40 | 41 | Gzip.prototype._start = async function() { 42 | const from = this._from; 43 | const to = this._to; 44 | 45 | const [statError, statData] = await tryToCatch(stat, from); 46 | 47 | if (statError) 48 | return this.emit('error', statError); 49 | 50 | if (!statData.size) 51 | return this.emit('error', Error('archive is empty')); 52 | 53 | this._n = statData.size; 54 | 55 | const unzipStream = zlib.createGunzip(); 56 | const readStream = fs.createReadStream(from); 57 | const writeStream = fs.createWriteStream(to); 58 | const progressStream = this.getProgressStream(); 59 | 60 | const [e] = await tryToCatch(pipe, [ 61 | readStream, 62 | progressStream, 63 | unzipStream, 64 | writeStream, 65 | ]); 66 | 67 | if (e) 68 | return this.emit('error', e); 69 | 70 | this.emit('file', path.basename(to)); 71 | this.emit('end'); 72 | }; 73 | 74 | Gzip.prototype.getProgressStream = function() { 75 | return through((chunk, enc, callback) => { 76 | this._i += chunk.length; 77 | this._progress(); 78 | callback(null, chunk); 79 | }); 80 | }; 81 | 82 | Gzip.prototype._progress = function() { 83 | const value = Math.round(this._i * 100 / this._n); 84 | 85 | this._percent = value; 86 | 87 | if (value !== this._percentPrev) { 88 | this._percentPrev = value; 89 | this.emit('progress', value); 90 | } 91 | }; 92 | -------------------------------------------------------------------------------- /lib/inly.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const process = require('node:process'); 4 | 5 | const path = require('path'); 6 | const util = require('util'); 7 | const onezip = require('onezip'); 8 | const jaguar = require('jaguar'); 9 | const bizzy = require('bizzy'); 10 | const gzip = require('./gzip'); 11 | const bzip2 = require('./bzip2'); 12 | const {EventEmitter} = require('events'); 13 | const isString = (a) => typeof a === 'string'; 14 | 15 | util.inherits(Inly, EventEmitter); 16 | 17 | module.exports = (from, to) => { 18 | check(from, to); 19 | const emitter = new Inly(from, to); 20 | 21 | process.nextTick(() => { 22 | emitter._start(); 23 | }); 24 | 25 | return emitter; 26 | }; 27 | 28 | function check(from, to) { 29 | if (!isString(from)) 30 | throw Error('from should be a string!'); 31 | 32 | if (!isString(to)) 33 | throw Error('to should be a string!'); 34 | } 35 | 36 | function Inly(from, to) { 37 | this._from = from; 38 | this._to = to; 39 | 40 | EventEmitter.call(this); 41 | } 42 | 43 | function getExtractor(name) { 44 | if (/\.zip/.test(name)) 45 | return onezip.extract; 46 | 47 | if (/\.(tar\.gz|tgz|tar)$/.test(name)) 48 | return jaguar.extract; 49 | 50 | if (/\.(tar\.bz2|tbz2|tar)$/.test(name)) 51 | return bizzy; 52 | 53 | if (/\.gz/.test(name)) 54 | return gzip; 55 | 56 | if (/\.bz2/.test(name)) 57 | return bzip2; 58 | } 59 | 60 | Inly.prototype._start = function() { 61 | const from = this._from; 62 | const to = this._to; 63 | const ext = path.extname(from); 64 | const error = Error(`Not supported archive type: "${ext}"`); 65 | const extractor = getExtractor(from); 66 | 67 | if (!extractor) 68 | return this.emit('error', error); 69 | 70 | extractor(from, to) 71 | .on('error', (e) => { 72 | this.emit('error', e); 73 | }) 74 | .on('progress', (n) => { 75 | this.emit('progress', n); 76 | }) 77 | .on('file', (file) => { 78 | this.emit('file', file); 79 | }) 80 | .on('end', () => { 81 | this.emit('end'); 82 | }); 83 | }; 84 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inly", 3 | "version": "5.0.1", 4 | "type": "commonjs", 5 | "description": "extract .zip, .gz, .bz2, .tar, .tar.gz, .tgz, .tbz2", 6 | "main": "lib/inly.js", 7 | "bin": { 8 | "inly": "bin/inly.js" 9 | }, 10 | "scripts": { 11 | "lint": "madrun lint", 12 | "fix:lint": "madrun fix:lint", 13 | "test": "madrun test", 14 | "coverage": "madrun coverage", 15 | "report": "madrun report", 16 | "watcher": "madrun watcher", 17 | "watch:test": "madrun watch:test", 18 | "watch:coverage": "madrun watch:coverage" 19 | }, 20 | "dependencies": { 21 | "bizzy": "^3.0.0", 22 | "glob": "^10.3.10", 23 | "jaguar": "^6.0.0", 24 | "onezip": "^6.0.2", 25 | "pipe-io": "^4.0.0", 26 | "through2": "^4.0.2", 27 | "try-to-catch": "^3.0.0", 28 | "unbzip2-stream": "^1.0.11", 29 | "yargs-parser": "^21.1.1" 30 | }, 31 | "devDependencies": { 32 | "eslint": "^8.56.0", 33 | "eslint-plugin-n": "^16.6.2", 34 | "eslint-plugin-putout": "^22.3.1", 35 | "madrun": "^10.0.1", 36 | "nodemon": "^3.0.3", 37 | "nyc": "^15.0.0", 38 | "putout": "^35.0.1", 39 | "supertape": "^10.0.0", 40 | "try-catch": "^3.0.1" 41 | }, 42 | "engines": { 43 | "node": ">=18" 44 | }, 45 | "repository": { 46 | "type": "git", 47 | "url": "https://github.com/coderaiser/node-inly" 48 | }, 49 | "keywords": [ 50 | "extract", 51 | "zip", 52 | "gzip", 53 | "bzip2", 54 | "tar", 55 | "gz", 56 | "bz2", 57 | "tgz", 58 | "tar.gz", 59 | "tar.bz2", 60 | "tbz2", 61 | "unzip" 62 | ], 63 | "author": "coderaiser (http://coderaiser.github.io/)", 64 | "license": "MIT", 65 | "bugs": { 66 | "url": "https://github.com/coderaiser/node-inly/issues" 67 | }, 68 | "homepage": "https://github.com/coderaiser/node-inly", 69 | "publishConfig": { 70 | "access": "public" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /test/bzip2.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {once} = require('events'); 4 | const {tmpdir} = require('os'); 5 | const {sep, join} = require('path'); 6 | 7 | const { 8 | readFileSync, 9 | unlinkSync, 10 | rmdirSync, 11 | mkdtempSync, 12 | } = require('fs'); 13 | 14 | const test = require('supertape'); 15 | const inly = require('..'); 16 | 17 | const fixture = join(__dirname, 'fixture'); 18 | 19 | test('inly: extract: bz2', async (t) => { 20 | const to = mkdtempSync(tmpdir() + sep); 21 | const from = join(fixture, 'fixture.txt.bz2'); 22 | const extracter = inly(from, to); 23 | 24 | await once(extracter, 'end'); 25 | 26 | const pathUnpacked = join(to, 'fixture.txt'); 27 | const pathFixture = join(fixture, 'fixture.txt'); 28 | 29 | const fileUnpacked = readFileSync(pathUnpacked); 30 | const fileFixture = readFileSync(pathFixture); 31 | 32 | unlinkSync(pathUnpacked); 33 | rmdirSync(to); 34 | 35 | t.deepEqual(fileUnpacked, fileFixture, 'should extract file'); 36 | t.end(); 37 | }); 38 | 39 | test('inly: extract: bz2: error: empty', async (t) => { 40 | const error = 'archive is empty'; 41 | const from = join(fixture, 'empty.bz2'); 42 | const extracter = inly(from, 'ss'); 43 | 44 | const [e] = await once(extracter, 'error'); 45 | 46 | t.equal(e.message, error, 'should extract file'); 47 | t.end(); 48 | }); 49 | 50 | test('inly: extract: bz2: error: not found', async (t) => { 51 | const error = `ENOENT: no such file or directory, stat 'not-found.bz2'`; 52 | const extracter = inly('not-found.bz2', 'ss'); 53 | 54 | const [e] = await once(extracter, 'error'); 55 | 56 | t.equal(e.message, error, 'should extract file'); 57 | t.end(); 58 | }); 59 | 60 | test('inly: extract: bz2: error: EACCESS', async (t) => { 61 | const error = `ENOENT: no such file or directory, open 'not-found/fixture.txt'`; 62 | const from = join(fixture, 'fixture.txt.bz2'); 63 | const extracter = inly(from, 'not-found'); 64 | 65 | const [e] = await once(extracter, 'error'); 66 | 67 | t.equal(e.message, error, 'should extract file'); 68 | t.end(); 69 | }); 70 | -------------------------------------------------------------------------------- /test/fixture/empty.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderaiser/node-inly/264e59fe6b50d6117792607275a2b9309a868842/test/fixture/empty.bz2 -------------------------------------------------------------------------------- /test/fixture/empty.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderaiser/node-inly/264e59fe6b50d6117792607275a2b9309a868842/test/fixture/empty.gz -------------------------------------------------------------------------------- /test/fixture/empty.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderaiser/node-inly/264e59fe6b50d6117792607275a2b9309a868842/test/fixture/empty.tar -------------------------------------------------------------------------------- /test/fixture/empty.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderaiser/node-inly/264e59fe6b50d6117792607275a2b9309a868842/test/fixture/empty.zip -------------------------------------------------------------------------------- /test/fixture/fixture.tar: -------------------------------------------------------------------------------- 1 | fixture.txt0000664000175000017500000000000713042343312014321 0ustar coderaisercoderaiserhello 2 | 3 | -------------------------------------------------------------------------------- /test/fixture/fixture.tar.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderaiser/node-inly/264e59fe6b50d6117792607275a2b9309a868842/test/fixture/fixture.tar.bz2 -------------------------------------------------------------------------------- /test/fixture/fixture.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderaiser/node-inly/264e59fe6b50d6117792607275a2b9309a868842/test/fixture/fixture.tar.gz -------------------------------------------------------------------------------- /test/fixture/fixture.txt: -------------------------------------------------------------------------------- 1 | hello 2 | 3 | -------------------------------------------------------------------------------- /test/fixture/fixture.txt.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderaiser/node-inly/264e59fe6b50d6117792607275a2b9309a868842/test/fixture/fixture.txt.bz2 -------------------------------------------------------------------------------- /test/fixture/fixture.txt.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderaiser/node-inly/264e59fe6b50d6117792607275a2b9309a868842/test/fixture/fixture.txt.gz -------------------------------------------------------------------------------- /test/fixture/fixture.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderaiser/node-inly/264e59fe6b50d6117792607275a2b9309a868842/test/fixture/fixture.zip -------------------------------------------------------------------------------- /test/gzip.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {once} = require('events'); 4 | const {tmpdir} = require('os'); 5 | const {sep, join} = require('path'); 6 | 7 | const { 8 | readFileSync, 9 | unlinkSync, 10 | rmdirSync, 11 | mkdtempSync, 12 | } = require('fs'); 13 | 14 | const test = require('supertape'); 15 | const inly = require('..'); 16 | 17 | const fixture = join(__dirname, 'fixture'); 18 | 19 | test('inly: extract: gzip', async (t) => { 20 | const to = mkdtempSync(tmpdir() + sep); 21 | const from = join(fixture, 'fixture.txt.gz'); 22 | const extracter = inly(from, to); 23 | 24 | await once(extracter, 'end'); 25 | 26 | const pathUnpacked = join(to, 'fixture.txt'); 27 | const pathFixture = join(fixture, 'fixture.txt'); 28 | const fileUnpacked = readFileSync(pathUnpacked); 29 | const fileFixture = readFileSync(pathFixture); 30 | 31 | unlinkSync(pathUnpacked); 32 | rmdirSync(to); 33 | 34 | t.deepEqual(fileFixture, fileUnpacked, 'should extract file'); 35 | t.end(); 36 | }); 37 | 38 | test('inly: extract: gzip: error: empty', async (t) => { 39 | const error = 'archive is empty'; 40 | const from = join(fixture, 'empty.gz'); 41 | const extracter = inly(from, 'ss'); 42 | const [e] = await once(extracter, 'error'); 43 | 44 | t.equal(e.message, error, 'should extract file'); 45 | t.end(); 46 | }); 47 | 48 | test('inly: extract: gzip: error: not found', async (t) => { 49 | const error = `ENOENT: no such file or directory, stat 'not-found.gz'`; 50 | const extracter = inly('not-found.gz', 'ss'); 51 | const [e] = await once(extracter, 'error'); 52 | 53 | t.equal(e.message, error, 'should extract file'); 54 | t.end(); 55 | }); 56 | 57 | test('inly: extract: gzip: error: not found: gz', async (t) => { 58 | const error = `ENOENT: no such file or directory, stat 'not-found.gz'`; 59 | const extracter = inly('not-found.gz', 'ss'); 60 | const [e] = await once(extracter, 'error'); 61 | 62 | t.equal(e.message, error, 'should extract file'); 63 | t.end(); 64 | }); 65 | 66 | test('inly: extract: gzip: error: EACCESS', async (t) => { 67 | const error = `ENOENT: no such file or directory, open 'not-found/fixture.txt'`; 68 | const from = join(fixture, 'fixture.txt.gz'); 69 | const extracter = inly(from, 'not-found'); 70 | const [e] = await once(extracter, 'error'); 71 | 72 | t.equal(e.message, error, 'should extract file'); 73 | t.end(); 74 | }); 75 | -------------------------------------------------------------------------------- /test/inly.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const tryCatch = require('try-catch'); 4 | const {once} = require('events'); 5 | const {tmpdir} = require('os'); 6 | const {sep} = require('path'); 7 | const {mkdtempSync} = require('fs'); 8 | 9 | const test = require('supertape'); 10 | const inly = require('..'); 11 | 12 | const tmp = () => mkdtempSync(tmpdir() + sep); 13 | 14 | test('inly: extract: no args', (t) => { 15 | const [error] = tryCatch(inly); 16 | 17 | t.equal(error.message, 'from should be a string!', 'should throw when no args'); 18 | t.end(); 19 | }); 20 | 21 | test('inly: extract: to', (t) => { 22 | const [error] = tryCatch(inly, 'hello'); 23 | 24 | t.equal(error.message, 'to should be a string!', 'should throw when no to'); 25 | t.end(); 26 | }); 27 | 28 | test('inly: extract: error: file not found', async (t) => { 29 | const expect = `ENOENT: no such file or directory, open 'hello.zip'`; 30 | const extracter = inly('hello.zip', 'hello'); 31 | const [e] = await once(extracter, 'error'); 32 | 33 | t.equal(e.message, expect, 'should emit error when file not found'); 34 | t.end(); 35 | }); 36 | 37 | test('inly: extract: error: wrong file type', async (t) => { 38 | const expect = 'Not supported archive type: ".js"'; 39 | const extracter = inly(__filename, tmp()); 40 | 41 | const [{message}] = await once(extracter, 'error'); 42 | 43 | t.equal(message, expect, 'should emit error when can not extract'); 44 | t.end(); 45 | }); 46 | -------------------------------------------------------------------------------- /test/tar.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {once} = require('events'); 4 | const {tmpdir} = require('os'); 5 | const {sep, join} = require('path'); 6 | 7 | const { 8 | readFileSync, 9 | unlinkSync, 10 | rmdirSync, 11 | mkdtempSync, 12 | } = require('fs'); 13 | 14 | const test = require('supertape'); 15 | const extract = require('..'); 16 | const fixture = join(__dirname, 'fixture'); 17 | 18 | test('tar: extract: error: file not found', async (t) => { 19 | const expect = `ENOENT: no such file or directory, open 'hello.tar.gz'`; 20 | const extracter = extract('hello.tar.gz', 'hello'); 21 | const [e] = await once(extracter, 'error'); 22 | 23 | t.equal(e.message, expect, 'should emit error when file not found'); 24 | t.end(); 25 | }); 26 | 27 | test('tar: extract: error: tar', async (t) => { 28 | const expect = 'No entries found'; 29 | const from = join(fixture, 'empty.tar'); 30 | const extracter = extract(from, 'hello'); 31 | const [e] = await once(extracter, 'error'); 32 | 33 | t.equal(e.message, expect, 'should emit error when file not found'); 34 | t.end(); 35 | }); 36 | 37 | test('tar: extract: gz', async (t) => { 38 | const to = mkdtempSync(tmpdir() + sep); 39 | const from = join(fixture, 'fixture.tar.gz'); 40 | const extracter = extract(from, to); 41 | 42 | await once(extracter, 'end'); 43 | 44 | const pathUnpacked = join(to, 'fixture.txt'); 45 | const pathFixture = join(fixture, 'fixture.txt'); 46 | const fileUnpacked = readFileSync(pathUnpacked); 47 | const fileFixture = readFileSync(pathFixture); 48 | 49 | unlinkSync(pathUnpacked); 50 | rmdirSync(to); 51 | 52 | t.deepEqual(fileFixture, fileUnpacked, 'should extract file'); 53 | t.end(); 54 | }); 55 | 56 | test('tar: extract: bz2', async (t) => { 57 | const to = mkdtempSync(tmpdir() + sep); 58 | const from = join(fixture, 'fixture.tar.bz2'); 59 | const extracter = extract(from, to); 60 | 61 | await once(extracter, 'end'); 62 | 63 | const pathUnpacked = join(to, 'fixture.txt'); 64 | const pathFixture = join(fixture, 'fixture.txt'); 65 | const fileUnpacked = readFileSync(pathUnpacked); 66 | const fileFixture = readFileSync(pathFixture); 67 | 68 | unlinkSync(pathUnpacked); 69 | rmdirSync(to); 70 | 71 | t.deepEqual(fileFixture, fileUnpacked, 'should extract file'); 72 | t.end(); 73 | }); 74 | 75 | test('tar: extract: tar', async (t) => { 76 | const to = mkdtempSync(tmpdir() + sep); 77 | const fixture = join(__dirname, 'fixture'); 78 | const from = join(fixture, 'fixture.tar'); 79 | const extracter = extract(from, to); 80 | 81 | await once(extracter, 'end'); 82 | 83 | const pathUnpacked = join(to, 'fixture.txt'); 84 | const pathFixture = join(fixture, 'fixture.txt'); 85 | const fileUnpacked = readFileSync(pathUnpacked); 86 | const fileFixture = readFileSync(pathFixture); 87 | 88 | unlinkSync(pathUnpacked); 89 | rmdirSync(to); 90 | 91 | t.deepEqual(fileFixture, fileUnpacked, 'should extract file'); 92 | t.end(); 93 | }); 94 | -------------------------------------------------------------------------------- /test/zip.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {once} = require('events'); 4 | const {tmpdir} = require('os'); 5 | const {sep, join} = require('path'); 6 | 7 | const { 8 | readFileSync, 9 | unlinkSync, 10 | rmdirSync, 11 | mkdtempSync, 12 | } = require('fs'); 13 | 14 | const test = require('supertape'); 15 | const inly = require('..'); 16 | 17 | const fixture = join(__dirname, 'fixture'); 18 | 19 | test('inly: extract: zip', async (t) => { 20 | const to = mkdtempSync(tmpdir() + sep); 21 | const from = join(fixture, 'fixture.zip'); 22 | const extracter = inly(from, to); 23 | 24 | await once(extracter, 'end'); 25 | 26 | const pathUnpacked = join(to, 'fixture.txt'); 27 | const pathFixture = join(fixture, 'fixture.txt'); 28 | const fileUnpacked = readFileSync(pathUnpacked); 29 | const fileFixture = readFileSync(pathFixture); 30 | 31 | unlinkSync(pathUnpacked); 32 | rmdirSync(to); 33 | 34 | t.deepEqual(fileFixture, fileUnpacked, 'should extract file'); 35 | t.end(); 36 | }); 37 | 38 | test('inly: extract: zip: empty: error', async (t) => { 39 | const error = 'end of central directory record signature not found'; 40 | const from = join(fixture, 'empty.zip'); 41 | const extracter = inly(from, 'hello'); 42 | const [e] = await once(extracter, 'error'); 43 | 44 | t.equal(e.message, error, 'should extract file'); 45 | t.end(); 46 | }); 47 | --------------------------------------------------------------------------------