Skip to content

Commit

Permalink
Handle EPIPE on stdout gracefully (#7194)
Browse files Browse the repository at this point in the history
* Handle EPIPE on stdout gracefully

* Update CHANGELOG.md
  • Loading branch information
Abhishek Reddy authored and arcanis committed Apr 19, 2019
1 parent d667753 commit d4805c3
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 0 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,17 @@ Please add one entry in this file for each change in Yarn's behavior. Use the sa

[#7127](https://github.com/yarnpkg/yarn/pull/7127) - [**Eli Perelman**](https://github.com/eliperelman)

- Prevents EPIPE errors from being printed.

[#7194](https://github.com/yarnpkg/yarn/pull/7194) - [**Abhishek Reddy**](https://github.com/arbscht)

- Adds support for the npm enterprise URLs when computing the offline mirror filenames.

[#7200](https://github.com/yarnpkg/yarn/pull/7200) - [**John Millikin**](https://john-millikin.com)

- Tweaks the lockfile parser logic to parse a few extra cases

[#7210](https://github.com/yarnpkg/yarn/pull/7210) - [**Maël Nison**](https://twitter.com/arcanis)

## 1.15.2

Expand Down
96 changes: 96 additions & 0 deletions __tests__/pipe.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/* @flow */
/* eslint max-len: 0 */

import execa from 'execa';
import {sh} from 'puka';
import makeTemp from './_temp.js';
import * as fs from '../src/util/fs.js';

const path = require('path');

function runYarnStreaming(args: Array<string> = [], options: Object = {}): execa.ExecaChildPromise {
if (!options['env']) {
options['env'] = {...process.env};
options['extendEnv'] = false;
}
options['env']['FORCE_COLOR'] = 0;

return execa.shell(sh`${path.resolve(__dirname, '../bin/yarn')} ${args}`, options);
}

test('terminate console stream quietly on EPIPE', async () => {
const cwd = await makeTemp();
const packageJsonPath = path.join(cwd, 'package.json');
const initialManifestFile = JSON.stringify({name: 'test', license: 'ISC', version: '1.0.0'});

await fs.writeFile(packageJsonPath, initialManifestFile);

const {stdout, stderr} = runYarnStreaming(['versions'], {cwd});

stdout.destroy();

await new Promise((resolve, reject) => {
let stderrOutput = '';
stderr.on('readable', () => {
const chunk = stderr.read();
if (chunk) {
stderrOutput += chunk;
} else {
resolve(stderrOutput);
}
});
stderr.on('error', err => {
reject(err);
});
})
.then(stderrOutput => {
expect(stderrOutput).not.toMatch(/EPIPE/);
})
.catch(err => {
expect(err).toBeFalsy();
});
});

test('terminate console stream preserving zero exit code on EPIPE', async () => {
const cwd = await makeTemp();
const packageJsonPath = path.join(cwd, 'package.json');
const initialManifestFile = JSON.stringify({name: 'test', license: 'ISC', version: '1.0.0'});

await fs.writeFile(packageJsonPath, initialManifestFile);

const proc = runYarnStreaming(['versions'], {cwd});

const {stdout} = proc;

stdout.destroy();

await new Promise(resolve => {
proc.on('exit', function(code, signal) {
resolve(code);
});
}).then(exitCode => {
expect(exitCode).toEqual(0);
});
});

test('terminate console stream preserving non-zero exit code on EPIPE', async () => {
const cwd = await makeTemp();
const packageJsonPath = path.join(cwd, 'package.json');
const initialManifestFile = JSON.stringify({name: 'test', license: 'ISC', version: '1.0.0'});

await fs.writeFile(packageJsonPath, initialManifestFile);

const proc = runYarnStreaming(['add'], {cwd});

const {stdout} = proc;

stdout.destroy();

await new Promise(resolve => {
proc.on('exit', function(code, signal) {
resolve(code);
});
}).then(exitCode => {
expect(exitCode).toEqual(1);
});
});
8 changes: 8 additions & 0 deletions src/cli/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ import handleSignals from '../util/signal-handler.js';
import {boolify, boolifyWithDefault} from '../util/conversion.js';
import {ProcessTermError} from '../errors';

process.stdout.prependListener('error', err => {
// swallow err only if downstream consumer process closed pipe early
if (err.code === 'EPIPE' || err.code === 'ERR_STREAM_DESTROYED') {
return;
}
throw err;
});

function findProjectRoot(base: string): string {
let prev = null;
let dir = base;
Expand Down

0 comments on commit d4805c3

Please sign in to comment.