code:
# yazl

[![Build Status](https://travis-ci.com/thejoshwolfe/yazl.svg?branch=master)](https://travis-ci.com/thejoshwolfe/yazl)
[![Coverage Status](https://coveralls.io/repos/github/thejoshwolfe/yazl/badge.svg?branch=master)](https://coveralls.io/github/thejoshwolfe/yazl?branch=master)

yet another zip library for node. For unzipping, see
[yauzl](https://github.com/thejoshwolfe/yauzl).

Design principles:

 * Don't block the JavaScript thread.
   Use and provide async APIs.
 * Keep memory usage under control.
   Don't attempt to buffer entire files in RAM at once.
 * Prefer to open input files one at a time than all at once.
   This is slightly suboptimal for time performance,
   but avoids OS-imposed limits on the number of simultaneously open file handles.

## Usage

```js
var yazl = require("yazl");

var zipfile = new yazl.ZipFile();
zipfile.addFile("file1.txt", "file1.txt");
// (add only files, not directories)
zipfile.addFile("path/to/file.txt", "path/in/zipfile.txt");
// pipe() can be called any time after the constructor
zipfile.outputStream.pipe(fs.createWriteStream("output.zip")).on("close", function() {
  console.log("done");
});
// alternate apis for adding files:
zipfile.addReadStream(process.stdin, "stdin.txt");
zipfile.addBuffer(Buffer.from("hello"), "hello.txt");
// call end() after all the files have been added
zipfile.end();
```

## API

### Class: ZipFile

#### new ZipFile()

No parameters.
Nothing can go wrong.

#### addFile(realPath, metadataPath, [options])

Adds a file from the file system at `realPath` into the zipfile as `metadataPath`.
Typically `metadataPath` would be calculated as `path.relative(root, realPath)`.
Unzip programs would extract the file from the zipfile as `metadataPath`.
`realPath` is not stored in the zipfile.

A valid `metadataPath` must not be blank.
If a `metadataPath` contains `"\\"` characters, they will be replaced by `"/"` characters.
After this substitution, a valid `metadataPath` must not start with `"/"` or `/[A-Za-z]:\//`,
and must not contain `".."` path segments.
File paths must not end with `"/"`, but see `addEmptyDirectory()`.
After UTF-8 encoding, `metadataPath` must be at most `0xffff` bytes in length.

`options` may be omitted or null and has the following structure and default values:

```js
{
  mtime: stats.mtime,
  mode: stats.mode,
  compress: true,
  forceZip64Format: false,
  fileComment: "", // or a UTF-8 Buffer
}
```

Use `mtime` and/or `mode` to override the values
that would normally be obtained by the `fs.Stats` for the `realPath`.
The mode is the unix permission bits and file type.
The mtime and mode are stored in the zip file in the fields "last mod file time",
"last mod file date", and "external file attributes".
yazl does not store group and user ids in the zip file.

If `compress` is `true`, the file data will be deflated (compression method 8).
If `compress` is `false`, the file data will be stored (compression method 0).

If `forceZip64Format` is `true`, yazl will use ZIP64 format in this entry's Data Descriptor
and Central Directory Record regardless of if it's required or not (this may be useful for testing.).
Otherwise, yazl will use ZIP64 format where necessary.

If `fileComment` is a `string`, it will be encoded with UTF-8.
If `fileComment` is a `Buffer`, it should be a UTF-8 encoded string.
In UTF-8, `fileComment` must be at most `0xffff` bytes in length.
This becomes the "file comment" field in this entry's central directory file header.

Internally, `fs.stat()` is called immediately in the `addFile` function,
and `fs.createReadStream()` is used later when the file data is actually required.
Throughout adding and encoding `n` files with `addFile()`,
the number of simultaneous open files is `O(1)`, probably just 1 at a time.

#### addReadStream(readStream, metadataPath, [options])

Adds a file to the zip file whose content is read from `readStream`.
See `addFile()` for info about the `metadataPath` parameter.
`options` may be omitted or null and has the following structure and default values:

```js
{
  mtime: new Date(),
  mode: 0o100664,
  compress: true,
  forceZip64Format: false,
  fileComment: "", // or a UTF-8 Buffer
  size: 12345, // example value
}
```

See `addFile()` for the meaning of `mtime`, `mode`, `compress`, `forceZip64Format`, and `fileComment`.
If `size` is given, it will be checked against the actual number of bytes in the `readStream`,
and an error will be emitted if there is a mismatch.

Note that yazl will `.pipe()` data from `readStream`, so be careful using `.on('data')`.
In certain versions of node, `.on('data')` makes `.pipe()` behave incorrectly.

#### addBuffer(buffer, metadataPath, [options])

Adds a file to the zip file whose content is `buffer`.
See below for info on the limitations on the size of `buffer`.
See `addFile()` for info about the `metadataPath` parameter.
`options` may be omitted or null and has the following structure and default values:

```js
{
  mtime: new Date(),
  mode: 0o100664,
  compress: true,
  forceZip64Format: false,
  fileComment: "", // or a UTF-8 Buffer
}
```

See `addFile()` for the meaning of `mtime`, `mode`, `compress`, `forceZip64Format`, and `fileComment`.

This method has the unique property that General Purpose Bit `3` will not be used in the Local File Header.
This doesn't matter for unzip implementations that conform to the Zip File Spec.
However, 7-Zip 9.20 has a known bug where General Purpose Bit `3` is declared an unsupported compression method
(note that it really has nothing to do with the compression method.).
See [issue #11](https://github.com/thejoshwolfe/yazl/issues/11).
If you would like to create zip files that 7-Zip 9.20 can understand,
you must use `addBuffer()` instead of `addFile()` or `addReadStream()` for all entries in the zip file
(and `addEmptyDirectory()` is fine too).

Note that even when yazl provides the file sizes in the Local File Header,
yazl never uses ZIP64 format for Local File Headers due to the size limit on `buffer` (see below).

##### Size limitation on buffer

In order to require the ZIP64 format for a local file header,
the provided `buffer` parameter would need to exceed `0xfffffffe` in length.
Alternatively, the `buffer` parameter might not exceed `0xfffffffe` in length,
but zlib compression fails to compress the buffer and actually inflates the data to more than `0xfffffffe` in length.
Both of these scenarios are not allowed by yazl, and those are enforced by a size limit on the `buffer` parameter.

According to [this zlib documentation](http://www.zlib.net/zlib_tech.html),
the worst case compression results in "an expansion of at most 13.5%, plus eleven bytes".
Furthermore, some configurations of Node.js impose a size limit of `0x3fffffff` on every `Buffer` object.
Running this size through the worst case compression of zlib still produces a size less than `0xfffffffe` bytes,

Therefore, yazl enforces that the provided `buffer` parameter must be at most `0x3fffffff` bytes long.

#### addEmptyDirectory(metadataPath, [options])

Adds an entry to the zip file that indicates a directory should be created,
even if no other items in the zip file are contained in the directory.
This method is only required if the zip file is intended to contain an empty directory.

See `addFile()` for info about the `metadataPath` parameter.
If `metadataPath` does not end with a `"/"`, a `"/"` will be appended.

`options` may be omitted or null and has the following structure and default values:

```js
{
  mtime: new Date(),
  mode: 040775,
}
```

See `addFile()` for the meaning of `mtime` and `mode`.

#### end([options], [finalSizeCallback])

Indicates that no more files will be added via `addFile()`, `addReadStream()`, or `addBuffer()`,
and causes the eventual close of `outputStream`.

`options` may be omitted or null and has the following structure and default values:

```js
{
  forceZip64Format: false,
  comment: "", // or a CP437 Buffer
}
```

If `forceZip64Format` is `true`, yazl will include the ZIP64 End of Central Directory Locator
and ZIP64 End of Central Directory Record regardless of whether or not they are required
(this may be useful for testing.).
Otherwise, yazl will include these structures if necessary.

If `comment` is a `string`, it will be encoded with CP437.
If `comment` is a `Buffer`, it should be a CP437 encoded string.
`comment` must be at most `0xffff` bytes in length and must not include the byte sequence `[0x50,0x4b,0x05,0x06]`.
This becomes the ".ZIP file comment" field in the end of central directory record.
Note that in practice, most zipfile readers interpret this field in UTF-8 instead of CP437.
If your string uses only codepoints in the range `0x20...0x7e`
(printable ASCII, no whitespace except for sinlge space `' '`),
then UTF-8 and CP437 (and ASCII) encodings are all identical.
This restriction is recommended for maxium compatibility.
To use UTF-8 encoding at your own risk, pass a `Buffer` into this function; it will not be validated.

If specified and non-null, `finalSizeCallback` is given the parameters `(finalSize)`
sometime during or after the call to `end()`.
`finalSize` is of type `Number` and can either be `-1`
or the guaranteed eventual size in bytes of the output data that can be read from `outputStream`.

Note that `finalSizeCallback` is usually called well before `outputStream` has piped all its data;
this callback does not mean that the stream is done.

If `finalSize` is `-1`, it means means the final size is too hard to guess before processing the input file data.
This will happen if and only if the `compress` option is `true` on any call to `addFile()`, `addReadStream()`, or `addBuffer()`,
or if `addReadStream()` is called and the optional `size` option is not given.
In other words, clients should know whether they're going to get a `-1` or a real value
by looking at how they are using this library.

The call to `finalSizeCallback` might be delayed if yazl is still waiting for `fs.Stats` for an `addFile()` entry.
If `addFile()` was never called, `finalSizeCallback` will be called during the call to `end()`.
It is not required to start piping data from `outputStream` before `finalSizeCallback` is called.
`finalSizeCallback` will be called only once, and only if this is the first call to `end()`.

#### outputStream

A readable stream that will produce the contents of the zip file.
It is typical to pipe this stream to a writable stream created from `fs.createWriteStream()`.

Internally, large amounts of file data are piped to `outputStream` using `pipe()`,
which means throttling happens appropriately when this stream is piped to a slow destination.

Data becomes available in this stream soon after calling one of `addFile()`, `addReadStream()`, or `addBuffer()`.
Clients can call `pipe()` on this stream at any time,
such as immediately after getting a new `ZipFile` instance, or long after calling `end()`.

This stream will remain open while you add entries until you `end()` the zip file.

As a reminder, be careful using both `.on('data')` and `.pipe()` with this stream.
In certain versions of node, you cannot use both `.on('data')` and `.pipe()` successfully.

### dateToDosDateTime(jsDate)

`jsDate` is a `Date` instance.
Returns `{date: date, time: time}`, where `date` and `time` are unsigned 16-bit integers.

## Regarding ZIP64 Support

yazl automatically uses ZIP64 format to support files and archives over `2^32 - 2` bytes (~4GB) in size
and to support archives with more than `2^16 - 2` (65534) files.
(See the `forceZip64Format` option in the API above for more control over this behavior.)
ZIP64 format is necessary to exceed the limits inherent in the original zip file format.

ZIP64 format is supported by most popular zipfile readers, but not by all of them.
Notably, the Mac Archive Utility does not understand ZIP64 format (as of writing this),
and will behave very strangely when presented with such an archive.

## Output Structure

The Zip File Spec leaves a lot of flexibility up to the zip file creator.
This section explains and justifies yazl's interpretation and decisions regarding this flexibility.

This section is probably not useful to yazl clients,
but may be interesting to unzip implementors and zip file enthusiasts.

### Disk Numbers

All values related to disk numbers are `0`,
because yazl has no multi-disk archive support.
(The exception being the Total Number of Disks field in
the ZIP64 End of Central Directory Locator, which is always `1`.)

### Version Made By

Always `0x033f == (3 << 8) | 63`, which means UNIX (3)
and made from the spec version 6.3 (63).

Note that the "UNIX" has implications in the External File Attributes.

### Version Needed to Extract

Usually `20`, meaning 2.0. This allows filenames and file comments to be UTF-8 encoded.

When ZIP64 format is used, some of the Version Needed to Extract values will be `45`, meaning 4.5.
When this happens, there may be a mix of `20` and `45` values throughout the zipfile.

### General Purpose Bit Flag

Bit `11` is always set.
Filenames (and file comments) are always encoded in UTF-8, even if the result is indistinguishable from ascii.

Bit `3` is usually set in the Local File Header.
To support both a streaming input and streaming output api,
it is impossible to know the crc32 before processing the file data.
When bit `3` is set, data Descriptors are given after each file data with this information, as per the spec.
But remember a complete metadata listing is still always available in the central directory record,
so if unzip implementations are relying on that, like they should,
none of this paragraph will matter anyway.
Even so, some popular unzip implementations do not follow the spec.
The Mac Archive Utility requires Data Descriptors to include the optional signature,
so yazl includes the optional data descriptor signature.
When bit `3` is not used, the Mac Archive Utility requires there to be no data descriptor, so yazl skips it in that case.
Additionally, 7-Zip 9.20 does not seem to support bit `3` at all
(see [issue #11](https://github.com/thejoshwolfe/yazl/issues/11)).

All other bits are unset.

### Internal File Attributes

Always `0`.
The "apparently an ASCII or text file" bit is always unset meaning "apparently binary".
This kind of determination is outside the scope of yazl,
and is probably not significant in any modern unzip implementation.

### External File Attributes

Always `stats.mode << 16`.
This is apparently the convention for "version made by" = `0x03xx` (UNIX).

Note that for directory entries (see `addEmptyDirectory()`),
it is conventional to use the lower 8 bits for the MS-DOS directory attribute byte.
However, the spec says this is only required if the Version Made By is DOS,
so this library does not do that.

### Directory Entries

When adding a `metadataPath` such as `"parent/file.txt"`, yazl does not add a directory entry for `"parent/"`,
because file entries imply the need for their parent directories.
Unzip clients seem to respect this style of pathing,
and the zip file spec does not specify what is standard in this regard.

In order to create empty directories, use `addEmptyDirectory()`.

### Size of Local File and Central Directory Entry Metadata

The spec recommends that "The combined length of any directory record and [the file name,
extra field, and comment fields] should not generally exceed 65,535 bytes".
yazl makes no attempt to respect this recommendation.
Instead, each of the fields is limited to 65,535 bytes due to the length of each being encoded as an unsigned 16 bit integer.

## Change History

 * 2.5.1
   * Fix support for old versions of Node and add official support for Node versions 0.10, 4, 6, 8, 10. [pull #49](https://github.com/thejoshwolfe/yazl/pull/49)
 * 2.5.0
   * Add support for `comment` and `fileComment`. [pull #44](https://github.com/thejoshwolfe/yazl/pull/44)
   * Avoid `new Buffer()`. [pull #43](https://github.com/thejoshwolfe/yazl/pull/43)
 * 2.4.3
   * Clarify readme. [pull #33](https://github.com/thejoshwolfe/yazl/pull/33)
 * 2.4.2
   * Remove octal literals to make yazl compatible with strict mode. [pull #28](https://github.com/thejoshwolfe/yazl/pull/28)
 * 2.4.1
   * Fix Mac Archive Utility compatibility issue. [issue #24](https://github.com/thejoshwolfe/yazl/issues/24)
 * 2.4.0
   * Add ZIP64 support. [issue #6](https://github.com/thejoshwolfe/yazl/issues/6)
 * 2.3.1
   * Remove `.npmignore` from npm package. [pull #22](https://github.com/thejoshwolfe/yazl/pull/22)
 * 2.3.0
   * `metadataPath` can have `\` characters now; they will be replaced with `/`. [issue #18](https://github.com/thejoshwolfe/yazl/issues/18)
 * 2.2.2
   * Fix 7-Zip compatibility issue. [pull request #17](https://github.com/thejoshwolfe/yazl/pull/17)
 * 2.2.1
   * Fix Mac Archive Utility compatibility issue. [issue #14](https://github.com/thejoshwolfe/yazl/issues/14)
 * 2.2.0
   * Avoid using general purpose bit 3 for `addBuffer()` calls. [issue #13](https://github.com/thejoshwolfe/yazl/issues/13)
 * 2.1.3
   * Fix bug when only addBuffer() and end() are called. [issue #12](https://github.com/thejoshwolfe/yazl/issues/12)
 * 2.1.2
   * Fixed typo in parameter validation. [pull request #10](https://github.com/thejoshwolfe/yazl/pull/10)
 * 2.1.1
   * Fixed stack overflow when using addBuffer() in certain ways. [issue #9](https://github.com/thejoshwolfe/yazl/issues/9)
 * 2.1.0
   * Added `addEmptyDirectory()`.
   * `options` is now optional for `addReadStream()` and `addBuffer()`.
 * 2.0.0
   * Initial release.


code:
var fs = require("fs");
var Transform = require("stream").Transform;
var PassThrough = require("stream").PassThrough;
var zlib = require("zlib");
var util = require("util");
var EventEmitter = require("events").EventEmitter;
var crc32 = require("buffer-crc32");

exports.ZipFile = ZipFile;
exports.dateToDosDateTime = dateToDosDateTime;

util.inherits(ZipFile, EventEmitter);
function ZipFile() {
  this.outputStream = new PassThrough();
  this.entries = [];
  this.outputStreamCursor = 0;
  this.ended = false; // .end() sets this
  this.allDone = false; // set when we've written the last bytes
  this.forceZip64Eocd = false; // configurable in .end()
}

ZipFile.prototype.addFile = function(realPath, metadataPath, options) {
  var self = this;
  metadataPath = validateMetadataPath(metadataPath, false);
  if (options == null) options = {};

  var entry = new Entry(metadataPath, false, options);
  self.entries.push(entry);
  fs.stat(realPath, function(err, stats) {
    if (err) return self.emit("error", err);
    if (!stats.isFile()) return self.emit("error", new Error("not a file: " + realPath));
    entry.uncompressedSize = stats.size;
    if (options.mtime == null) entry.setLastModDate(stats.mtime);
    if (options.mode == null) entry.setFileAttributesMode(stats.mode);
    entry.setFileDataPumpFunction(function() {
      var readStream = fs.createReadStream(realPath);
      entry.state = Entry.FILE_DATA_IN_PROGRESS;
      readStream.on("error", function(err) {
        self.emit("error", err);
      });
      pumpFileDataReadStream(self, entry, readStream);
    });
    pumpEntries(self);
  });
};

ZipFile.prototype.addReadStream = function(readStream, metadataPath, options) {
  var self = this;
  metadataPath = validateMetadataPath(metadataPath, false);
  if (options == null) options = {};
  var entry = new Entry(metadataPath, false, options);
  self.entries.push(entry);
  entry.setFileDataPumpFunction(function() {
    entry.state = Entry.FILE_DATA_IN_PROGRESS;
    pumpFileDataReadStream(self, entry, readStream);
  });
  pumpEntries(self);
};

ZipFile.prototype.addBuffer = function(buffer, metadataPath, options) {
  var self = this;
  metadataPath = validateMetadataPath(metadataPath, false);
  if (buffer.length > 0x3fffffff) throw new Error("buffer too large: " + buffer.length + " > " + 0x3fffffff);
  if (options == null) options = {};
  if (options.size != null) throw new Error("options.size not allowed");
  var entry = new Entry(metadataPath, false, options);
  entry.uncompressedSize = buffer.length;
  entry.crc32 = crc32.unsigned(buffer);
  entry.crcAndFileSizeKnown = true;
  self.entries.push(entry);
  if (!entry.compress) {
    setCompressedBuffer(buffer);
  } else {
    zlib.deflateRaw(buffer, function(err, compressedBuffer) {
      setCompressedBuffer(compressedBuffer);
    });
  }
  function setCompressedBuffer(compressedBuffer) {
    entry.compressedSize = compressedBuffer.length;
    entry.setFileDataPumpFunction(function() {
      writeToOutputStream(self, compressedBuffer);
      writeToOutputStream(self, entry.getDataDescriptor());
      entry.state = Entry.FILE_DATA_DONE;

      // don't call pumpEntries() recursively.
      // (also, don't call process.nextTick recursively.)
      setImmediate(function() {
        pumpEntries(self);
      });
    });
    pumpEntries(self);
  }
};

ZipFile.prototype.addEmptyDirectory = function(metadataPath, options) {
  var self = this;
  metadataPath = validateMetadataPath(metadataPath, true);
  if (options == null) options = {};
  if (options.size != null) throw new Error("options.size not allowed");
  if (options.compress != null) throw new Error("options.compress not allowed");
  var entry = new Entry(metadataPath, true, options);
  self.entries.push(entry);
  entry.setFileDataPumpFunction(function() {
    writeToOutputStream(self, entry.getDataDescriptor());
    entry.state = Entry.FILE_DATA_DONE;
    pumpEntries(self);
  });
  pumpEntries(self);
};

var eocdrSignatureBuffer = bufferFrom([0x50, 0x4b, 0x05, 0x06]);

ZipFile.prototype.end = function(options, finalSizeCallback) {
  if (typeof options === "function") {
    finalSizeCallback = options;
    options = null;
  }
  if (options == null) options = {};
  if (this.ended) return;
  this.ended = true;
  this.finalSizeCallback = finalSizeCallback;
  this.forceZip64Eocd = !!options.forceZip64Format;
  if (options.comment) {
    if (typeof options.comment === "string") {
      this.comment = encodeCp437(options.comment);
    } else {
      // It should be a Buffer
      this.comment = options.comment;
    }
    if (this.comment.length > 0xffff) throw new Error("comment is too large");
    // gotta check for this, because the zipfile format is actually ambiguous.
    if (bufferIncludes(this.comment, eocdrSignatureBuffer)) throw new Error("comment contains end of central directory record signature");
  } else {
    // no comment.
    this.comment = EMPTY_BUFFER;
  }
  pumpEntries(this);
};

function writeToOutputStream(self, buffer) {
  self.outputStream.write(buffer);
  self.outputStreamCursor += buffer.length;
}

function pumpFileDataReadStream(self, entry, readStream) {
  var crc32Watcher = new Crc32Watcher();
  var uncompressedSizeCounter = new ByteCounter();
  var compressor = entry.compress ? new zlib.DeflateRaw() : new PassThrough();
  var compressedSizeCounter = new ByteCounter();
  readStream.pipe(crc32Watcher)
            .pipe(uncompressedSizeCounter)
            .pipe(compressor)
            .pipe(compressedSizeCounter)
            .pipe(self.outputStream, {end: false});
  compressedSizeCounter.on("end", function() {
    entry.crc32 = crc32Watcher.crc32;
    if (entry.uncompressedSize == null) {
      entry.uncompressedSize = uncompressedSizeCounter.byteCount;
    } else {
      if (entry.uncompressedSize !== uncompressedSizeCounter.byteCount) return self.emit("error", new Error("file data stream has unexpected number of bytes"));
    }
    entry.compressedSize = compressedSizeCounter.byteCount;
    self.outputStreamCursor += entry.compressedSize;
    writeToOutputStream(self, entry.getDataDescriptor());
    entry.state = Entry.FILE_DATA_DONE;
    pumpEntries(self);
  });
}

function pumpEntries(self) {
  if (self.allDone) return;
  // first check if finalSize is finally known
  if (self.ended && self.finalSizeCallback != null) {
    var finalSize = calculateFinalSize(self);
    if (finalSize != null) {
      // we have an answer
      self.finalSizeCallback(finalSize);
      self.finalSizeCallback = null;
    }
  }

  // pump entries
  var entry = getFirstNotDoneEntry();
  function getFirstNotDoneEntry() {
    for (var i = 0; i < self.entries.length; i++) {
      var entry = self.entries[i];
      if (entry.state < Entry.FILE_DATA_DONE) return entry;
    }
    return null;
  }
  if (entry != null) {
    // this entry is not done yet
    if (entry.state < Entry.READY_TO_PUMP_FILE_DATA) return; // input file not open yet
    if (entry.state === Entry.FILE_DATA_IN_PROGRESS) return; // we'll get there
    // start with local file header
    entry.relativeOffsetOfLocalHeader = self.outputStreamCursor;
    var localFileHeader = entry.getLocalFileHeader();
    writeToOutputStream(self, localFileHeader);
    entry.doFileDataPump();
  } else {
    // all cought up on writing entries
    if (self.ended) {
      // head for the exit
      self.offsetOfStartOfCentralDirectory = self.outputStreamCursor;
      self.entries.forEach(function(entry) {
        var centralDirectoryRecord = entry.getCentralDirectoryRecord();
        writeToOutputStream(self, centralDirectoryRecord);
      });
      writeToOutputStream(self, getEndOfCentralDirectoryRecord(self));
      self.outputStream.end();
      self.allDone = true;
    }
  }
}

function calculateFinalSize(self) {
  var pretendOutputCursor = 0;
  var centralDirectorySize = 0;
  for (var i = 0; i < self.entries.length; i++) {
    var entry = self.entries[i];
    // compression is too hard to predict
    if (entry.compress) return -1;
    if (entry.state >= Entry.READY_TO_PUMP_FILE_DATA) {
      // if addReadStream was called without providing the size, we can't predict the final size
      if (entry.uncompressedSize == null) return -1;
    } else {
      // if we're still waiting for fs.stat, we might learn the size someday
      if (entry.uncompressedSize == null) return null;
    }
    // we know this for sure, and this is important to know if we need ZIP64 format.
    entry.relativeOffsetOfLocalHeader = pretendOutputCursor;
    var useZip64Format = entry.useZip64Format();

    pretendOutputCursor += LOCAL_FILE_HEADER_FIXED_SIZE + entry.utf8FileName.length;
    pretendOutputCursor += entry.uncompressedSize;
    if (!entry.crcAndFileSizeKnown) {
      // use a data descriptor
      if (useZip64Format) {
        pretendOutputCursor += ZIP64_DATA_DESCRIPTOR_SIZE;
      } else {
        pretendOutputCursor += DATA_DESCRIPTOR_SIZE;
      }
    }

    centralDirectorySize += CENTRAL_DIRECTORY_RECORD_FIXED_SIZE + entry.utf8FileName.length + entry.fileComment.length;
    if (useZip64Format) {
      centralDirectorySize += ZIP64_EXTENDED_INFORMATION_EXTRA_FIELD_SIZE;
    }
  }

  var endOfCentralDirectorySize = 0;
  if (self.forceZip64Eocd ||
      self.entries.length >= 0xffff ||
      centralDirectorySize >= 0xffff ||
      pretendOutputCursor >= 0xffffffff) {
    // use zip64 end of central directory stuff
    endOfCentralDirectorySize += ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIZE + ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIZE;
  }
  endOfCentralDirectorySize += END_OF_CENTRAL_DIRECTORY_RECORD_SIZE + self.comment.length;
  return pretendOutputCursor + centralDirectorySize + endOfCentralDirectorySize;
}

var ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIZE = 56;
var ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIZE = 20;
var END_OF_CENTRAL_DIRECTORY_RECORD_SIZE = 22;
function getEndOfCentralDirectoryRecord(self, actuallyJustTellMeHowLongItWouldBe) {
  var needZip64Format = false;
  var normalEntriesLength = self.entries.length;
  if (self.forceZip64Eocd || self.entries.length >= 0xffff) {
    normalEntriesLength = 0xffff;
    needZip64Format = true;
  }
  var sizeOfCentralDirectory = self.outputStreamCursor - self.offsetOfStartOfCentralDirectory;
  var normalSizeOfCentralDirectory = sizeOfCentralDirectory;
  if (self.forceZip64Eocd || sizeOfCentralDirectory >= 0xffffffff) {
    normalSizeOfCentralDirectory = 0xffffffff;
    needZip64Format = true;
  }
  var normalOffsetOfStartOfCentralDirectory = self.offsetOfStartOfCentralDirectory;
  if (self.forceZip64Eocd || self.offsetOfStartOfCentralDirectory >= 0xffffffff) {
    normalOffsetOfStartOfCentralDirectory = 0xffffffff;
    needZip64Format = true;
  }
  if (actuallyJustTellMeHowLongItWouldBe) {
    if (needZip64Format) {
      return (
        ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIZE +
        ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIZE +
        END_OF_CENTRAL_DIRECTORY_RECORD_SIZE
      );
    } else {
      return END_OF_CENTRAL_DIRECTORY_RECORD_SIZE;
    }
  }

  var eocdrBuffer = bufferAlloc(END_OF_CENTRAL_DIRECTORY_RECORD_SIZE + self.comment.length);
  // end of central dir signature                       4 bytes  (0x06054b50)
  eocdrBuffer.writeUInt32LE(0x06054b50, 0);
  // number of this disk                                2 bytes
  eocdrBuffer.writeUInt16LE(0, 4);
  // number of the disk with the start of the central directory  2 bytes
  eocdrBuffer.writeUInt16LE(0, 6);
  // total number of entries in the central directory on this disk  2 bytes
  eocdrBuffer.writeUInt16LE(normalEntriesLength, 8);
  // total number of entries in the central directory   2 bytes
  eocdrBuffer.writeUInt16LE(normalEntriesLength, 10);
  // size of the central directory                      4 bytes
  eocdrBuffer.writeUInt32LE(normalSizeOfCentralDirectory, 12);
  // offset of start of central directory with respect to the starting disk number  4 bytes
  eocdrBuffer.writeUInt32LE(normalOffsetOfStartOfCentralDirectory, 16);
  // .ZIP file comment length                           2 bytes
  eocdrBuffer.writeUInt16LE(self.comment.length, 20);
  // .ZIP file comment                                  (variable size)
  self.comment.copy(eocdrBuffer, 22);

  if (!needZip64Format) return eocdrBuffer;

  // ZIP64 format
  // ZIP64 End of Central Directory Record
  var zip64EocdrBuffer = bufferAlloc(ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIZE);
  // zip64 end of central dir signature                                             4 bytes  (0x06064b50)
  zip64EocdrBuffer.writeUInt32LE(0x06064b50, 0);
  // size of zip64 end of central directory record                                  8 bytes
  writeUInt64LE(zip64EocdrBuffer, ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIZE - 12, 4);
  // version made by                                                                2 bytes
  zip64EocdrBuffer.writeUInt16LE(VERSION_MADE_BY, 12);
  // version needed to extract                                                      2 bytes
  zip64EocdrBuffer.writeUInt16LE(VERSION_NEEDED_TO_EXTRACT_ZIP64, 14);
  // number of this disk                                                            4 bytes
  zip64EocdrBuffer.writeUInt32LE(0, 16);
  // number of the disk with the start of the central directory                     4 bytes
  zip64EocdrBuffer.writeUInt32LE(0, 20);
  // total number of entries in the central directory on this disk                  8 bytes
  writeUInt64LE(zip64EocdrBuffer, self.entries.length, 24);
  // total number of entries in the central directory                               8 bytes
  writeUInt64LE(zip64EocdrBuffer, self.entries.length, 32);
  // size of the central directory                                                  8 bytes
  writeUInt64LE(zip64EocdrBuffer, sizeOfCentralDirectory, 40);
  // offset of start of central directory with respect to the starting disk number  8 bytes
  writeUInt64LE(zip64EocdrBuffer, self.offsetOfStartOfCentralDirectory, 48);
  // zip64 extensible data sector                                                   (variable size)
  // nothing in the zip64 extensible data sector

  // ZIP64 End of Central Directory Locator
  var zip64EocdlBuffer = bufferAlloc(ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIZE);
  // zip64 end of central dir locator signature                               4 bytes  (0x07064b50)
  zip64EocdlBuffer.writeUInt32LE(0x07064b50, 0);
  // number of the disk with the start of the zip64 end of central directory  4 bytes
  zip64EocdlBuffer.writeUInt32LE(0, 4);
  // relative offset of the zip64 end of central directory record             8 bytes
  writeUInt64LE(zip64EocdlBuffer, self.outputStreamCursor, 8);
  // total number of disks                                                    4 bytes
  zip64EocdlBuffer.writeUInt32LE(1, 16);

  return Buffer.concat([
    zip64EocdrBuffer,
    zip64EocdlBuffer,
    eocdrBuffer,
  ]);
}

function validateMetadataPath(metadataPath, isDirectory) {
  if (metadataPath === "") throw new Error("empty metadataPath");
  metadataPath = metadataPath.replace(/\\/g, "/");
  if (/^[a-zA-Z]:/.test(metadataPath) || /^\//.test(metadataPath)) throw new Error("absolute path: " + metadataPath);
  if (metadataPath.split("/").indexOf("..") !== -1) throw new Error("invalid relative path: " + metadataPath);
  var looksLikeDirectory = /\/$/.test(metadataPath);
  if (isDirectory) {
    // append a trailing '/' if necessary.
    if (!looksLikeDirectory) metadataPath += "/";
  } else {
    if (looksLikeDirectory) throw new Error("file path cannot end with '/': " + metadataPath);
  }
  return metadataPath;
}

var EMPTY_BUFFER = bufferAlloc(0);

// this class is not part of the public API
function Entry(metadataPath, isDirectory, options) {
  this.utf8FileName = bufferFrom(metadataPath);
  if (this.utf8FileName.length > 0xffff) throw new Error("utf8 file name too long. " + utf8FileName.length + " > " + 0xffff);
  this.isDirectory = isDirectory;
  this.state = Entry.WAITING_FOR_METADATA;
  this.setLastModDate(options.mtime != null ? options.mtime : new Date());
  if (options.mode != null) {
    this.setFileAttributesMode(options.mode);
  } else {
    this.setFileAttributesMode(isDirectory ? 0o40775 : 0o100664);
  }
  if (isDirectory) {
    this.crcAndFileSizeKnown = true;
    this.crc32 = 0;
    this.uncompressedSize = 0;
    this.compressedSize = 0;
  } else {
    // unknown so far
    this.crcAndFileSizeKnown = false;
    this.crc32 = null;
    this.uncompressedSize = null;
    this.compressedSize = null;
    if (options.size != null) this.uncompressedSize = options.size;
  }
  if (isDirectory) {
    this.compress = false;
  } else {
    this.compress = true; // default
    if (options.compress != null) this.compress = !!options.compress;
  }
  this.forceZip64Format = !!options.forceZip64Format;
  if (options.fileComment) {
    if (typeof options.fileComment === "string") {
      this.fileComment = bufferFrom(options.fileComment, "utf-8");
    } else {
      // It should be a Buffer
      this.fileComment = options.fileComment;
    }
    if (this.fileComment.length > 0xffff) throw new Error("fileComment is too large");
  } else {
    // no comment.
    this.fileComment = EMPTY_BUFFER;
  }
}
Entry.WAITING_FOR_METADATA = 0;
Entry.READY_TO_PUMP_FILE_DATA = 1;
Entry.FILE_DATA_IN_PROGRESS = 2;
Entry.FILE_DATA_DONE = 3;
Entry.prototype.setLastModDate = function(date) {
  var dosDateTime = dateToDosDateTime(date);
  this.lastModFileTime = dosDateTime.time;
  this.lastModFileDate = dosDateTime.date;
};
Entry.prototype.setFileAttributesMode = function(mode) {
  if ((mode & 0xffff) !== mode) throw new Error("invalid mode. expected: 0 <= " + mode + " <= " + 0xffff);
  // http://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute/14727#14727
  this.externalFileAttributes = (mode << 16) >>> 0;
};
// doFileDataPump() should not call pumpEntries() directly. see issue #9.
Entry.prototype.setFileDataPumpFunction = function(doFileDataPump) {
  this.doFileDataPump = doFileDataPump;
  this.state = Entry.READY_TO_PUMP_FILE_DATA;
};
Entry.prototype.useZip64Format = function() {
  return (
    (this.forceZip64Format) ||
    (this.uncompressedSize != null && this.uncompressedSize > 0xfffffffe) ||
    (this.compressedSize != null && this.compressedSize > 0xfffffffe) ||
    (this.relativeOffsetOfLocalHeader != null && this.relativeOffsetOfLocalHeader > 0xfffffffe)
  );
}
var LOCAL_FILE_HEADER_FIXED_SIZE = 30;
var VERSION_NEEDED_TO_EXTRACT_UTF8 = 20;
var VERSION_NEEDED_TO_EXTRACT_ZIP64 = 45;
// 3 = unix. 63 = spec version 6.3
var VERSION_MADE_BY = (3 << 8) | 63;
var FILE_NAME_IS_UTF8 = 1 << 11;
var UNKNOWN_CRC32_AND_FILE_SIZES = 1 << 3;
Entry.prototype.getLocalFileHeader = function() {
  var crc32 = 0;
  var compressedSize = 0;
  var uncompressedSize = 0;
  if (this.crcAndFileSizeKnown) {
    crc32 = this.crc32;
    compressedSize = this.compressedSize;
    uncompressedSize = this.uncompressedSize;
  }

  var fixedSizeStuff = bufferAlloc(LOCAL_FILE_HEADER_FIXED_SIZE);
  var generalPurposeBitFlag = FILE_NAME_IS_UTF8;
  if (!this.crcAndFileSizeKnown) generalPurposeBitFlag |= UNKNOWN_CRC32_AND_FILE_SIZES;

  // local file header signature     4 bytes  (0x04034b50)
  fixedSizeStuff.writeUInt32LE(0x04034b50, 0);
  // version needed to extract       2 bytes
  fixedSizeStuff.writeUInt16LE(VERSION_NEEDED_TO_EXTRACT_UTF8, 4);
  // general purpose bit flag        2 bytes
  fixedSizeStuff.writeUInt16LE(generalPurposeBitFlag, 6);
  // compression method              2 bytes
  fixedSizeStuff.writeUInt16LE(this.getCompressionMethod(), 8);
  // last mod file time              2 bytes
  fixedSizeStuff.writeUInt16LE(this.lastModFileTime, 10);
  // last mod file date              2 bytes
  fixedSizeStuff.writeUInt16LE(this.lastModFileDate, 12);
  // crc-32                          4 bytes
  fixedSizeStuff.writeUInt32LE(crc32, 14);
  // compressed size                 4 bytes
  fixedSizeStuff.writeUInt32LE(compressedSize, 18);
  // uncompressed size               4 bytes
  fixedSizeStuff.writeUInt32LE(uncompressedSize, 22);
  // file name length                2 bytes
  fixedSizeStuff.writeUInt16LE(this.utf8FileName.length, 26);
  // extra field length              2 bytes
  fixedSizeStuff.writeUInt16LE(0, 28);
  return Buffer.concat([
    fixedSizeStuff,
    // file name (variable size)
    this.utf8FileName,
    // extra field (variable size)
    // no extra fields
  ]);
};
var DATA_DESCRIPTOR_SIZE = 16;
var ZIP64_DATA_DESCRIPTOR_SIZE = 24;
Entry.prototype.getDataDescriptor = function() {
  if (this.crcAndFileSizeKnown) {
    // the Mac Archive Utility requires this not be present unless we set general purpose bit 3
    return EMPTY_BUFFER;
  }
  if (!this.useZip64Format()) {
    var buffer = bufferAlloc(DATA_DESCRIPTOR_SIZE);
    // optional signature (required according to Archive Utility)
    buffer.writeUInt32LE(0x08074b50, 0);
    // crc-32                          4 bytes
    buffer.writeUInt32LE(this.crc32, 4);
    // compressed size                 4 bytes
    buffer.writeUInt32LE(this.compressedSize, 8);
    // uncompressed size               4 bytes
    buffer.writeUInt32LE(this.uncompressedSize, 12);
    return buffer;
  } else {
    // ZIP64 format
    var buffer = bufferAlloc(ZIP64_DATA_DESCRIPTOR_SIZE);
    // optional signature (unknown if anyone cares about this)
    buffer.writeUInt32LE(0x08074b50, 0);
    // crc-32                          4 bytes
    buffer.writeUInt32LE(this.crc32, 4);
    // compressed size                 8 bytes
    writeUInt64LE(buffer, this.compressedSize, 8);
    // uncompressed size               8 bytes
    writeUInt64LE(buffer, this.uncompressedSize, 16);
    return buffer;
  }
};
var CENTRAL_DIRECTORY_RECORD_FIXED_SIZE = 46;
var ZIP64_EXTENDED_INFORMATION_EXTRA_FIELD_SIZE = 28;
Entry.prototype.getCentralDirectoryRecord = function() {
  var fixedSizeStuff = bufferAlloc(CENTRAL_DIRECTORY_RECORD_FIXED_SIZE);
  var generalPurposeBitFlag = FILE_NAME_IS_UTF8;
  if (!this.crcAndFileSizeKnown) generalPurposeBitFlag |= UNKNOWN_CRC32_AND_FILE_SIZES;

  var normalCompressedSize = this.compressedSize;
  var normalUncompressedSize = this.uncompressedSize;
  var normalRelativeOffsetOfLocalHeader = this.relativeOffsetOfLocalHeader;
  var versionNeededToExtract;
  var zeiefBuffer;
  if (this.useZip64Format()) {
    normalCompressedSize = 0xffffffff;
    normalUncompressedSize = 0xffffffff;
    normalRelativeOffsetOfLocalHeader = 0xffffffff;
    versionNeededToExtract = VERSION_NEEDED_TO_EXTRACT_ZIP64;

    // ZIP64 extended information extra field
    zeiefBuffer = bufferAlloc(ZIP64_EXTENDED_INFORMATION_EXTRA_FIELD_SIZE);
    // 0x0001                  2 bytes    Tag for this "extra" block type
    zeiefBuffer.writeUInt16LE(0x0001, 0);
    // Size                    2 bytes    Size of this "extra" block
    zeiefBuffer.writeUInt16LE(ZIP64_EXTENDED_INFORMATION_EXTRA_FIELD_SIZE - 4, 2);
    // Original Size           8 bytes    Original uncompressed file size
    writeUInt64LE(zeiefBuffer, this.uncompressedSize, 4);
    // Compressed Size         8 bytes    Size of compressed data
    writeUInt64LE(zeiefBuffer, this.compressedSize, 12);
    // Relative Header Offset  8 bytes    Offset of local header record
    writeUInt64LE(zeiefBuffer, this.relativeOffsetOfLocalHeader, 20);
    // Disk Start Number       4 bytes    Number of the disk on which this file starts
    // (omit)
  } else {
    versionNeededToExtract = VERSION_NEEDED_TO_EXTRACT_UTF8;
    zeiefBuffer = EMPTY_BUFFER;
  }

  // central file header signature   4 bytes  (0x02014b50)
  fixedSizeStuff.writeUInt32LE(0x02014b50, 0);
  // version made by                 2 bytes
  fixedSizeStuff.writeUInt16LE(VERSION_MADE_BY, 4);
  // version needed to extract       2 bytes
  fixedSizeStuff.writeUInt16LE(versionNeededToExtract, 6);
  // general purpose bit flag        2 bytes
  fixedSizeStuff.writeUInt16LE(generalPurposeBitFlag, 8);
  // compression method              2 bytes
  fixedSizeStuff.writeUInt16LE(this.getCompressionMethod(), 10);
  // last mod file time              2 bytes
  fixedSizeStuff.writeUInt16LE(this.lastModFileTime, 12);
  // last mod file date              2 bytes
  fixedSizeStuff.writeUInt16LE(this.lastModFileDate, 14);
  // crc-32                          4 bytes
  fixedSizeStuff.writeUInt32LE(this.crc32, 16);
  // compressed size                 4 bytes
  fixedSizeStuff.writeUInt32LE(normalCompressedSize, 20);
  // uncompressed size               4 bytes
  fixedSizeStuff.writeUInt32LE(normalUncompressedSize, 24);
  // file name length                2 bytes
  fixedSizeStuff.writeUInt16LE(this.utf8FileName.length, 28);
  // extra field length              2 bytes
  fixedSizeStuff.writeUInt16LE(zeiefBuffer.length, 30);
  // file comment length             2 bytes
  fixedSizeStuff.writeUInt16LE(this.fileComment.length, 32);
  // disk number start               2 bytes
  fixedSizeStuff.writeUInt16LE(0, 34);
  // internal file attributes        2 bytes
  fixedSizeStuff.writeUInt16LE(0, 36);
  // external file attributes        4 bytes
  fixedSizeStuff.writeUInt32LE(this.externalFileAttributes, 38);
  // relative offset of local header 4 bytes
  fixedSizeStuff.writeUInt32LE(normalRelativeOffsetOfLocalHeader, 42);

  return Buffer.concat([
    fixedSizeStuff,
    // file name (variable size)
    this.utf8FileName,
    // extra field (variable size)
    zeiefBuffer,
    // file comment (variable size)
    this.fileComment,
  ]);
};
Entry.prototype.getCompressionMethod = function() {
  var NO_COMPRESSION = 0;
  var DEFLATE_COMPRESSION = 8;
  return this.compress ? DEFLATE_COMPRESSION : NO_COMPRESSION;
};

function dateToDosDateTime(jsDate) {
  var date = 0;
  date |= jsDate.getDate() & 0x1f; // 1-31
  date |= ((jsDate.getMonth() + 1) & 0xf) << 5; // 0-11, 1-12
  date |= ((jsDate.getFullYear() - 1980) & 0x7f) << 9; // 0-128, 1980-2108

  var time = 0;
  time |= Math.floor(jsDate.getSeconds() / 2); // 0-59, 0-29 (lose odd numbers)
  time |= (jsDate.getMinutes() & 0x3f) << 5; // 0-59
  time |= (jsDate.getHours() & 0x1f) << 11; // 0-23

  return {date: date, time: time};
}

function writeUInt64LE(buffer, n, offset) {
  // can't use bitshift here, because JavaScript only allows bitshifting on 32-bit integers.
  var high = Math.floor(n / 0x100000000);
  var low = n % 0x100000000;
  buffer.writeUInt32LE(low, offset);
  buffer.writeUInt32LE(high, offset + 4);
}

function defaultCallback(err) {
  if (err) throw err;
}

util.inherits(ByteCounter, Transform);
function ByteCounter(options) {
  Transform.call(this, options);
  this.byteCount = 0;
}
ByteCounter.prototype._transform = function(chunk, encoding, cb) {
  this.byteCount += chunk.length;
  cb(null, chunk);
};

util.inherits(Crc32Watcher, Transform);
function Crc32Watcher(options) {
  Transform.call(this, options);
  this.crc32 = 0;
}
Crc32Watcher.prototype._transform = function(chunk, encoding, cb) {
  this.crc32 = crc32.unsigned(chunk, this.crc32);
  cb(null, chunk);
};

var cp437 = '\u0000☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñÑªº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ ';
if (cp437.length !== 256) throw new Error("assertion failure");
var reverseCp437 = null;

function encodeCp437(string) {
  if (/^[\x20-\x7e]*$/.test(string)) {
    // CP437, ASCII, and UTF-8 overlap in this range.
    return bufferFrom(string, "utf-8");
  }

  // This is the slow path.
  if (reverseCp437 == null) {
    // cache this once
    reverseCp437 = {};
    for (var i = 0; i < cp437.length; i++) {
      reverseCp437[cp437[i]] = i;
    }
  }

  var result = bufferAlloc(string.length);
  for (var i = 0; i < string.length; i++) {
    var b = reverseCp437[string[i]];
    if (b == null) throw new Error("character not encodable in CP437: " + JSON.stringify(string[i]));
    result[i] = b;
  }

  return result;
}

function bufferAlloc(size) {
  bufferAlloc = modern;
  try {
    return bufferAlloc(size);
  } catch (e) {
    bufferAlloc = legacy;
    return bufferAlloc(size);
  }
  function modern(size) {
    return Buffer.allocUnsafe(size);
  }
  function legacy(size) {
    return new Buffer(size);
  }
}
function bufferFrom(something, encoding) {
  bufferFrom = modern;
  try {
    return bufferFrom(something, encoding);
  } catch (e) {
    bufferFrom = legacy;
    return bufferFrom(something, encoding);
  }
  function modern(something, encoding) {
    return Buffer.from(something, encoding);
  }
  function legacy(something, encoding) {
    return new Buffer(something, encoding);
  }
}
function bufferIncludes(buffer, content) {
  bufferIncludes = modern;
  try {
    return bufferIncludes(buffer, content);
  } catch (e) {
    bufferIncludes = legacy;
    return bufferIncludes(buffer, content);
  }
  function modern(buffer, content) {
    return buffer.includes(content);
  }
  function legacy(buffer, content) {
    for (var i = 0; i <= buffer.length - content.length; i++) {
      for (var j = 0;; j++) {
        if (j === content.length) return true;
        if (buffer[i + j] !== content[j]) break;
      }
    }
    return false;
  }
}

license: mit


yazl

description: yet another zip library for node

test: node test/test.js

test-cov: istanbul cover test/test.js

test-travis: istanbul cover --report lcovonly test/test.js

type: git

keywords: zip stream archive file

author: Josh Wolfe <thejoshwolfe@gmail.com>

license: MIT


code:
declare class Queue<ValueType> implements Iterable<ValueType> {
	/**
	The size of the queue.
	*/
	readonly size: number;

	/**
	Tiny queue data structure.

	The instance is an [`Iterable`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols), which means you can iterate over the queue front to back with a “for…of” loop, or use spreading to convert the queue to an array. Don't do this unless you really need to though, since it's slow.

	@example
	```
	import Queue = require('yocto-queue');

	const queue = new Queue();

	queue.enqueue('🦄');
	queue.enqueue('🌈');

	console.log(queue.size);
	//=> 2

	console.log(...queue);
	//=> '🦄 🌈'

	console.log(queue.dequeue());
	//=> '🦄'

	console.log(queue.dequeue());
	//=> '🌈'
	```
	*/
	constructor();

	[Symbol.iterator](): IterableIterator<ValueType>;

	/**
	Add a value to the queue.
	*/
	enqueue(value: ValueType): void;

	/**
	Remove the next value in the queue.

	@returns The removed value or `undefined` if the queue is empty.
	*/
	dequeue(): ValueType | undefined;

	/**
	Clear the queue.
	*/
	clear(): void;
}

export = Queue;


code:
class Node {
	/// value;
	/// next;

	constructor(value) {
		this.value = value;

		// TODO: Remove this when targeting Node.js 12.
		this.next = undefined;
	}
}

class Queue {
	// TODO: Use private class fields when targeting Node.js 12.
	// #_head;
	// #_tail;
	// #_size;

	constructor() {
		this.clear();
	}

	enqueue(value) {
		const node = new Node(value);

		if (this._head) {
			this._tail.next = node;
			this._tail = node;
		} else {
			this._head = node;
			this._tail = node;
		}

		this._size++;
	}

	dequeue() {
		const current = this._head;
		if (!current) {
			return;
		}

		this._head = this._head.next;
		this._size--;
		return current.value;
	}

	clear() {
		this._head = undefined;
		this._tail = undefined;
		this._size = 0;
	}

	get size() {
		return this._size;
	}

	* [Symbol.iterator]() {
		let current = this._head;

		while (current) {
			yield current.value;
			current = current.next;
		}
	}
}

module.exports = Queue;


yocto-queue

description: Tiny queue data structure

repository: sindresorhus/yocto-queue

test: xo && ava && tsd

keywords: queue data structure algorithm queues queuing list array linkedlist fifo enqueue dequeue data-structure


code:
# yocto-queue [![](https://badgen.net/bundlephobia/minzip/yocto-queue)](https://bundlephobia.com/result?p=yocto-queue)

> Tiny queue data structure

You should use this package instead of an array if you do a lot of `Array#push()` and `Array#shift()` on large arrays, since `Array#shift()` has [linear time complexity](https://medium.com/@ariel.salem1989/an-easy-to-use-guide-to-big-o-time-complexity-5dcf4be8a444#:~:text=O(N)%E2%80%94Linear%20Time) *O(n)* while `Queue#dequeue()` has [constant time complexity](https://medium.com/@ariel.salem1989/an-easy-to-use-guide-to-big-o-time-complexity-5dcf4be8a444#:~:text=O(1)%20%E2%80%94%20Constant%20Time) *O(1)*. That makes a huge difference for large arrays.

> A [queue](https://en.wikipedia.org/wiki/Queue_(abstract_data_type)) is an ordered list of elements where an element is inserted at the end of the queue and is removed from the front of the queue. A queue works based on the first-in, first-out ([FIFO](https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics))) principle.

## Install

```
$ npm install yocto-queue
```

## Usage

```js
const Queue = require('yocto-queue');

const queue = new Queue();

queue.enqueue('🦄');
queue.enqueue('🌈');

console.log(queue.size);
//=> 2

console.log(...queue);
//=> '🦄 🌈'

console.log(queue.dequeue());
//=> '🦄'

console.log(queue.dequeue());
//=> '🌈'
```

## API

### `queue = new Queue()`

The instance is an [`Iterable`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols), which means you can iterate over the queue front to back with a “for…of” loop, or use spreading to convert the queue to an array. Don't do this unless you really need to though, since it's slow.

#### `.enqueue(value)`

Add a value to the queue.

#### `.dequeue()`

Remove the next value in the queue.

Returns the removed value or `undefined` if the queue is empty.

#### `.clear()`

Clear the queue.

#### `.size`

The size of the queue.

## Related

- [quick-lru](https://github.com/sindresorhus/quick-lru) - Simple “Least Recently Used” (LRU) cache


code:
# CHANGELOG

## Unreleased

* Support `sendemail-validate` hook [#173](https://github.com/typicode/husky/pull/173)
* Support `HUSKY_SKIP_INSTALL` environment variable for skipping git hooks installation
* Drop `nvm` installed with `brew` support as it's not supported by `nvm` itself (see [creationix/nvm#important-notes](https://github.com/creationix/nvm#important-notes)), `nvm` standard installation is still supported though

## 0.14.3

* Fix handle space in `PATH` [#150](https://github.com/typicode/husky/pull/114)

## 0.14.2

* Fix handle space in `HOME`

## 0.14.1

* Fix Git hooks install on Windows
* Fix hook script when `nvm` was installed with Brew

## 0.14.0

* Fix `npm@5` `Error: Cannot find module` warning when uninstalling
* Drop `Node 0.12` support
* Don't reload `nvm` if it's already in `PATH`
* Add Git worktree support [#114](https://github.com/typicode/husky/pull/114)
* Hide irrelevant `--no-verify` message for `prepare-commit-msg` [#137](https://github.com/typicode/husky/issues/137)

## 0.13.4

* Add Node version to husky output

## 0.13.3

* Revert `Fixes issue with OS X + brew where nvm was loaded even when npm was already present` that was introduced in `v0.13.0` as it was preventing Husky to load `nvm` in some cases [#106](https://github.com/typicode/husky/issues/106)

## 0.13.2

* Fixes issue [#103](https://github.com/typicode/husky/issues/103)

## 0.13.1

* Makes it easier for projects to transition from [ghooks](https://github.com/gtramontina/ghooks) by detecting ghooks installed scripts and automatically migrating them

## 0.13.0

* Makes `husky` a little less verbose by default
* Fixes issue with `OS X + brew` where `nvm` was loaded even when `npm` was already present
* Fixes issue with Git `v1.9` on Windows
* Prevents Git hooks being installed when husky is in a sub `node_modules` directory (i.e. `./node_modules/A/node_modules/husky`)

## 0.12.0

* Adds Git submodule support
* Adds Cygwin support
* Improves edge cases support (`.git` not found and `git` not in `PATH`)
* If `npm` is already present in path, doesn't load `nvm` default or `.nvmrc` version, which makes things faster in terminal. In GUI apps, the behavior is unchanged.


code:
# Hooks

Husky supports all git hooks (https://git-scm.com/docs/githooks). Simply add the corresponding `npm script` to your `package.json`.

| Git hook | npm script |
| -------- | ---------- |
| applypatch-msg | applypatchmsg |
| commit-msg | commitmsg |
| post-applypatch | postapplypatch |
| post-checkout | postcheckout |
| post-commit | postcommit |
| post-merge | postmerge |
| post-receive | postreceive |
| post-rewrite | postrewrite |
| post-update | postupdate |
| pre-applypatch | preapplypatch |
| pre-auto-gc | preautogc |
| pre-commit | precommit |
| pre-push | prepush |
| pre-rebase | prerebase |
| pre-receive | prereceive |
| prepare-commit-msg | preparecommitmsg |
| push-to-checkout | pushtocheckout |
| update | update |
| sendemail-validate | sendemailvalidate |


code:
# yorkie

> Git hooks made easy

This is a fork of [husky](https://github.com/typicode/husky) with a few changes:

- Prioritizes `package.json` located next to `.git` directory, instead of hard-coded upward search. This avoids the problem when a root package in a lerna monorepo and a sub package both depends on husky, it gets confused and double-updates the root git hooks with wrong paths.

- Changed where hooks are read from in `package.json`:

  **Before**

  ``` json
  {
    "scripts": {
      "precommit": "foo"
    }
  }
  ```

  **After**

  ``` json
  {
    "gitHooks": {
      "pre-commit": "foo"
    }
  }
  ```


code:
'use strict'

const fs = require('fs')
const path = require('path')
const mkdirp = require('mkdirp')
const rimraf = require('rimraf')
const tempy = require('tempy')
const installFrom = require('../src/install')
const uninstallFrom = require('../src/uninstall')

function install(rootDir, dir) {
  installFrom(path.join(rootDir, dir))
}

function uninstall(rootDir, dir) {
  uninstallFrom(path.join(rootDir, dir))
}

function mkdir(rootDir, dir) {
  mkdirp.sync(path.join(rootDir, dir))
}

function writeFile(dir, filePath, data) {
  fs.writeFileSync(path.join(dir, filePath), data)
}

function readFile(dir, filePath) {
  return fs.readFileSync(path.join(dir, filePath), 'utf-8')
}

function exists(dir, filePath) {
  return fs.existsSync(path.join(dir, filePath))
}

describe('yorkie', () => {
  let dir
  beforeEach(() => (dir = tempy.directory()))
  afterEach(() => rimraf.sync(dir))

  it('should support basic layout', () => {
    mkdir(dir, '.git/hooks')
    mkdir(dir, 'node_modules/yorkie')
    writeFile(dir, 'package.json', '{}')

    install(dir, '/node_modules/yorkie')
    const hook = readFile(dir, '.git/hooks/pre-commit')

    expect(hook).toMatch('#yorkie')
    expect(hook).toMatch('cd "."')
    expect(hook).toMatch(`node "./node_modules/yorkie/src/runner.js" pre-commit`)
    expect(hook).toMatch('--no-verify')

    const prepareCommitMsg = readFile(dir, '.git/hooks/prepare-commit-msg')
    expect(prepareCommitMsg).toMatch('cannot be bypassed')

    uninstall(dir, 'node_modules/yorkie')
    expect(exists(dir, '.git/hooks/pre-push')).toBeFalsy()
  })

  it('should not install git hooks when installed in sub directory', () => {
    mkdir(dir, '.git/hooks')
    mkdir(dir, 'A/B/node_modules/yorkie')
    writeFile(dir, 'A/B/package.json', '{}')

    install(dir, 'A/B/node_modules/yorkie')
    expect(exists(dir, '.git/hooks/pre-commit')).toBeFalsy()
  })

  it('should support git submodule', () => {
    mkdir(dir, '.git/modules/A/B')
    mkdir(dir, 'A/B/node_modules/yorkie')
    writeFile(dir, 'package.json', '{}')
    writeFile(dir, 'A/B/package.json', '{}')
    writeFile(dir, 'A/B/.git', 'git: ../../.git/modules/A/B')

    install(dir, 'A/B/node_modules/yorkie')
    const hook = readFile(dir, '.git/modules/A/B/hooks/pre-commit')

    expect(hook).toMatch('cd "."')

    uninstall(dir, 'A/B/node_modules/yorkie')
    expect(exists(dir, '.git/hooks/pre-push')).toBeFalsy()
  })

  it('should not install git hooks in submodule sub directory', () => {
    mkdir(dir, '.git/modules/A/B')
    mkdir(dir, 'A/B/C/node_modules/yorkie')
    writeFile(dir, 'package.json', '{}')
    writeFile(dir, 'A/B/C/package.json', '{}')
    writeFile(dir, 'A/B/.git', 'git: ../../.git/modules/A/B')

    install(dir, 'A/B/C/node_modules/yorkie')
    expect(exists(dir, '.git/modules/A/B/hooks/pre-commit')).toBeFalsy()
  })

  it('should support git worktrees', () => {
    mkdir(dir, '.git/worktrees/B')
    mkdir(dir, 'A/B/node_modules/yorkie')
    writeFile(dir, 'package.json', '{}')
    writeFile(dir, 'A/B/package.json', '{}')

    // Git path for worktrees is absolute
    const absolutePath = path.join(dir, '.git/worktrees/B')
    writeFile(dir, 'A/B/.git', `git: ${absolutePath}`)

    install(dir, 'A/B/node_modules/yorkie')
    const hook = readFile(dir, '.git/worktrees/B/hooks/pre-commit')

    expect(hook).toMatch('cd "."')

    uninstall(dir, 'A/B/node_modules/yorkie')
    expect(exists(dir, '.git/hooks/pre-commit')).toBeFalsy()
  })

  it('should not modify user hooks', () => {
    mkdir(dir, '.git/hooks')
    mkdir(dir, 'node_modules/yorkie')
    writeFile(dir, '.git/hooks/pre-push', 'foo')

    // Verify that it's not overwritten
    install(dir, 'node_modules/yorkie')
    const hook = readFile(dir, '.git/hooks/pre-push')
    expect(hook).toBe('foo')

    uninstall(dir, 'node_modules/yorkie')
    expect(exists(dir, '.git/hooks/pre-push')).toBeTruthy()
  })

  it('should not install from /node_modules/A/node_modules', () => {
    mkdir(dir, '.git/hooks')
    mkdir(dir, 'node_modules/A/node_modules/yorkie')

    install(dir, 'node_modules/A/node_modules/yorkie')
    expect(exists(dir, '.git/hooks/pre-push')).toBeFalsy()
  })

  it("should not crash if there's no .git directory", () => {
    mkdir(dir, 'node_modules/yorkie')

    expect(() => install(dir, 'node_modules/yorkie')).not.toThrow()
    expect(() => uninstall(dir, 'node_modules/yorkie')).not.toThrow()
  })

  it('should migrate existing scripts (ghooks)', () => {
    mkdir(dir, '.git/hooks')
    writeFile(dir, 'package.json', '{}')
    mkdir(dir, '/node_modules/yorkie')
    writeFile(
      dir,
      '.git/hooks/pre-commit',
      '// Generated by ghooks. Do not edit this file.'
    )

    install(dir, 'node_modules/yorkie')
    const hook = readFile(dir, '.git/hooks/pre-commit')
    expect(hook).toMatch('yorkie')
    expect(hook).not.toMatch('ghooks')
  })

  it('should migrate existing scripts (pre-commit)', () => {
    mkdir(dir, '.git/hooks')
    writeFile(dir, 'package.json', '{}')
    mkdir(dir, '/node_modules/yorkie')
    writeFile(dir, '.git/hooks/pre-commit', './node_modules/pre-commit/hook')

    install(dir, 'node_modules/yorkie')
    const hook = readFile(dir, '.git/hooks/pre-commit')
    expect(hook).toMatch('yorkie')
    expect(hook).not.toMatch('./node_modules/pre-commit/hook')
  })
})


code:
# Test against the latest version of this Node.js version
environment:
  nodejs_version: "8"

# Install scripts. (runs after repo cloning)
install:
  # Get the latest stable version of Node.js or io.js
  - ps: Install-Product node $env:nodejs_version
  # install modules
  - npm install

# Post-install test scripts.
test_script:
  # Output useful info for debugging.
  - node --version
  - npm --version
  # run tests
  - npm test

# Don't actually build.
build: off


code:
'use strict'

// Run when package is installed
const path = require('path')
const isCI = require('is-ci')
const installFrom = require('../src/install')

if (isCI && !process.env.HUSKY_IGNORE_CI && !process.env.YORKIE_IGNORE_CI) {
  console.log('CI detected, skipping Git hooks installation')
  process.exit(0)
}

if (process.env.HUSKY_SKIP_INSTALL || process.env.YORKIE_SKIP_INSTALL) {
  console.log(
    `env variable HUSKY_SKIP_INSTALL is set to ${process.env
      .HUSKY_SKIP_INSTALL}, skipping Git hooks installation`
  )
  process.exit(0)
}

console.log('setting up Git hooks')

const depDir = path.join(__dirname, '..')
installFrom(depDir)


code:
'use strict'

// Run when package is uninstalled
const path = require('path')
const uninstallFrom = require('../src/uninstall')

console.log('husky')
console.log('uninstalling Git hooks')

const depDir = path.join(__dirname, '..')
uninstallFrom(depDir)


code:
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")

case `uname` in
    *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac

if [ -x "$basedir/node" ]; then
  exec "$basedir/node"  "$basedir/../which/bin/which" "$@"
else
  exec node  "$basedir/../which/bin/which" "$@"
fi


code:
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0

IF EXIST "%dp0%\node.exe" (
  SET "_prog=%dp0%\node.exe"
) ELSE (
  SET "_prog=node"
  SET PATHEXT=%PATHEXT:;.JS;=;%
)

endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%"  "%dp0%\..\which\bin\which" %*


code:
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent

$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
  # Fix case when both the Windows and Linux builds of Node
  # are installed in the same directory
  $exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
  # Support pipeline input
  if ($MyInvocation.ExpectingInput) {
    $input | & "$basedir/node$exe"  "$basedir/../which/bin/which" $args
  } else {
    & "$basedir/node$exe"  "$basedir/../which/bin/which" $args
  }
  $ret=$LASTEXITCODE
} else {
  # Support pipeline input
  if ($MyInvocation.ExpectingInput) {
    $input | & "node$exe"  "$basedir/../which/bin/which" $args
  } else {
    & "node$exe"  "$basedir/../which/bin/which" $args
  }
  $ret=$LASTEXITCODE
}
exit $ret


code:
## 5.0.0 - 2016-10-30

- Add support for `options.shell`
- Improve parsing of shebangs by using [`shebang-command`](https://github.com/kevva/shebang-command) module
- Refactor some code to make it more clear
- Update README caveats


code:
# cross-spawn

[![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Build status][appveyor-image]][appveyor-url] [![Dependency status][david-dm-image]][david-dm-url] [![Dev Dependency status][david-dm-dev-image]][david-dm-dev-url]

[npm-url]:https://npmjs.org/package/cross-spawn
[downloads-image]:http://img.shields.io/npm/dm/cross-spawn.svg
[npm-image]:http://img.shields.io/npm/v/cross-spawn.svg
[travis-url]:https://travis-ci.org/IndigoUnited/node-cross-spawn
[travis-image]:http://img.shields.io/travis/IndigoUnited/node-cross-spawn/master.svg
[appveyor-url]:https://ci.appveyor.com/project/satazor/node-cross-spawn
[appveyor-image]:https://img.shields.io/appveyor/ci/satazor/node-cross-spawn/master.svg
[david-dm-url]:https://david-dm.org/IndigoUnited/node-cross-spawn
[david-dm-image]:https://img.shields.io/david/IndigoUnited/node-cross-spawn.svg
[david-dm-dev-url]:https://david-dm.org/IndigoUnited/node-cross-spawn#info=devDependencies
[david-dm-dev-image]:https://img.shields.io/david/dev/IndigoUnited/node-cross-spawn.svg

A cross platform solution to node's spawn and spawnSync.

## Installation

`$ npm install cross-spawn`

If you are using `spawnSync` on node 0.10 or older, you will also need to install `spawn-sync`:

`$ npm install spawn-sync`

## Why

Node has issues when using spawn on Windows:

- It ignores [PATHEXT](https://github.com/joyent/node/issues/2318)
- It does not support [shebangs](http://pt.wikipedia.org/wiki/Shebang)
- No `options.shell` support on node < v6
- It does not allow you to run `del` or `dir`

All these issues are handled correctly by `cross-spawn`.
There are some known modules, such as [win-spawn](https://github.com/ForbesLindesay/win-spawn), that try to solve this but they are either broken or provide faulty escaping of shell arguments.

## Usage

Exactly the same way as node's [`spawn`](https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options) or [`spawnSync`](https://nodejs.org/api/child_process.html#child_process_child_process_spawnsync_command_args_options), so it's a drop in replacement.

```js
var spawn = require('cross-spawn');

// Spawn NPM asynchronously
var child = spawn('npm', ['list', '-g', '-depth', '0'], { stdio: 'inherit' });

// Spawn NPM synchronously
var results = spawn.sync('npm', ['list', '-g', '-depth', '0'], { stdio: 'inherit' });
```

## Caveats

#### `options.shell` as an alternative to `cross-spawn`

Starting from node v6, `spawn` has a `shell` option that allows you run commands from within a shell. This new option solves most of the problems that `cross-spawn` attempts to solve, but:

- It's not supported in node < v6
- It has no support for shebangs on Windows
- You must manually escape the command and arguments which is very error prone, specially when passing user input

If you are using the `shell` option to spawn a command in a cross platform way, consider using `cross-spawn` instead. You have been warned.

#### Shebangs

While `cross-spawn` handles shebangs on Windows, its support is limited: e.g.: it doesn't handle arguments after the path, e.g.: `#!/bin/bash -e`.

Remember to always test your code on Windows!

## Tests

`$ npm test`

## License

Released under the [MIT License](http://www.opensource.org/licenses/mit-license.php).


code:
'use strict';

var cp = require('child_process');
var parse = require('./lib/parse');
var enoent = require('./lib/enoent');

var cpSpawnSync = cp.spawnSync;

function spawn(command, args, options) {
    var parsed;
    var spawned;

    // Parse the arguments
    parsed = parse(command, args, options);

    // Spawn the child process
    spawned = cp.spawn(parsed.command, parsed.args, parsed.options);

    // Hook into child process "exit" event to emit an error if the command
    // does not exists, see: https://github.com/IndigoUnited/node-cross-spawn/issues/16
    enoent.hookChildProcess(spawned, parsed);

    return spawned;
}

function spawnSync(command, args, options) {
    var parsed;
    var result;

    if (!cpSpawnSync) {
        try {
            cpSpawnSync = require('spawn-sync');  // eslint-disable-line global-require
        } catch (ex) {
            throw new Error(
                'In order to use spawnSync on node 0.10 or older, you must ' +
                'install spawn-sync:\n\n' +
                '  npm install spawn-sync --save'
            );
        }
    }

    // Parse the arguments
    parsed = parse(command, args, options);

    // Spawn the child process
    result = cpSpawnSync(parsed.command, parsed.args, parsed.options);

    // Analyze if the command does not exists, see: https://github.com/IndigoUnited/node-cross-spawn/issues/16
    result.error = result.error || enoent.verifyENOENTSync(result.status, parsed);

    return result;
}

module.exports = spawn;
module.exports.spawn = spawn;
module.exports.sync = spawnSync;

module.exports._parse = parse;
module.exports._enoent = enoent;


code:
'use strict';

var isWin = process.platform === 'win32';
var resolveCommand = require('./util/resolveCommand');

var isNode10 = process.version.indexOf('v0.10.') === 0;

function notFoundError(command, syscall) {
    var err;

    err = new Error(syscall + ' ' + command + ' ENOENT');
    err.code = err.errno = 'ENOENT';
    err.syscall = syscall + ' ' + command;

    return err;
}

function hookChildProcess(cp, parsed) {
    var originalEmit;

    if (!isWin) {
        return;
    }

    originalEmit = cp.emit;
    cp.emit = function (name, arg1) {
        var err;

        // If emitting "exit" event and exit code is 1, we need to check if
        // the command exists and emit an "error" instead
        // See: https://github.com/IndigoUnited/node-cross-spawn/issues/16
        if (name === 'exit') {
            err = verifyENOENT(arg1, parsed, 'spawn');

            if (err) {
                return originalEmit.call(cp, 'error', err);
            }
        }

        return originalEmit.apply(cp, arguments);
    };
}

function verifyENOENT(status, parsed) {
    if (isWin && status === 1 && !parsed.file) {
        return notFoundError(parsed.original, 'spawn');
    }

    return null;
}

function verifyENOENTSync(status, parsed) {
    if (isWin && status === 1 && !parsed.file) {
        return notFoundError(parsed.original, 'spawnSync');
    }

    // If we are in node 10, then we are using spawn-sync; if it exited
    // with -1 it probably means that the command does not exist
    if (isNode10 && status === -1) {
        parsed.file = isWin ? parsed.file : resolveCommand(parsed.original);

        if (!parsed.file) {
            return notFoundError(parsed.original, 'spawnSync');
        }
    }

    return null;
}

module.exports.hookChildProcess = hookChildProcess;
module.exports.verifyENOENT = verifyENOENT;
module.exports.verifyENOENTSync = verifyENOENTSync;
module.exports.notFoundError = notFoundError;


code:
'use strict';

var resolveCommand = require('./util/resolveCommand');
var hasEmptyArgumentBug = require('./util/hasEmptyArgumentBug');
var escapeArgument = require('./util/escapeArgument');
var escapeCommand = require('./util/escapeCommand');
var readShebang = require('./util/readShebang');

var isWin = process.platform === 'win32';
var skipShellRegExp = /\.(?:com|exe)$/i;

// Supported in Node >= 6 and >= 4.8
var supportsShellOption = parseInt(process.version.substr(1).split('.')[0], 10) >= 6 ||
 parseInt(process.version.substr(1).split('.')[0], 10) === 4 && parseInt(process.version.substr(1).split('.')[1], 10) >= 8;

function parseNonShell(parsed) {
    var shebang;
    var needsShell;
    var applyQuotes;

    if (!isWin) {
        return parsed;
    }

    // Detect & add support for shebangs
    parsed.file = resolveCommand(parsed.command);
    parsed.file = parsed.file || resolveCommand(parsed.command, true);
    shebang = parsed.file && readShebang(parsed.file);

    if (shebang) {
        parsed.args.unshift(parsed.file);
        parsed.command = shebang;
        needsShell = hasEmptyArgumentBug || !skipShellRegExp.test(resolveCommand(shebang) || resolveCommand(shebang, true));
    } else {
        needsShell = hasEmptyArgumentBug || !skipShellRegExp.test(parsed.file);
    }

    // If a shell is required, use cmd.exe and take care of escaping everything correctly
    if (needsShell) {
        // Escape command & arguments
        applyQuotes = (parsed.command !== 'echo');  // Do not quote arguments for the special "echo" command
        parsed.command = escapeCommand(parsed.command);
        parsed.args = parsed.args.map(function (arg) {
            return escapeArgument(arg, applyQuotes);
        });

        // Make use of cmd.exe
        parsed.args = ['/d', '/s', '/c', '"' + parsed.command + (parsed.args.length ? ' ' + parsed.args.join(' ') : '') + '"'];
        parsed.command = process.env.comspec || 'cmd.exe';
        parsed.options.windowsVerbatimArguments = true;  // Tell node's spawn that the arguments are already escaped
    }

    return parsed;
}

function parseShell(parsed) {
    var shellCommand;

    // If node supports the shell option, there's no need to mimic its behavior
    if (supportsShellOption) {
        return parsed;
    }

    // Mimic node shell option, see: https://github.com/nodejs/node/blob/b9f6a2dc059a1062776133f3d4fd848c4da7d150/lib/child_process.js#L335
    shellCommand = [parsed.command].concat(parsed.args).join(' ');

    if (isWin) {
        parsed.command = typeof parsed.options.shell === 'string' ? parsed.options.shell : process.env.comspec || 'cmd.exe';
        parsed.args = ['/d', '/s', '/c', '"' + shellCommand + '"'];
        parsed.options.windowsVerbatimArguments = true;  // Tell node's spawn that the arguments are already escaped
    } else {
        if (typeof parsed.options.shell === 'string') {
            parsed.command = parsed.options.shell;
        } else if (process.platform === 'android') {
            parsed.command = '/system/bin/sh';
        } else {
            parsed.command = '/bin/sh';
        }

        parsed.args = ['-c', shellCommand];
    }

    return parsed;
}

// ------------------------------------------------

function parse(command, args, options) {
    var parsed;

    // Normalize arguments, similar to nodejs
    if (args && !Array.isArray(args)) {
        options = args;
        args = null;
    }

    args = args ? args.slice(0) : [];  // Clone array to avoid changing the original
    options = options || {};

    // Build our parsed object
    parsed = {
        command: command,
        args: args,
        options: options,
        file: undefined,
        original: command,
    };

    // Delegate further parsing to shell or non-shell
    return options.shell ? parseShell(parsed) : parseNonShell(parsed);
}

module.exports = parse;


code:
'use strict';

function escapeArgument(arg, quote) {
    // Convert to string
    arg = '' + arg;

    // If we are not going to quote the argument,
    // escape shell metacharacters, including double and single quotes:
    if (!quote) {
        arg = arg.replace(/([()%!^<>&|;,"'\s])/g, '^$1');
    } else {
        // Sequence of backslashes followed by a double quote:
        // double up all the backslashes and escape the double quote
        arg = arg.replace(/(\\*)"/g, '$1$1\\"');

        // Sequence of backslashes followed by the end of the string
        // (which will become a double quote later):
        // double up all the backslashes
        arg = arg.replace(/(\\*)$/, '$1$1');

        // All other backslashes occur literally

        // Quote the whole thing:
        arg = '"' + arg + '"';
    }

    return arg;
}

module.exports = escapeArgument;


code:
'use strict';

var escapeArgument = require('./escapeArgument');

function escapeCommand(command) {
    // Do not escape if this command is not dangerous..
    // We do this so that commands like "echo" or "ifconfig" work
    // Quoting them, will make them unaccessible
    return /^[a-z0-9_-]+$/i.test(command) ? command : escapeArgument(command, true);
}

module.exports = escapeCommand;


code:
'use strict';

// See: https://github.com/IndigoUnited/node-cross-spawn/pull/34#issuecomment-221623455
function hasEmptyArgumentBug() {
    var nodeVer;

    if (process.platform !== 'win32') {
        return false;
    }

    nodeVer = process.version.substr(1).split('.').map(function (num) {
        return parseInt(num, 10);
    });

    return (nodeVer[0] === 0 && nodeVer[1] < 12);
}

module.exports = hasEmptyArgumentBug();


code:
'use strict';

var fs = require('fs');
var LRU = require('lru-cache');
var shebangCommand = require('shebang-command');

var shebangCache = new LRU({ max: 50, maxAge: 30 * 1000 });  // Cache just for 30sec

function readShebang(command) {
    var buffer;
    var fd;
    var shebang;

    // Check if it is in the cache first
    if (shebangCache.has(command)) {
        return shebangCache.get(command);
    }

    // Read the first 150 bytes from the file
    buffer = new Buffer(150);

    try {
        fd = fs.openSync(command, 'r');
        fs.readSync(fd, buffer, 0, 150, 0);
        fs.closeSync(fd);
    } catch (e) { /* empty */ }

    // Attempt to extract shebang (null is returned if not a shebang)
    shebang = shebangCommand(buffer.toString());

    // Store the shebang in the cache
    shebangCache.set(command, shebang);

    return shebang;
}

module.exports = readShebang;


code:
'use strict';

var path = require('path');
var which = require('which');
var LRU = require('lru-cache');

var commandCache = new LRU({ max: 50, maxAge: 30 * 1000 });  // Cache just for 30sec

function resolveCommand(command, noExtension) {
    var resolved;

    noExtension = !!noExtension;
    resolved = commandCache.get(command + '!' + noExtension);

    // Check if its resolved in the cache
    if (commandCache.has(command)) {
        return commandCache.get(command);
    }

    try {
        resolved = !noExtension ?
            which.sync(command) :
            which.sync(command, { pathExt: path.delimiter + (process.env.PATHEXT || '') });
    } catch (e) { /* empty */ }

    commandCache.set(command + '!' + noExtension, resolved);

    return resolved;
}

module.exports = resolveCommand;


cross-spawn

description: Cross platform child_process#spawn and child_process#spawnSync

test: node test/prepare && mocha --bail test/test

keywords: spawn spawnSync windows cross platform path ext path-ext path_ext shebang hashbang cmd execute


code:
'use strict';
const childProcess = require('child_process');
const util = require('util');
const crossSpawn = require('cross-spawn');
const stripEof = require('strip-eof');
const npmRunPath = require('npm-run-path');
const isStream = require('is-stream');
const _getStream = require('get-stream');
const pFinally = require('p-finally');
const onExit = require('signal-exit');
const errname = require('./lib/errname');
const stdio = require('./lib/stdio');

const TEN_MEGABYTES = 1000 * 1000 * 10;

function handleArgs(cmd, args, opts) {
	let parsed;

	opts = Object.assign({
		extendEnv: true,
		env: {}
	}, opts);

	if (opts.extendEnv) {
		opts.env = Object.assign({}, process.env, opts.env);
	}

	if (opts.__winShell === true) {
		delete opts.__winShell;
		parsed = {
			command: cmd,
			args,
			options: opts,
			file: cmd,
			original: cmd
		};
	} else {
		parsed = crossSpawn._parse(cmd, args, opts);
	}

	opts = Object.assign({
		maxBuffer: TEN_MEGABYTES,
		stripEof: true,
		preferLocal: true,
		localDir: parsed.options.cwd || process.cwd(),
		encoding: 'utf8',
		reject: true,
		cleanup: true
	}, parsed.options);

	opts.stdio = stdio(opts);

	if (opts.preferLocal) {
		opts.env = npmRunPath.env(Object.assign({}, opts, {cwd: opts.localDir}));
	}

	return {
		cmd: parsed.command,
		args: parsed.args,
		opts,
		parsed
	};
}

function handleInput(spawned, opts) {
	const input = opts.input;

	if (input === null || input === undefined) {
		return;
	}

	if (isStream(input)) {
		input.pipe(spawned.stdin);
	} else {
		spawned.stdin.end(input);
	}
}

function handleOutput(opts, val) {
	if (val && opts.stripEof) {
		val = stripEof(val);
	}

	return val;
}

function handleShell(fn, cmd, opts) {
	let file = '/bin/sh';
	let args = ['-c', cmd];

	opts = Object.assign({}, opts);

	if (process.platform === 'win32') {
		opts.__winShell = true;
		file = process.env.comspec || 'cmd.exe';
		args = ['/s', '/c', `"${cmd}"`];
		opts.windowsVerbatimArguments = true;
	}

	if (opts.shell) {
		file = opts.shell;
		delete opts.shell;
	}

	return fn(file, args, opts);
}

function getStream(process, stream, encoding, maxBuffer) {
	if (!process[stream]) {
		return null;
	}

	let ret;

	if (encoding) {
		ret = _getStream(process[stream], {
			encoding,
			maxBuffer
		});
	} else {
		ret = _getStream.buffer(process[stream], {maxBuffer});
	}

	return ret.catch(err => {
		err.stream = stream;
		err.message = `${stream} ${err.message}`;
		throw err;
	});
}

module.exports = (cmd, args, opts) => {
	let joinedCmd = cmd;

	if (Array.isArray(args) && args.length > 0) {
		joinedCmd += ' ' + args.join(' ');
	}

	const parsed = handleArgs(cmd, args, opts);
	const encoding = parsed.opts.encoding;
	const maxBuffer = parsed.opts.maxBuffer;

	let spawned;
	try {
		spawned = childProcess.spawn(parsed.cmd, parsed.args, parsed.opts);
	} catch (err) {
		return Promise.reject(err);
	}

	let removeExitHandler;
	if (parsed.opts.cleanup) {
		removeExitHandler = onExit(() => {
			spawned.kill();
		});
	}

	let timeoutId = null;
	let timedOut = false;

	const cleanupTimeout = () => {
		if (timeoutId) {
			clearTimeout(timeoutId);
			timeoutId = null;
		}
	};

	if (parsed.opts.timeout > 0) {
		timeoutId = setTimeout(() => {
			timeoutId = null;
			timedOut = true;
			spawned.kill(parsed.opts.killSignal);
		}, parsed.opts.timeout);
	}

	const processDone = new Promise(resolve => {
		spawned.on('exit', (code, signal) => {
			cleanupTimeout();
			resolve({code, signal});
		});

		spawned.on('error', err => {
			cleanupTimeout();
			resolve({err});
		});

		if (spawned.stdin) {
			spawned.stdin.on('error', err => {
				cleanupTimeout();
				resolve({err});
			});
		}
	});

	function destroy() {
		if (spawned.stdout) {
			spawned.stdout.destroy();
		}

		if (spawned.stderr) {
			spawned.stderr.destroy();
		}
	}

	const handlePromise = () => pFinally(Promise.all([
		processDone,
		getStream(spawned, 'stdout', encoding, maxBuffer),
		getStream(spawned, 'stderr', encoding, maxBuffer)
	]).then(arr => {
		const result = arr[0];
		const stdout = arr[1];
		const stderr = arr[2];

		let err = result.err;
		const code = result.code;
		const signal = result.signal;

		if (removeExitHandler) {
			removeExitHandler();
		}

		if (err || code !== 0 || signal !== null) {
			if (!err) {
				let output = '';

				if (Array.isArray(parsed.opts.stdio)) {
					if (parsed.opts.stdio[2] !== 'inherit') {
						output += output.length > 0 ? stderr : `\n${stderr}`;
					}

					if (parsed.opts.stdio[1] !== 'inherit') {
						output += `\n${stdout}`;
					}
				} else if (parsed.opts.stdio !== 'inherit') {
					output = `\n${stderr}${stdout}`;
				}

				err = new Error(`Command failed: ${joinedCmd}${output}`);
				err.code = code < 0 ? errname(code) : code;
			}

			// TODO: missing some timeout logic for killed
			// https://github.com/nodejs/node/blob/master/lib/child_process.js#L203
			// err.killed = spawned.killed || killed;
			err.killed = err.killed || spawned.killed;

			err.stdout = stdout;
			err.stderr = stderr;
			err.failed = true;
			err.signal = signal || null;
			err.cmd = joinedCmd;
			err.timedOut = timedOut;

			if (!parsed.opts.reject) {
				return err;
			}

			throw err;
		}

		return {
			stdout: handleOutput(parsed.opts, stdout),
			stderr: handleOutput(parsed.opts, stderr),
			code: 0,
			failed: false,
			killed: false,
			signal: null,
			cmd: joinedCmd,
			timedOut: false
		};
	}), destroy);

	crossSpawn._enoent.hookChildProcess(spawned, parsed.parsed);

	handleInput(spawned, parsed.opts);

	spawned.then = (onfulfilled, onrejected) => handlePromise().then(onfulfilled, onrejected);
	spawned.catch = onrejected => handlePromise().catch(onrejected);

	return spawned;
};

module.exports.stdout = function () {
	// TODO: set `stderr: 'ignore'` when that option is implemented
	return module.exports.apply(null, arguments).then(x => x.stdout);
};

module.exports.stderr = function () {
	// TODO: set `stdout: 'ignore'` when that option is implemented
	return module.exports.apply(null, arguments).then(x => x.stderr);
};

module.exports.shell = (cmd, opts) => handleShell(module.exports, cmd, opts);

module.exports.sync = (cmd, args, opts) => {
	const parsed = handleArgs(cmd, args, opts);

	if (isStream(parsed.opts.input)) {
		throw new TypeError('The `input` option cannot be a stream in sync mode');
	}

	const result = childProcess.spawnSync(parsed.cmd, parsed.args, parsed.opts);

	if (result.error || result.status !== 0) {
		throw (result.error || new Error(result.stderr === '' ? result.stdout : result.stderr));
	}

	result.stdout = handleOutput(parsed.opts, result.stdout);
	result.stderr = handleOutput(parsed.opts, result.stderr);

	return result;
};

module.exports.shellSync = (cmd, opts) => handleShell(module.exports.sync, cmd, opts);

module.exports.spawn = util.deprecate(module.exports, 'execa.spawn() is deprecated. Use execa() instead.');


code:
'use strict';
// The Node team wants to deprecate `process.bind(...)`.
//   https://github.com/nodejs/node/pull/2768
//
// However, we need the 'uv' binding for errname support.
// This is a defensive wrapper around it so `execa` will not fail entirely if it stops working someday.
//
// If this ever stops working. See: https://github.com/sindresorhus/execa/issues/31#issuecomment-215939939 for another possible solution.
let uv;

try {
	uv = process.binding('uv');

	if (typeof uv.errname !== 'function') {
		throw new TypeError('uv.errname is not a function');
	}
} catch (err) {
	console.error('execa/lib/errname: unable to establish process.binding(\'uv\')', err);
	uv = null;
}

function errname(uv, code) {
	if (uv) {
		return uv.errname(code);
	}

	if (!(code < 0)) {
		throw new Error('err >= 0');
	}

	return `Unknown system error ${code}`;
}

module.exports = code => errname(uv, code);

// Used for testing the fallback behavior
module.exports.__test__ = errname;


code:
'use strict';
const alias = ['stdin', 'stdout', 'stderr'];

const hasAlias = opts => alias.some(x => Boolean(opts[x]));

module.exports = opts => {
	if (!opts) {
		return null;
	}

	if (opts.stdio && hasAlias(opts)) {
		throw new Error(`It's not possible to provide \`stdio\` in combination with one of ${alias.map(x => `\`${x}\``).join(', ')}`);
	}

	if (typeof opts.stdio === 'string') {
		return opts.stdio;
	}

	const stdio = opts.stdio || [];

	if (!Array.isArray(stdio)) {
		throw new TypeError(`Expected \`stdio\` to be of type \`string\` or \`Array\`, got \`${typeof stdio}\``);
	}

	const result = [];
	const len = Math.max(stdio.length, alias.length);

	for (let i = 0; i < len; i++) {
		let value = null;

		if (stdio[i] !== undefined) {
			value = stdio[i];
		} else if (opts[alias[i]] !== undefined) {
			value = opts[alias[i]];
		}

		result[i] = value;
	}

	return result;
};


execa

description: A better `child_process`

repository: sindresorhus/execa

test: xo && nyc ava

keywords: exec child process execute fork execfile spawn file shell bin binary binaries npm path local

reporter: text lcov

exclude: **/fixtures/** **/test.js **/test/**


code:
# execa [![Build Status: Linux](https://travis-ci.org/sindresorhus/execa.svg?branch=master)](https://travis-ci.org/sindresorhus/execa) [![Build status: Windows](https://ci.appveyor.com/api/projects/status/x5ajamxtjtt93cqv/branch/master?svg=true)](https://ci.appveyor.com/project/sindresorhus/execa/branch/master) [![Coverage Status](https://coveralls.io/repos/github/sindresorhus/execa/badge.svg?branch=master)](https://coveralls.io/github/sindresorhus/execa?branch=master)

> A better [`child_process`](https://nodejs.org/api/child_process.html)

## Why

- Promise interface.
- [Strips EOF](https://github.com/sindresorhus/strip-eof) from the output so you don't have to `stdout.trim()`.
- Supports [shebang](https://en.wikipedia.org/wiki/Shebang_(Unix)) binaries cross-platform.
- [Improved Windows support.](https://github.com/IndigoUnited/node-cross-spawn#why)
- Higher max buffer. 10 MB instead of 200 KB.
- [Executes locally installed binaries by name.](#preferlocal)
- [Cleans up spawned processes when the parent process dies.](#cleanup)

## Install

```
$ npm install --save execa
```

## Usage

```js
const execa = require('execa');

execa('echo', ['unicorns']).then(result => {
	console.log(result.stdout);
	//=> 'unicorns'
});

// pipe the child process stdout to the current stdout
execa('echo', ['unicorns']).stdout.pipe(process.stdout);

execa.shell('echo unicorns').then(result => {
	console.log(result.stdout);
	//=> 'unicorns'
});

// example of catching an error
execa.shell('exit 3').catch(error => {
	console.log(error);
	/*
	{
		message: 'Command failed: /bin/sh -c exit 3'
		killed: false,
		code: 3,
		signal: null,
		cmd: '/bin/sh -c exit 3',
		stdout: '',
		stderr: '',
		timedOut: false
	}
	*/
});
```

## API

### execa(file, [arguments], [options])

Execute a file.

Think of this as a mix of `child_process.execFile` and `child_process.spawn`.

Returns a [`child_process` instance](https://nodejs.org/api/child_process.html#child_process_class_childprocess), which is enhanced to also be a `Promise` for a result `Object` with `stdout` and `stderr` properties.

### execa.stdout(file, [arguments], [options])

Same as `execa()`, but returns only `stdout`.

### execa.stderr(file, [arguments], [options])

Same as `execa()`, but returns only `stderr`.

### execa.shell(command, [options])

Execute a command through the system shell. Prefer `execa()` whenever possible, as it's both faster and safer.

Returns a [`child_process` instance](https://nodejs.org/api/child_process.html#child_process_class_childprocess).

The `child_process` instance is enhanced to also be promise for a result object with `stdout` and `stderr` properties.

### execa.sync(file, [arguments], [options])

Execute a file synchronously.

Returns the same result object as [`child_process.spawnSync`](https://nodejs.org/api/child_process.html#child_process_child_process_spawnsync_command_args_options).

This method throws an `Error` if the command fails.

### execa.shellSync(file, [options])

Execute a command synchronously through the system shell.

Returns the same result object as [`child_process.spawnSync`](https://nodejs.org/api/child_process.html#child_process_child_process_spawnsync_command_args_options).

### options

Type: `Object`

#### cwd

Type: `string`

Default: `process.cwd()`

Current working directory of the child process.

#### env

Type: `Object`

Default: `process.env`

Environment key-value pairs. Extends automatically from `process.env`. Set `extendEnv` to `false` if you don't want this.

#### extendEnv

Type: `boolean`

Default: `true`

Set to `false` if you don't want to extend the environment variables when providing the `env` property.

#### argv0

Type: `string`

Explicitly set the value of `argv[0]` sent to the child process. This will be set to `command` or `file` if not specified.

#### stdio

Type: `Array` `string`

Default: `pipe`

Child's [stdio](https://nodejs.org/api/child_process.html#child_process_options_stdio) configuration.

#### detached

Type: `boolean`

Prepare child to run independently of its parent process. Specific behavior [depends on the platform](https://nodejs.org/api/child_process.html#child_process_options_detached).

#### uid

Type: `number`

Sets the user identity of the process.

#### gid

Type: `number`

Sets the group identity of the process.

#### shell

Type: `boolean` `string`

Default: `false`

If `true`, runs `command` inside of a shell. Uses `/bin/sh` on UNIX and `cmd.exe` on Windows. A different shell can be specified as a string. The shell should understand the `-c` switch on UNIX or `/d /s /c` on Windows.

#### stripEof

Type: `boolean`

Default: `true`

[Strip EOF](https://github.com/sindresorhus/strip-eof) (last newline) from the output.

#### preferLocal

Type: `boolean`

Default: `true`

Prefer locally installed binaries when looking for a binary to execute.

If you `$ npm install foo`, you can then `execa('foo')`.

#### localDir

Type: `string`

Default: `process.cwd()`

Preferred path to find locally installed binaries in (use with `preferLocal`).

#### input

Type: `string` `Buffer` `stream.Readable`

Write some input to the `stdin` of your binary.

Streams are not allowed when using the synchronous methods.

#### reject

Type: `boolean`

Default: `true`

Setting this to `false` resolves the promise with the error instead of rejecting it.

#### cleanup

Type: `boolean`

Default: `true`

Keep track of the spawned process and `kill` it when the parent process exits.

#### encoding

Type: `string`

Default: `utf8`

Specify the character encoding used to decode the `stdout` and `stderr` output.

#### timeout

Type: `number`

Default: `0`

If timeout is greater than `0`, the parent will send the signal identified by the `killSignal` property (the default is `SIGTERM`) if the child runs longer than timeout milliseconds.

#### maxBuffer

Type: `number`

Default: `10000000` (10MB)

Largest amount of data in bytes allowed on `stdout` or `stderr`.

#### killSignal

Type: `string` `number`

Default: `SIGTERM`

Signal value to be used when the spawned process will be killed.

#### stdin

Type: `string` `number` `Stream` `undefined` `null`

Default: `pipe`

Same options as [`stdio`](https://nodejs.org/dist/latest-v6.x/docs/api/child_process.html#child_process_options_stdio).

#### stdout

Type: `string` `number` `Stream` `undefined` `null`

Default: `pipe`

Same options as [`stdio`](https://nodejs.org/dist/latest-v6.x/docs/api/child_process.html#child_process_options_stdio).

#### stderr

Type: `string` `number` `Stream` `undefined` `null`

Default: `pipe`

Same options as [`stdio`](https://nodejs.org/dist/latest-v6.x/docs/api/child_process.html#child_process_options_stdio).

## Tips

### Save and pipe output from a child process

Let's say you want to show the output of a child process in real-time while also saving it to a variable.

```js
const execa = require('execa');
const getStream = require('get-stream');

const stream = execa('echo', ['foo']).stdout;

stream.pipe(process.stdout);

getStream(stream).then(value => {
	console.log('child output:', value);
});
```

## License

MIT © [Sindre Sorhus](https://sindresorhus.com)


code:
'use strict';
const PassThrough = require('stream').PassThrough;

module.exports = opts => {
	opts = Object.assign({}, opts);

	const array = opts.array;
	let encoding = opts.encoding;
	const buffer = encoding === 'buffer';
	let objectMode = false;

	if (array) {
		objectMode = !(encoding || buffer);
	} else {
		encoding = encoding || 'utf8';
	}

	if (buffer) {
		encoding = null;
	}

	let len = 0;
	const ret = [];
	const stream = new PassThrough({objectMode});

	if (encoding) {
		stream.setEncoding(encoding);
	}

	stream.on('data', chunk => {
		ret.push(chunk);

		if (objectMode) {
			len = ret.length;
		} else {
			len += chunk.length;
		}
	});

	stream.getBufferedValue = () => {
		if (array) {
			return ret;
		}

		return buffer ? Buffer.concat(ret, len) : ret.join('');
	};

	stream.getBufferedLength = () => len;

	return stream;
};


code:
'use strict';
const bufferStream = require('./buffer-stream');

function getStream(inputStream, opts) {
	if (!inputStream) {
		return Promise.reject(new Error('Expected a stream'));
	}

	opts = Object.assign({maxBuffer: Infinity}, opts);

	const maxBuffer = opts.maxBuffer;
	let stream;
	let clean;

	const p = new Promise((resolve, reject) => {
		const error = err => {
			if (err) { // null check
				err.bufferedData = stream.getBufferedValue();
			}

			reject(err);
		};

		stream = bufferStream(opts);
		inputStream.once('error', error);
		inputStream.pipe(stream);

		stream.on('data', () => {
			if (stream.getBufferedLength() > maxBuffer) {
				reject(new Error('maxBuffer exceeded'));
			}
		});
		stream.once('error', error);
		stream.on('end', resolve);

		clean = () => {
			// some streams doesn't implement the `stream.Readable` interface correctly
			if (inputStream.unpipe) {
				inputStream.unpipe(stream);
			}
		};
	});

	p.then(clean, clean);

	return p.then(() => stream.getBufferedValue());
}

module.exports = getStream;
module.exports.buffer = (stream, opts) => getStream(stream, Object.assign({}, opts, {encoding: 'buffer'}));
module.exports.array = (stream, opts) => getStream(stream, Object.assign({}, opts, {array: true}));


get-stream

description: Get a stream as a string, buffer, or array

repository: sindresorhus/get-stream

test: xo && ava

keywords: get stream promise concat string str text buffer read data consume readable readablestream array object obj