Update app and tooling
This commit is contained in:
parent
3046531bdd
commit
e620ec7349
4950 changed files with 2975120 additions and 10 deletions
4
node_modules/promisepipe/.travis.yml
generated
vendored
Normal file
4
node_modules/promisepipe/.travis.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- "10"
|
||||
- "8"
|
||||
20
node_modules/promisepipe/LICENSE
generated
vendored
Normal file
20
node_modules/promisepipe/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Esa-Matti Suuronen and Thomas Grainger
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
92
node_modules/promisepipe/README.md
generated
vendored
Normal file
92
node_modules/promisepipe/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
# promisePipe
|
||||
|
||||
Safely pipe node.js streams while capturing all errors to a single promise.
|
||||
|
||||
## Install
|
||||
|
||||
npm install promisepipe
|
||||
|
||||
## API
|
||||
|
||||
```
|
||||
promisePipe(<readable stream>, [transform streams...], <writeable stream>)
|
||||
```
|
||||
|
||||
It returns a native promise. On success the resolved value will be an array of the
|
||||
streams passed in. When rejected an error object is created with following
|
||||
keys:
|
||||
|
||||
- `source`: The stream that caused the error
|
||||
- `originalError`: Original error from the stream
|
||||
- `message`: The error message from original error
|
||||
|
||||
Note: the last stream in the chain needs to be a writable stream, not a duplex/transform stream. If you use a 3rd party library which returns deplux streams instead of writable streams, you'll need to add something like [`.pipe(devnull())`](https://www.npmjs.com/package/dev-null) to the end, otherwise the promise will never resolve ([#16](https://github.com/epeli/node-promisepipe/issues/16)).
|
||||
|
||||
Starting with v3, all streams are destroyed if there's an error to prevent memory leaks.
|
||||
|
||||
## Example
|
||||
|
||||
```javascript
|
||||
var promisePipe = require("promisepipe");
|
||||
|
||||
promisePipe(
|
||||
fs.createReadStream(INPUT_FILE),
|
||||
new UpcaseTransform(),
|
||||
fs.createWriteStream(OUTPUT_FILE),
|
||||
).then(function(streams){
|
||||
console.log("Done writing to the output file stream!");
|
||||
}, function(err) {
|
||||
console.log("This stream failed:", err.source);
|
||||
console.log("Original error was:", err.originalError);
|
||||
});
|
||||
```
|
||||
|
||||
or with async-wait
|
||||
|
||||
```javascript
|
||||
var promisePipe = require("promisepipe");
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
await promisePipe(
|
||||
fs.createReadStream(INPUT_FILE),
|
||||
new UpcaseTransform(),
|
||||
fs.createWriteStream(OUTPUT_FILE)
|
||||
);
|
||||
console.log("Done writing to the output file stream!");
|
||||
} catch (err) {
|
||||
console.log("This stream failed:", err.source);
|
||||
console.log("Original error was:", err.originalError);
|
||||
}
|
||||
})();
|
||||
|
||||
```
|
||||
|
||||
## Why?
|
||||
|
||||
Stream piping in node.js is cool, but error handling is not because streams
|
||||
do not bubble errors to the target streams.
|
||||
|
||||
For example if the previous example is written like this:
|
||||
|
||||
```javascript
|
||||
fs.createReadStream(INPUT_FILE)
|
||||
.pipe(new UpcaseTransform())
|
||||
.pipe(fs.createReadStream(OUTPUT_FILE))
|
||||
```
|
||||
|
||||
It might crash your program at any time. You must handle the errors
|
||||
from each stream manually like this:
|
||||
|
||||
```javascript
|
||||
fs.createReadStream(INPUT_FILE).on("error", function(err) {
|
||||
// handle the error
|
||||
}).pipe(new UpcaseTransform()).on("error", function(err) {
|
||||
// handle the error
|
||||
}).pipe(fs.createReadStream(OUTPUT_FILE)).on("error", function(err) {
|
||||
// handle the error
|
||||
})
|
||||
```
|
||||
|
||||
Handling errors this way can be very cumbersome. `promisepipe` simplifies
|
||||
error handling by sending the first error occurance into a promise.
|
||||
1
node_modules/promisepipe/fixtures/input.txt
generated
vendored
Normal file
1
node_modules/promisepipe/fixtures/input.txt
generated
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
foobar
|
||||
1
node_modules/promisepipe/fixtures/input.txt.x
generated
vendored
Normal file
1
node_modules/promisepipe/fixtures/input.txt.x
generated
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
foobarX
|
||||
1
node_modules/promisepipe/fixtures/input.txt.y
generated
vendored
Normal file
1
node_modules/promisepipe/fixtures/input.txt.y
generated
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
foobarY
|
||||
96
node_modules/promisepipe/index.js
generated
vendored
Normal file
96
node_modules/promisepipe/index.js
generated
vendored
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
'use strict';
|
||||
|
||||
class StreamError extends Error {
|
||||
constructor(err, source) {
|
||||
const message = err && err.message || err;
|
||||
super(message);
|
||||
this.source = source;
|
||||
this.originalError = err;
|
||||
}
|
||||
}
|
||||
|
||||
const events = ['error', 'end', 'close', 'finish'];
|
||||
|
||||
function cleanupEventHandlers(stream, listener) {
|
||||
events.map(e => stream.removeListener(e, listener));
|
||||
}
|
||||
|
||||
function streamPromise(stream, state) {
|
||||
if (stream === process.stdout || stream === process.stderr) {
|
||||
return Promise.resolve(stream);
|
||||
}
|
||||
|
||||
// see https://github.com/epeli/node-promisepipe/issues/2
|
||||
// and https://github.com/epeli/node-promisepipe/issues/15
|
||||
const isReadable = stream.readable || typeof stream._read === 'function';
|
||||
|
||||
function on(evt) {
|
||||
function executor(resolve, reject) {
|
||||
const fn = evt === 'error' ?
|
||||
err => reject(new StreamError(err, stream)) :
|
||||
() => {
|
||||
// For readable streams, we ignore the "finish" event. However, if there
|
||||
// already was an error on another stream, the "end" event may never come,
|
||||
// so in that case we accept "finish" too.
|
||||
if (isReadable && evt === 'finish' && !state.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
cleanupEventHandlers(stream, fn);
|
||||
resolve(stream);
|
||||
};
|
||||
stream.on(evt, fn);
|
||||
}
|
||||
|
||||
return new Promise(executor);
|
||||
}
|
||||
|
||||
return Promise.race(events.map(on));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {...Stream} stream
|
||||
*/
|
||||
function promisePipe(stream) {
|
||||
let i = arguments.length;
|
||||
const streams = [];
|
||||
while ( i-- ) streams[i] = arguments[i];
|
||||
|
||||
const allStreams = streams
|
||||
.reduce((current, next) => current.concat(next), []);
|
||||
|
||||
allStreams.reduce((current, next) => current.pipe(next));
|
||||
return allStreamsDone(streams);
|
||||
}
|
||||
|
||||
function allStreamsDone(allStreams) {
|
||||
let state = {};
|
||||
let firstRejection;
|
||||
|
||||
return Promise.all(allStreams.map(stream => streamPromise(stream, state).catch((e) => {
|
||||
if (!firstRejection) {
|
||||
firstRejection = e;
|
||||
state.error = true;
|
||||
|
||||
// Close all streams as they are not closed automatically on error.
|
||||
allStreams.forEach(stream => {
|
||||
if (stream !== process.stdout && stream !== process.stderr) {
|
||||
stream.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
}))).then((allResults) => {
|
||||
if (firstRejection) {
|
||||
throw firstRejection;
|
||||
}
|
||||
|
||||
return allResults;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = Object.assign(promisePipe, {
|
||||
__esModule: true,
|
||||
default: promisePipe,
|
||||
justPromise: streams => allStreamsDone(streams),
|
||||
StreamError,
|
||||
});
|
||||
27
node_modules/promisepipe/package.json
generated
vendored
Normal file
27
node_modules/promisepipe/package.json
generated
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"name": "promisepipe",
|
||||
"version": "3.0.0",
|
||||
"description": "Pipe node.js streams safely with Promises",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "mocha test.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/epeli/node-promisepipe"
|
||||
},
|
||||
"keywords": [
|
||||
"promise",
|
||||
"stream"
|
||||
],
|
||||
"author": "Esa-Matti Suuronen",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/epeli/node-promisepipe/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "^3.2.0",
|
||||
"mz": "^2.6.0"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
208
node_modules/promisepipe/test.js
generated
vendored
Normal file
208
node_modules/promisepipe/test.js
generated
vendored
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
/*global it, describe, beforeEach */
|
||||
|
||||
var stream = require("stream");
|
||||
var fs = require("mz/fs");
|
||||
var assert = require("assert");
|
||||
var util = require("util");
|
||||
|
||||
var promisePipe = require("./index");
|
||||
var StreamError = promisePipe.StreamError;
|
||||
|
||||
var INPUT = __dirname + "/fixtures/input.txt";
|
||||
var OUTPUT = __dirname + "/fixtures/output.txt";
|
||||
|
||||
function Upcase(){
|
||||
stream.Transform.call(this);
|
||||
}
|
||||
|
||||
function W () {
|
||||
stream.Writable.call(this);
|
||||
}
|
||||
|
||||
function raise(err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
util.inherits(Upcase, stream.Transform);
|
||||
util.inherits(W, stream.Writable);
|
||||
|
||||
Upcase.prototype._transform = function(chunk, encoding, cb) {
|
||||
if (/X/.test(chunk.toString())) {
|
||||
cb(new Error("X is not allowed"));
|
||||
return;
|
||||
}
|
||||
this.str = (this.str || '') + chunk.toString();
|
||||
this.push(chunk.toString().toUpperCase());
|
||||
cb();
|
||||
};
|
||||
|
||||
Upcase.prototype._flush = function(cb) {
|
||||
setTimeout(() => {
|
||||
if (/Y/.test(this.str)) {
|
||||
cb(new Error("Y is not allowed"));
|
||||
return;
|
||||
}
|
||||
cb()
|
||||
}, 1);
|
||||
};
|
||||
|
||||
W.prototype._write = function () {
|
||||
this.emit("error", new Error("Write failed"));
|
||||
this.emit("error", new Error("Write failed"));
|
||||
};
|
||||
|
||||
describe("promisePipe", function() {
|
||||
|
||||
beforeEach(function() {
|
||||
return fs
|
||||
.unlink(OUTPUT)
|
||||
.catch(err => err.code === "ENOENT" || raise(err));
|
||||
});
|
||||
|
||||
it("can do basic piping", function() {
|
||||
var input = fs.createReadStream(INPUT);
|
||||
var output = fs.createWriteStream(OUTPUT);
|
||||
|
||||
return promisePipe(input, output).then(function(pipeChain) {
|
||||
|
||||
assert.equal(input, pipeChain[0], "Resolved promise passes stream pipe chain back");
|
||||
assert.equal(output, pipeChain[1]);
|
||||
|
||||
return fs
|
||||
.readFile(OUTPUT)
|
||||
.then(data => assert.equal(data.toString().trim(), "foobar"));
|
||||
});
|
||||
});
|
||||
|
||||
["stdout", "stderr"].forEach(function(stdio) {
|
||||
it("can pipe to " + stdio, function() {
|
||||
var input = fs.createReadStream(INPUT);
|
||||
return promisePipe(input, process[stdio]);
|
||||
});
|
||||
|
||||
it("can handle errors when dest is " + stdio, function() {
|
||||
var input = fs.createReadStream("bad");
|
||||
var output = process[stdio];
|
||||
|
||||
return promisePipe(input, output)
|
||||
.catch(err => err)
|
||||
.then(function(err) {
|
||||
assert(err);
|
||||
assert.equal(err.originalError.code, "ENOENT");
|
||||
assert.equal(err.source, input);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("can handle errors from source", function() {
|
||||
var input = fs.createReadStream("bad");
|
||||
var output = fs.createWriteStream(OUTPUT);
|
||||
|
||||
return promisePipe(input, output)
|
||||
.catch(err => err)
|
||||
.then(function(err) {
|
||||
assert(err);
|
||||
assert.equal(err.originalError.code, "ENOENT");
|
||||
assert.equal(err.source, input);
|
||||
});
|
||||
});
|
||||
|
||||
it("can handle errors from target", function() {
|
||||
var input = fs.createReadStream(INPUT);
|
||||
var output = fs.createWriteStream("/bad");
|
||||
|
||||
return promisePipe(input, output)
|
||||
.catch(err => err)
|
||||
.then(function(err) {
|
||||
assert(err);
|
||||
assert.ok([ "EACCES", "EPERM" ].includes(err.originalError.code));
|
||||
});
|
||||
});
|
||||
|
||||
it("can pipe via transforms", function() {
|
||||
var input = fs.createReadStream(INPUT);
|
||||
var output = fs.createWriteStream(OUTPUT);
|
||||
|
||||
return promisePipe(input, new Upcase(), output).then(function() {
|
||||
return fs
|
||||
.readFile(OUTPUT)
|
||||
.then(data => assert.equal(data.toString().trim(), "FOOBAR"));
|
||||
});
|
||||
});
|
||||
|
||||
it("can handle errors from transforms", function() {
|
||||
var input = fs.createReadStream(INPUT + ".x");
|
||||
var output = fs.createWriteStream(OUTPUT);
|
||||
|
||||
return promisePipe(input, new Upcase(), output)
|
||||
.catch(err => err)
|
||||
.then(function(err) {
|
||||
assert(err);
|
||||
assert.equal(err.originalError.message, "X is not allowed");
|
||||
});
|
||||
});
|
||||
|
||||
it("can handle errors from _flush in transforms", function() {
|
||||
var input = fs.createReadStream(INPUT + ".y");
|
||||
var output = fs.createWriteStream(OUTPUT);
|
||||
|
||||
const upcase = new Upcase()
|
||||
return promisePipe(input, upcase, output)
|
||||
.catch(err => err)
|
||||
.then(function(err) {
|
||||
assert(err);
|
||||
assert.equal(err.originalError.message, "Y is not allowed");
|
||||
});
|
||||
});
|
||||
|
||||
it("can handle multiple errors", function() {
|
||||
var input = fs.createReadStream(INPUT);
|
||||
|
||||
return promisePipe(input, new W())
|
||||
.catch(err => err)
|
||||
.then(function(err) {
|
||||
assert(err);
|
||||
assert.equal(err.originalError.message, "Write failed");
|
||||
});
|
||||
});
|
||||
|
||||
it("closes streams on errors", function() {
|
||||
var input = fs.createReadStream(INPUT + ".x");
|
||||
var output = fs.createWriteStream(OUTPUT);
|
||||
|
||||
return promisePipe(input, new Upcase(), output)
|
||||
.catch(err => err)
|
||||
.then(function() {
|
||||
return fs.unlink(OUTPUT);
|
||||
});
|
||||
});
|
||||
|
||||
it("resolves chains with transform streams on error", function() {
|
||||
var input = fs.createReadStream(INPUT);
|
||||
var output = fs.createWriteStream("/bad");
|
||||
|
||||
return promisePipe(input, new Upcase(), output)
|
||||
.catch(err => err)
|
||||
.then(function(err) {
|
||||
assert(err);
|
||||
assert.ok([ "EACCES", "EPERM" ].includes(err.originalError.code));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('StreamError', function() {
|
||||
it('uses the provided error message', function() {
|
||||
var err = new StreamError({message: 'foo'});
|
||||
assert.equal(err.message,'foo');
|
||||
});
|
||||
|
||||
it('uses assign message as error string param', function() {
|
||||
var err = new StreamError('foo');
|
||||
assert.equal(err.message,'foo');
|
||||
});
|
||||
|
||||
it('should not throw if no message', function() {
|
||||
var err = new StreamError();
|
||||
assert.equal(err.message,'');
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue