diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 index d0154a2..747d8fb --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,9 @@ node_modules dist .vscode **/*.tar.gz -!test.tar.gz **/*.zip +**/*.7z +!test.tar.gz +!test.zip +!test.7z coverage/ diff --git a/README.md b/README.md old mode 100755 new mode 100644 index bb1aca1..985e976 --- a/README.md +++ b/README.md @@ -82,8 +82,9 @@ await extractTarball('foo.tar.gz'); ## Roadmap -* Support more types of compression (`zip`, `bz2`, `7z`, etc.) +* Support more types of compression (~~`zip`~~, `bz2`, ~~`7z`~~, etc.) * Extraction goes along with those + * **NOTE:** There is no good package to work with 7zip files, so this is being put on the backlog. * Support extracting to a specific directory * Support extracting more than 1 file at a time diff --git a/__tests__/compress.spec.ts b/__tests__/compress.spec.ts new file mode 100644 index 0000000..ede0b43 --- /dev/null +++ b/__tests__/compress.spec.ts @@ -0,0 +1,52 @@ +import { readdir, remove, pathExists } from "fs-extra"; +import { dirname } from "path"; +import { compressFiles } from "../src/compress"; +import { ArchiveType } from "../src/utils"; + +describe('compress', () => { + beforeEach(() => { + process.chdir(dirname(__filename)); + }); + + afterEach(async () => { + const files = await readdir('.'); + files.filter(x => { + return !x.endsWith('spec.ts') && + !x.includes('test.tar.gz') && + !x.includes('test.zip') && + !x.includes('test.7z'); + }).forEach(async z => { + await remove(z); + }); + }); + + it('should compress a list of files to .tar.gz', async () => { + const out = 'app.spec.ts.tar.gz'; + await compressFiles(ArchiveType["tar.gz"], out, 'index.spec.ts'); + const ex = await pathExists(out); + expect(ex).toBeTruthy(); + await remove(out); + }); + + it('should fail if file does not exist', async () => { + try { + await compressFiles(ArchiveType["tar.gz"], 'out.tar.gz', 'foo.bar'); + } catch (err) { + expect(err).toBeDefined(); + } + }); + + it('should compress a list of files to .zip', async () => { + const out = 'index.spec.ts.zip'; + await compressFiles(ArchiveType.zip, out, 'index.spec.ts'); + const ex = await pathExists(out); + expect(ex).toBeTruthy(); + }); + + it('should compress a list of files to .7z', async () => { + const out = 'index.spec.ts.7z'; + await compressFiles(ArchiveType["7z"], out, 'index.spec.ts'); + const ex = await pathExists(out); + expect(ex).toBeTruthy(); + }); +}); \ No newline at end of file diff --git a/__tests__/extract.spec.ts b/__tests__/extract.spec.ts new file mode 100644 index 0000000..20a4c9c --- /dev/null +++ b/__tests__/extract.spec.ts @@ -0,0 +1,40 @@ +import { readdir, remove, pathExists } from "fs-extra"; +import { dirname } from "path"; +import { extractArchive } from "../src/extract"; +import { ArchiveType } from "../src/utils"; + +describe('extract', () => { + beforeEach(() => { + process.chdir(dirname(__filename)); + }); + + afterEach(async () => { + const files = await readdir('.'); + files.filter(x => { + return !x.endsWith('spec.ts') && + !x.includes('test.tar.gz') && + !x.includes('test.zip') && + !x.includes('test.7z'); + }).forEach(async z => { + await remove(z); + }); + }); + + it('should extract a single tarball', async () => { + await extractArchive(ArchiveType["tar.gz"], 'test.tar.gz'); + const ex = await pathExists('LICENSE'); + expect(ex).toBeTruthy(); + }); + + it('should extract a single .zip', async () => { + await extractArchive(ArchiveType.zip, 'test.zip'); + const ex = await pathExists('LICENSE'); + expect(ex).toBeTruthy(); + }); + + it('should extract a single .7z', async () => { + await extractArchive(ArchiveType["7z"], 'test.7z'); + const ex = await pathExists('LICENSE'); + expect(ex).toBeTruthy(); + }); +}); \ No newline at end of file diff --git a/__tests__/index.spec.ts b/__tests__/index.spec.ts index 6535a33..9a61679 100755 --- a/__tests__/index.spec.ts +++ b/__tests__/index.spec.ts @@ -1,165 +1,172 @@ import { readdir, remove, - pathExists, - createFileSync, - unlinkSync + pathExists } from 'fs-extra'; import { dirname } from 'path'; -import { compressFiles, extractArchive } from '../src/index'; import * as childProcess from 'child_process'; describe('jsbackup', () => { + let options = {}; beforeEach(() => { process.chdir(dirname(__filename)); + options = { cwd: __dirname, shell: true } }); afterEach(async () => { const files = await readdir('.'); - files.filter(x => !x.endsWith('spec.ts') && !x.includes('test.tar.gz')).forEach(async z => { + files.filter(x => { + return !x.endsWith('spec.ts') && + !x.includes('test.tar.gz') && + !x.includes('test.zip') && + !x.includes('test.7z'); + }).forEach(async z => { await remove(z); }); - await remove('test'); await remove('../test'); }); - it('should compress a list of files to .tar.gz', async () => { - const out = 'app.spec.ts.tar.gz'; - await compressFiles('tar.gz', out, 'index.spec.ts'); - const ex = await pathExists(out); - expect(ex).toBeTruthy(); - await remove(out); - }); - - it('should fail if file does not exist', async () => { - try { - await compressFiles('tar.gz', 'out.tar.gz', 'foo.bar'); - } catch (err) { - expect(err).toBeDefined(); - } - }); - - it('should extract a single tarball', async () => { - const out = 'testFile.tar.gz'; - const iN = 'test'; - createFileSync(iN); - await compressFiles('tar.gz', out, iN); - unlinkSync(iN); - await extractArchive('tar.gz', out); - const ex = await pathExists(out); - expect(ex).toBeTruthy(); - await remove(out); - await remove(iN); - }); - - it('should compress a list of files to .zip', async () => { - const out = 'index.spec.ts.zip'; - await compressFiles('zip', out, 'index.spec.ts'); - const ex = await pathExists(out); - expect(ex).toBeTruthy(); - await remove(out); + it('should fail if both compress and extract are provided', done => { + let err = ''; + const child = childProcess.spawn('node', ['../dist/index.js', '-x', 'f', '-c', 'f', '-t', 'f'], options); + child.stderr.on('data', chunk => err += chunk); + child.on('close', () => { + expect(err).toContain('Cannot have both \'x\' and \'c\'.'); + done(); + }); }); - it('should extract a single .zip', async () => { - const out = 'test.zip'; - const iN = 'test'; - createFileSync(iN); - await compressFiles('zip', out, iN); - unlinkSync(iN); - await extractArchive('zip', out); - const ex = await pathExists(out); - expect(ex).toBeTruthy(); - await remove(out); - await remove(iN); + it('should fail if invalid type', done => { + let err = ''; + const child = childProcess.spawn('node', ['../dist/index.js', '-x', 'f', '-t', 'f'], options); + child.stderr.on('data', chunk => err += chunk); + child.on('close', () => { + expect(err).toContain('You must give a valid type. Type given: f.'); + done(); + }); }); - describe('cli', () => { - let options = {}; - - beforeEach(() => options = { cwd: __dirname, shell: true }); - - it('should fail if both compress and extract are provided', done => { + describe('compress', () => { + it('should fail if no arguments', done => { let err = ''; - const child = childProcess.spawn('node', ['../dist/index.js', '-x', 'f', '-c', 'f', '-t', 'f'], options); + const child = childProcess.spawn('node', ['../dist/index.js', '-c', '-t', 'tar.gz'], options); child.stderr.on('data', chunk => err += chunk); child.on('close', () => { - expect(err).toContain('Cannot have both \'x\' and \'c\'.'); + expect(err).toContain('Not enough non-option arguments: got 0, need at least 1'); done(); }); }); - it('should fail if invalid type', done => { + it('should fail if one argument', done => { let err = ''; - const child = childProcess.spawn('node', ['../dist/index.js', '-x', 'f', '-t', 'f'], options); + const child = childProcess.spawn('node', ['../dist/index.js', '-c', 'f', '-t', 'tar.gz'], options); child.stderr.on('data', chunk => err += chunk); child.on('close', () => { - expect(err).toContain('You must give a valid type. Type given: f.'); + expect(err).toContain('Need at least 2 arguments for compression; receieved 1'); + done(); + }); + }); + + it('should compress files to tar', done => { + let err = ''; + let out = ''; + const child = childProcess.spawn('node', ['../dist/index.js', '-c', 'file.tar.gz', __filename, '-t', 'tar.gz'], options); + child.stderr.on('data', chunk => err += chunk); + child.stdout.on('data', chunk => out += chunk); + child.on('close', async () => { + expect(err).toBe(''); + expect(out).toContain('done'); + const exists = await pathExists('file.tar.gz'); + expect(exists).toBeTruthy(); + done(); + }); + }); + + it('should compress files to zip', done => { + let err = ''; + let out = ''; + const child = childProcess.spawn('node', ['../dist/index.js', '-c', 'file.zip', __filename, '-t', 'zip'], options); + child.stderr.on('data', chunk => err += chunk); + child.stdout.on('data', chunk => out += chunk); + child.on('close', async () => { + expect(err).toBe(''); + expect(out).toContain('done'); + const exists = await pathExists('file.zip'); + expect(exists).toBeTruthy(); done(); }); }); - describe('compress', () => { - it('should fail if no arguments', done => { - let err = ''; - const child = childProcess.spawn('node', ['../dist/index.js', '-c', '-t', 'tar.gz'], options); - child.stderr.on('data', chunk => err += chunk); - child.on('close', () => { - expect(err).toContain('Not enough non-option arguments: got 0, need at least 1'); - done(); - }); + it('should compress files to 7z', done => { + let err = ''; + let out = ''; + const child = childProcess.spawn('node', ['../dist/index.js', '-c', 'file.7z', __filename, '-t', '7z'], options); + child.stderr.on('data', chunk => err += chunk); + child.stdout.on('data', chunk => out += chunk); + child.on('close', async () => { + expect(err).toBe(''); + expect(out).toContain('done'); + const exists = await pathExists('file.7z'); + expect(exists).toBeTruthy(); + done(); }); + }); + }); - it('should fail if one argument', done => { - let err = ''; - const child = childProcess.spawn('node', ['../dist/index.js', '-c', 'f', '-t', 'tar.gz'], options); - child.stderr.on('data', chunk => err += chunk); - child.on('close', () => { - expect(err).toContain('Need at least 2 arguments for compression; receieved 1'); - done(); - }); + describe('extract', () => { + it('should fail if too many arguments', done => { + let err = ''; + const child = childProcess.spawn('node', ['../dist/index.js', '-x', 'f', 'd', '-t', 'tar.gz'], options); + child.stderr.on('data', chunk => err += chunk); + child.on('close', () => { + expect(err).toContain('Can only have one argument for extraction but receieved 2'); + done(); }); + }); - it('should compress files', done => { - let err = ''; - let out = ''; - const child = childProcess.spawn('node', ['../dist/index.js', '-c', 'file.tar.gz', __filename, '-t', 'tar.gz'], options); - child.stderr.on('data', chunk => err += chunk); - child.stdout.on('data', chunk => out += chunk); - child.on('close', async () => { - expect(err).toBe(''); - expect(out).toContain('done'); - const exists = await pathExists('file.tar.gz'); - expect(exists).toBeTruthy(); - done(); - }); + it('should extract tarball', done => { + let err = ''; + let out = ''; + const child = childProcess.spawn('node', ['../dist/index.js', '-x', 'test.tar.gz', '-t', 'tar.gz'], options); + child.stderr.on('data', chunk => err += chunk); + child.stdout.on('data', chunk => out += chunk); + child.on('close', async () => { + expect(err).toBe(''); + expect(out).toContain('done'); + const exists = await pathExists('LICENSE'); + expect(exists).toBeTruthy(); + done(); }); }); - describe('extract', () => { - it('should fail if too many arguments', done => { - let err = ''; - const child = childProcess.spawn('node', ['../dist/index.js', '-x', 'f', 'd', '-t', 'tar.gz'], options); - child.stderr.on('data', chunk => err += chunk); - child.on('close', () => { - expect(err).toContain('Can only have one argument for extraction but receieved 2'); - done(); - }); + it('should extract zip', done => { + let err = ''; + let out = ''; + const child = childProcess.spawn('node', ['../dist/index.js', '-x', 'test.zip', '-t', 'zip'], options); + child.stderr.on('data', chunk => err += chunk); + child.stdout.on('data', chunk => out += chunk); + child.on('close', async () => { + expect(err).toBe(''); + expect(out).toContain('done'); + const exists = await pathExists('LICENSE'); + expect(exists).toBeTruthy(); + done(); }); + }); - it('should extract files', done => { - let err = ''; - let out = ''; - const child = childProcess.spawn('node', ['../dist/index.js', '-x', 'test.tar.gz', '-t', 'tar.gz'], options); - child.stderr.on('data', chunk => err += chunk); - child.stdout.on('data', chunk => out += chunk); - child.on('close', async () => { - expect(err).toBe(''); - expect(out).toContain('done'); - const exists = await pathExists('LICENSE'); - expect(exists).toBeTruthy(); - done(); - }); + it('should extract 7z', done => { + let err = ''; + let out = ''; + const child = childProcess.spawn('node', ['../dist/index.js', '-x', 'test.7z', '-t', '7z'], options); + child.stderr.on('data', chunk => err += chunk); + child.stdout.on('data', chunk => out += chunk); + child.on('close', async () => { + expect(err).toBe(''); + expect(out).toContain('done'); + const exists = await pathExists('LICENSE'); + expect(exists).toBeTruthy(); + done(); }); }); }); diff --git a/__tests__/test.7z b/__tests__/test.7z new file mode 100644 index 0000000..61dc355 Binary files /dev/null and b/__tests__/test.7z differ diff --git a/__tests__/test.zip b/__tests__/test.zip new file mode 100644 index 0000000..fa0e916 Binary files /dev/null and b/__tests__/test.zip differ diff --git a/__tests__/utils.spec.ts b/__tests__/utils.spec.ts new file mode 100644 index 0000000..b25a7da --- /dev/null +++ b/__tests__/utils.spec.ts @@ -0,0 +1,26 @@ +import { readdir, remove } from "fs-extra"; +import { dirname } from "path"; +import { error, checkExists } from "../src/utils"; + +describe('utils', () => { + beforeEach(() => { + process.chdir(dirname(__filename)); + }); + + describe('error', () => { + it('should throw error with the given string', () => { + expect(error('error')).toThrowError('error'); + }); + }); + + describe('checkExists', () => { + it('should throw error if path does not exist', () => { + const file = 'nonexistentfile.txt'; + expect(checkExists(file)).toThrowError(`jsbackup: ${file} does not exist.`); + }); + + it('should not throw error if file exists', () => { + expect(checkExists(__filename)).not.toThrow(); + }); + }) +}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index dbc0ff9..083416a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,52 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "7z-extract": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/7z-extract/-/7z-extract-0.0.3.tgz", + "integrity": "sha1-JWRpEmzuAlh9Y4u8npL+QKe1qVw=", + "requires": { + "fs-extra": "^0.30.0", + "lodash": "^4.13.1", + "simple-cli-parser": "0.0.3", + "uid-safe": "^2.1.1" + }, + "dependencies": { + "fs-extra": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", + "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0", + "path-is-absolute": "^1.0.0", + "rimraf": "^2.2.8" + } + }, + "jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "7zip-bin": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.0.3.tgz", + "integrity": "sha512-GLyWIFBbGvpKPGo55JyRZAo4lVbnBiD52cKlw/0Vt+wnmKvWJkpZvsjVoaIolyBXDeAQKSicRtqFNPem9w0WYA==" + }, "@babel/code-frame": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", @@ -782,9 +828,9 @@ }, "dependencies": { "acorn": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.0.tgz", - "integrity": "sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", + "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", "dev": true } } @@ -1029,8 +1075,7 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "base": { "version": "0.11.2", @@ -1165,7 +1210,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1470,8 +1514,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "configstore": { "version": "3.1.2", @@ -2143,8 +2186,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "2.1.2", @@ -2198,7 +2240,6 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -2433,7 +2474,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -2442,8 +2482,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "1.3.5", @@ -3740,6 +3779,14 @@ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, + "klaw": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", + "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", + "requires": { + "graceful-fs": "^4.1.9" + } + }, "kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -3782,8 +3829,7 @@ "lodash": { "version": "4.17.15", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, "lodash.memoize": { "version": "4.1.2", @@ -3910,7 +3956,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -4214,7 +4259,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -4307,8 +4351,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { "version": "1.0.2", @@ -4519,6 +4562,11 @@ "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", "dev": true }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, "rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -4977,6 +5025,11 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, + "simple-cli-parser": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/simple-cli-parser/-/simple-cli-parser-0.0.3.tgz", + "integrity": "sha1-teJeJbfNr9ctxdnz+/6wOL9Do1k=" + }, "sisteransi": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.4.tgz", @@ -5678,6 +5731,14 @@ "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", "dev": true }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "~1.0.0" + } + }, "undefsafe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", @@ -6067,8 +6128,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write-file-atomic": { "version": "3.0.3", diff --git a/package.json b/package.json old mode 100755 new mode 100644 index 7284bed..a5af478 --- a/package.json +++ b/package.json @@ -30,6 +30,8 @@ "jsbackup": "./dist/index.js" }, "dependencies": { + "7z-extract": "0.0.3", + "7zip-bin": "^5.0.3", "adm-zip": "^0.4.14", "chalk": "^3.0.0", "fs-extra": "^8.1.0", @@ -59,12 +61,10 @@ "transform": { "^.+\\.tsx?$": "ts-jest" }, - "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", + "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(js?|ts?)$", "moduleFileExtensions": [ "ts", - "tsx", "js", - "jsx", "json", "node" ] diff --git a/src/compress.ts b/src/compress.ts new file mode 100644 index 0000000..a3b523f --- /dev/null +++ b/src/compress.ts @@ -0,0 +1,68 @@ +import { path7za } from '7zip-bin'; +import { dirname, extname } from 'path'; +import { create as createTar } from 'tar'; +import AdmZip from 'adm-zip'; +import { existsSync, mkdirp } from 'fs-extra'; +import { error, ArchiveType, checkExists } from './utils'; + +async function compressZip(outfile: string, files: string[]): Promise { + const extension = extname(outfile); + if (extension !== '.zip') { + error(`Invalid extension given to compress to .zip. Expected .zip, received: ${extension}.`); + } + + const zip = new AdmZip(); + files.forEach(file => zip.addLocalFile(file)); + zip.writeZip(outfile); +} + +async function compressTarGz(outfile: string, files: string[]): Promise { + const parts = outfile.split('.'); + const gz = parts.pop(); + const tar2 = parts.pop(); + if (gz !== 'gz' && tar2 !== 'tar') { + error(`Invalid extension given to compress to tar.gz. Expected tar.gz, received: ${tar2}.${gz}.`); + } + + await createTar({ + gzip: true, + file: outfile + }, files); +} + +async function compress7z(outfile: string, files: string[]): Promise { + if (extname(outfile) !== '.7z') { + error(`Invalid extension given to compress to .7z. Expected .7z, received ${extname(outfile)}`); + } + + await new Zip().add(outfile, files, { + $bin: path7za, + noArchiveOnFail: true + }); +} + +/** + * Compresses `files` into `outfile`, and creates necessary + * top-level directories in the path contained in `outfile` + * @param files files to compress + * @param outfile name of the outputted archive + */ +export async function compressFiles(type: ArchiveType, outfile: string, ...files: string[]): Promise { + if (!existsSync(dirname(outfile))) { + await mkdirp(dirname(outfile)); + } + + files.forEach(checkExists); + + switch (type) { + case ArchiveType['tar.gz']: + return await compressTarGz(outfile, files); + case ArchiveType.zip: + return await compressZip(outfile, files); + case ArchiveType['7z']: + return await compress7z(outfile, files); + default: + const extension = extname(outfile); + throw new Error(`jsbackup: File type ${extension} not supported.`); + } +} diff --git a/src/extract.ts b/src/extract.ts new file mode 100644 index 0000000..510e207 --- /dev/null +++ b/src/extract.ts @@ -0,0 +1,50 @@ +import { path7za } from '7zip-bin'; +import { dirname, extname } from 'path'; +import { extract as extractTar } from 'tar'; +import AdmZip from 'adm-zip'; +import { checkExists, ArchiveType } from './utils'; +import * as Zip from '7z-extract'; + +/** + * Extracts the given tarball + * @param file tarball to extract + */ +async function extractTarball(file: string): Promise { + await extractTar({ + file: file + }); +} + +async function extractZip(file: string): Promise { + checkExists(file); + + const zip = new AdmZip(file); + zip.extractAllTo(file.split('.zip')[0], true); +} + +async function extract7z(file: string): Promise { + await Zip.extractFull(file, dirname(file), { + $bin: path7za, + recursive: true + }); +} + +/** + * Extracts the given file + * @param file tarball to extract + */ +export async function extractArchive(type: ArchiveType, file: string): Promise { + checkExists(file); + + switch (type) { + case 'tar.gz': + return await extractTarball(file); + case 'zip': + return await extractZip(file); + case '7z': + return await extract7z(file); + default: + const extension = extname(file); + throw new Error(`jsbackup: File type '${extension}' not supported.`); + } +} diff --git a/src/index.ts b/src/index.ts index 4a9df1f..26e0122 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,46 +1,22 @@ #!/usr/bin/env node -import { existsSync, mkdirp } from 'fs-extra'; -import { extract as extractTar, create as createTar } from 'tar'; -import { dirname } from 'path'; import * as yargs from 'yargs'; -import * as AdmZip from 'adm-zip'; import { bold } from 'chalk'; - -/** - * All types of archives this supports - */ -export type ArchiveType = 'zip' | 'tar.gz'; - -/** - * Writes an error to the console and then exits with code 1 - * @param err the error to write to the console - */ -const error = (err: string) => { - throw new Error(err); -}; - -/** - * checks if a file exists and if not, throws an error. - * @param file the full path to the file to check - */ -const checkExists = (file: string) => { - if (!existsSync(file)) { - throw new Error(`jsbackup: ${file} does not exist.`); - } -}; +import { error, ArchiveType } from './utils'; +import { compressFiles } from './compress'; +import { extractArchive } from './extract'; async function commandLine(): Promise { const argv = yargs - .usage('Usage: jsbackup -t