Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add extractBuffer() function #122

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@ declare namespace extract {
defaultFileMode?: number;
onEntry?: (entry: Entry, zipfile: ZipFile) => void;
}

type ExtractBuffer = (buffer: Buffer, opts: extract.Options) => Promise<void>;
type ExtractFile = (zipPath: string, opts: extract.Options) => Promise<void>;
}

declare function extract(
zipPath: string,
opts: extract.Options,
): Promise<void>;
declare const extract: extract.ExtractFile & {
extractBuffer: extract.ExtractBuffer,
extractFile: extract.ExtractFile
};

export = extract;
59 changes: 41 additions & 18 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,33 @@ const { promisify } = require('util')
const stream = require('stream')
const yauzl = require('yauzl')

const fromBuffer = promisify(yauzl.fromBuffer)
const openZip = promisify(yauzl.open)
const pipeline = promisify(stream.pipeline)

const yauzlOpts = { lazyEntries: true }

class Extractor {
constructor (zipPath, opts) {
this.zipPath = zipPath
constructor (opts) {
this.opts = opts
}

async extract () {
debug('opening', this.zipPath, 'with opts', this.opts)
async extractFile (zipPath) {
debug('extracting file', zipPath, 'with opts', this.opts)
const zipfile = await openZip(zipPath, yauzlOpts)
return this.extract(zipfile)
}

async extractBuffer (buffer) {
debug('extracting buffer with opts', this.opts)
const zipfile = await fromBuffer(buffer, yauzlOpts)
return this.extract(zipfile)
}

this.zipfile = await openZip(this.zipPath, { lazyEntries: true })
async extract (zipfile) {
await this.ensureDir()

this.zipfile = zipfile
this.canceled = false

return new Promise((resolve, reject) => {
Expand All @@ -29,11 +43,9 @@ class Extractor {
})
this.zipfile.readEntry()

this.zipfile.on('close', () => {
if (!this.canceled) {
debug('zip extraction complete')
resolve()
}
this.zipfile.on('end', () => {
debug('zip extraction complete')
resolve()
})

this.zipfile.on('entry', async entry => {
Expand Down Expand Up @@ -158,16 +170,27 @@ class Extractor {

return mode
}
}

module.exports = async function (zipPath, opts) {
debug('creating target directory', opts.dir)
async ensureDir () {
debug('creating target directory', this.opts.dir)

if (!path.isAbsolute(opts.dir)) {
throw new Error('Target directory is expected to be absolute')
if (!path.isAbsolute(this.opts.dir)) {
throw new Error('Target directory is expected to be absolute')
}

await fs.mkdir(this.opts.dir, { recursive: true })
this.opts.dir = await fs.realpath(this.opts.dir)
}
}

await fs.mkdir(opts.dir, { recursive: true })
opts.dir = await fs.realpath(opts.dir)
return new Extractor(zipPath, opts).extract()
async function extractFile (filename, opts) {
return new Extractor(opts).extractFile(filename)
}

async function extractBuffer (buffer, opts) {
return new Extractor(opts).extractBuffer(buffer)
}

module.exports = extractFile
module.exports.extractBuffer = extractBuffer
module.exports.extractFile = extractFile
19 changes: 18 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,31 @@ const extract = require('extract-zip')

async function main () {
try {
await extract(source, { dir: target })
await extract(filename, { dir: target })
console.log('Extraction complete')
} catch (err) {
// handle any errors
}
}
```

The `extractBuffer` helper function can be used if the zipped data is
not stored in a file. It has the same `source` and `options` parameters:

```javascript
const extract = require('extract-zip')

async function main () {
try {
await extract.extractBuffer(buffer, { dir: target })
console.log('Extraction complete')
} catch (err) {
// handle any errors
}
}
```


### Options

- `dir` (required) - the path to the directory where the extracted files are written
Expand Down
12 changes: 12 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ async function tempExtract (t, suffix, zipPath) {
return dirPath
}

async function tempExtractBuf (t, suffix, buffer) {
const dirPath = await mkdtemp(t, suffix)
await extract.extractBuffer(buffer, { dir: dirPath })
return dirPath
}

async function pathExists (t, pathToCheck, message) {
const exists = await fs.pathExists(pathToCheck)
t.true(exists, message)
Expand All @@ -45,6 +51,12 @@ test('files', async t => {
await pathExists(t, path.join(dirPath, 'cats', 'gJqEYBs.jpg'), 'file created')
})

test('buffer', async t => {
const catsBuf = await fs.readFile(catsZip)
const dirPath = await tempExtractBuf(t, 'files', catsBuf)
await pathExists(t, path.join(dirPath, 'cats', 'gJqEYBs.jpg'), 'file created')
})

test('symlinks', async t => {
const dirPath = await tempExtract(t, 'symlinks', catsZip)
const symlink = path.join(dirPath, 'cats', 'orange_symlink')
Expand Down