From 0e67b6c0d3d44350020040766a2ccd4c1249d002 Mon Sep 17 00:00:00 2001 From: arbscht Date: Fri, 12 Apr 2019 23:35:07 +1200 Subject: [PATCH 1/2] Handle EPIPE on stdout gracefully --- __tests__/pipe.js | 96 +++++++++++++++++++++++++++++++++++++++++++++++ src/cli/index.js | 8 ++++ 2 files changed, 104 insertions(+) create mode 100644 __tests__/pipe.js diff --git a/__tests__/pipe.js b/__tests__/pipe.js new file mode 100644 index 0000000000..a0478ec8d9 --- /dev/null +++ b/__tests__/pipe.js @@ -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 = [], 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); + }); +}); diff --git a/src/cli/index.js b/src/cli/index.js index bcc70f15c7..3e3ae3fc1e 100644 --- a/src/cli/index.js +++ b/src/cli/index.js @@ -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; From 15e04c959b132c0a653d2f534074daaffbe77f46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Fri, 19 Apr 2019 17:47:36 +0100 Subject: [PATCH 2/2] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82411fdd5c..fddaab4ee7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,10 @@ 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) + ## 1.15.2 The 1.15.1 doesn't exist due to a release hiccup.