From 305715781718eb47a238a941141ed6ce45381149 Mon Sep 17 00:00:00 2001 From: arbscht Date: Fri, 12 Apr 2019 23:35:07 +1200 Subject: [PATCH] Handle EPIPE on stdout gracefully --- .../package.json.bin | 56 +++++++++++++ .../GET/registry.yarnpkg.com/is-pnp.bin | 18 +++++ .../is-pnp/-/is-pnp-1.0.2.tgz.bin | Bin 0 -> 2033 bytes __tests__/pipe.js | 75 ++++++++++++++++++ package.json | 1 + src/cli/index.js | 8 ++ yarn.lock | 7 ++ 7 files changed, 165 insertions(+) create mode 100644 __tests__/fixtures/request-cache/GET/gitlab.com/leanlabsio/kanban/raw/f139bd887dea8e48c46fd7fcfe42b5ffc53d79dd/package.json.bin create mode 100644 __tests__/fixtures/request-cache/GET/registry.yarnpkg.com/is-pnp.bin create mode 100644 __tests__/fixtures/request-cache/GET/registry.yarnpkg.com/is-pnp/-/is-pnp-1.0.2.tgz.bin create mode 100644 __tests__/pipe.js diff --git a/__tests__/fixtures/request-cache/GET/gitlab.com/leanlabsio/kanban/raw/f139bd887dea8e48c46fd7fcfe42b5ffc53d79dd/package.json.bin b/__tests__/fixtures/request-cache/GET/gitlab.com/leanlabsio/kanban/raw/f139bd887dea8e48c46fd7fcfe42b5ffc53d79dd/package.json.bin new file mode 100644 index 0000000000..066c1a431f --- /dev/null +++ b/__tests__/fixtures/request-cache/GET/gitlab.com/leanlabsio/kanban/raw/f139bd887dea8e48c46fd7fcfe42b5ffc53d79dd/package.json.bin @@ -0,0 +1,56 @@ +HTTP/1.1 200 OK +Server: nginx +Date: Thu, 04 Apr 2019 10:02:38 GMT +Content-Type: text/plain; charset=utf-8 +Content-Length: 1181 +Cache-Control: max-age=3600, public +Content-Disposition: inline +Etag: W/"78309fbf8af4479c47eca65b0c5e3f51" +X-Content-Type-Options: nosniff +X-Frame-Options: DENY +X-Request-Id: MdyDA59Ki6 +X-Runtime: 0.089005 +X-Ua-Compatible: IE=edge +X-Xss-Protection: 1; mode=block +Strict-Transport-Security: max-age=31536000 +Content-Security-Policy: object-src 'none'; worker-src https://assets.gitlab-static.net https://gl-canary.freetls.fastly.net https://gitlab.com blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://assets.gitlab-static.net https://gl-canary.freetls.fastly.net https://www.google.com/recaptcha/ https://www.recaptcha.net/ https://www.gstatic.com/recaptcha/ https://apis.google.com; style-src 'self' 'unsafe-inline' https://assets.gitlab-static.net https://gl-canary.freetls.fastly.net; img-src * data: blob:; frame-src 'self' https://www.google.com/recaptcha/ https://www.recaptcha.net/ https://content.googleapis.com https://content-compute.googleapis.com https://content-cloudbilling.googleapis.com https://content-cloudresourcemanager.googleapis.com https://*.codesandbox.io; frame-ancestors 'self'; connect-src 'self' https://assets.gitlab-static.net https://gl-canary.freetls.fastly.net wss://gitlab.com https://sentry.gitlab.net https://customers.gitlab.com https://snowplow.trx.gitlab.net + +{ + "name": "kanban", + "version": "0.0.1", + "repository": "gitlab.com/leanlabsio/kanban", + "scripts": { + "install": "npm install", + "build": "grunt build", + "watch": "grunt watch" + }, + "devDependencies": { + "grunt": "~0.4.1", + "grunt-cli": "~0.1.13", + "grunt-contrib-copy": "^0.5.0", + "grunt-contrib-concat": "~0.5.0", + "grunt-contrib-watch": "~0.5.3", + "grunt-contrib-uglify": "~0.7.0", + "grunt-sass": "1.0.0", + "grunt-contrib-connect": "~0.8.0", + "grunt-connect-proxy": "~0.1.11" + }, + "dependencies": { + "angular": "=1.5.6", + "angular-lodash": "https://github.com/EMSSConsulting/angular-lodash.git#68a726c", + "foundation-sites": "5.5.2", + "angular-foundation": "https://github.com/pineconellc/angular-foundation.git#8f3f260", + "angular-loading-bar": "=0.5.2", + "angular-storage": "=0.0.6", + "angular-ui-router": "=0.3.0", + "angularjs-datepicker": "=0.2.15", + "font-awesome": "=4.6.3", + "markdown-it": "=5.0.2", + "markdown-it-emoji": "=1.1.0", + "ng-sortable": "=1.3.6", + "sass-flex-mixin": "=1.0.3", + "lodash": "=4.13.1", + "twemoji": "=2.1.0", + "angular-file-upload": "=2.3.4" + } +} diff --git a/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/is-pnp.bin b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/is-pnp.bin new file mode 100644 index 0000000000..e964520335 --- /dev/null +++ b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/is-pnp.bin @@ -0,0 +1,18 @@ +HTTP/1.1 200 OK +Date: Thu, 04 Apr 2019 10:01:49 GMT +Content-Type: application/vnd.npm.install-v1+json +Content-Length: 4072 +Connection: keep-alive +Set-Cookie: __cfduid=df244f3eed75eb2c0e38ba688ab565fc41554372108; expires=Fri, 03-Apr-20 10:01:48 GMT; path=/; domain=.registry.yarnpkg.com; HttpOnly +CF-Cache-Status: MISS +Cache-Control: max-age=300 +CF-Ray: 4c22716d384c0b20-SYD +Accept-Ranges: bytes +ETag: "49fffc656197ace1f624570132931c23" +Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct" +Last-Modified: Thu, 01 Nov 2018 01:11:09 GMT +Vary: accept-encoding, accept +x-amz-meta-rev: 3-72d30e72712ca5e8a55f6613d7a30236 +Server: cloudflare + +{"versions":{"1.0.0":{"name":"is-pnp","version":"1.0.0","bin":{"is-pnp":"./bin.js"},"_hasShrinkwrap":false,"directories":{},"dist":{"shasum":"47d3d7151df242eb7ca4768fc2e7f6228871899a","integrity":"sha512-Lx0Sh5h20HtLz+xnlh4NRhQ6w231tGN4yuU3oBi6vn7oSbiIk2V4sqoIn00bczm9ojwkFPzzFUeSOIKEgYpszA==","tarball":"https://registry.npmjs.org/is-pnp/-/is-pnp-1.0.0.tgz","fileCount":5,"unpackedSize":2268,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJb2lGmCRA9TVsSAnZWagAARHYP/1botUA//2RoqJEP3nHB\nnQ0G1qhSYL685aLgZTWJW9pS3ucPzxxeofbnByOsbSjUqycwsHBqL/DDeYsq\nz3b0dmfcfESK+CSafYECSnj8kPJH5N5VU2UXs+A+wLEM0D0nSe4wfJLVptA0\nrOpeYrkfwX8ZC1EGLGvQM4cMqpk3Ji6RBZHtD0U/ttYR3A0vP5P76oO2/tBl\n3QSK3nJSpU83OkMBPVY3hnDrGkCjJqwvcqWoWUG0/BtHS5KYD99yrxFAzT5f\nMhtqCZiediDcGtrMhP5b0VAkqdrwFPg/tZPpgqYCevR+2ipWhkxhD0Z4yyx1\n2AuJnEoQKRiBiFPIv5aiJZD9Jj00iey4tdQ1NcXnRtAA16Lo0t4MX/OnkyJW\nBfcSd795mVpe4AO9u6bqMDZfkQrBRunxa7OKKCEXUGihm0YNtTWKat5ym1ia\n9H9Bwlgm3GoFAuHQMu07ZEwidLpWoQfjrTUOufw+/rKNSIRoMNuL3Z/8i4Hj\nazYRMgdzb9rkf6+F7LLJKnyZaDY/bS/KIb61LdkfkX8JCcKUfhVAd5KIvI4e\nAwLAUfHwWM2u2smbexFzvgvXnNFmVrcCGBbMcIrQ+JfiJ+izFVJ7YyLY+Fpt\nwxbV0Vug6gyYDyDtjFCdeL2wFmgR3+nASXWFY4CZ+ZoFX+ontSZ4U5b9PTIs\n1ojq\r\n=DAoM\r\n-----END PGP SIGNATURE-----\r\n"},"engines":{"node":">=6"}},"1.0.1":{"name":"is-pnp","version":"1.0.1","bin":{"is-pnp":"./sources/cli.js"},"_hasShrinkwrap":false,"directories":{},"dist":{"shasum":"ec0e4e443287214ce90bcb6fcd112d20de51f120","integrity":"sha512-uwFQ9SabYoNJwZnDqHUs+WTPvegmoLHjVQqkX4+MiKSjhRBRk+n/zKBAex4yvWnzbN352W7FUuYX8L5yEVmiVA==","tarball":"https://registry.npmjs.org/is-pnp/-/is-pnp-1.0.1.tgz","fileCount":5,"unpackedSize":2276,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJb2lJICRA9TVsSAnZWagAAX2IP/jE0+3aAdO7cu9DXNJnb\njoi3rDo8CRfARhWbJRhUj7omDxeP37U3dIIkK69AL8XZSPPUQ8o2uH1AOjZB\niI8cp0IX4bgalRXzEWPj3v8l3z6/BwJoUXy7N5ugdxron+AYj4kS4I1jI6Pa\nUo3z096sc8zAgXFE00RiKwUBGNw+4xpg0yVri/vXpezsahi8r4qZbrc9/hr3\nWzoIK6GwQnGpn5FEhdiuPbB0QiZvalYHn5v2IWtMpQG4ed6jM/vSBAqKqkFv\n95McUD13wz7iJ59BaJHwbypD/WFWz34ZBPZ8QpONk6UAJ2IZwh6J2Wj44k7a\nNtiEj3xo3A5stikfELI//H0AruTI6sSI++EJ8YvHQUj73s6kG0Z/FzPptgKp\nKYLSqXNAC/zHiId1s7uj+jsCLXIm4G2bPQUTsDMfejDS6TfjAVO2COxRF34q\ncLGzYFL1/R+/hO4ckoLl7vz6Vm/zSlMaxZPDUFDy8d0ScxHZOL+760cdIhjT\nr1XGyXAmyjGLWTHvA6sE5X0H0PgghQErsLYXi679KBdnZjN2+Xcmv0pNIqbg\nwS7SBjV+KGpc1PnwtZdfvZcegvY+252zsBvRye9/hcQ0Rdmetbod8XicA3Bw\nuTLC2N9opHeTI7a9bKrACXkSApJftjzYSfnycb27r9bgIAY6BeNhr7JFEMKV\nnxUD\r\n=8Blc\r\n-----END PGP SIGNATURE-----\r\n"},"engines":{"node":">=6"}},"1.0.2":{"name":"is-pnp","version":"1.0.2","bin":{"is-pnp":"./sources/cli.js"},"_hasShrinkwrap":false,"directories":{},"dist":{"shasum":"cbe5d6ad751897822fd92539ac5cfa37c04f3852","integrity":"sha512-BzoewUq0EZFJYKlYpObv2xRdTRJQXwMLbk2Y5l8Dhl0EQGPhdPrSdBlQaAT46gIKjAkNBNScDiUDbuBCTDNCsQ==","tarball":"https://registry.npmjs.org/is-pnp/-/is-pnp-1.0.2.tgz","fileCount":5,"unpackedSize":2282,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJb2lKpCRA9TVsSAnZWagAAkFcP+QHfioEIu01ww57y/Jnw\neOtM4JPvx0hDMKxMBKSyvVHmjZKwqxvkAnqdyybHts7i6auvuuMKDO43oaRO\n5GY5i/u29arwBKnPgeVrC6TUnbVxiSP4KBtfzRs3YCXbQ0MfSPmXiH4nzzi/\ngWeNGutgCAbefJjB0eahVCUgO7DL+sM8kEt2iNSDM1gKUDSVq5yvsZD20tX+\n/60eLG4xtz/2zXDKjoqo+DZtvW9kdLJ6TlvbDU1mt1OBSzkTiakIe1XXymVT\n+gRC0QGKGMP+YJG+VothHez/JAagl/92c/JDsb54HkW/OkUKCHFyUAw0P3h1\n46p0vEr54rHa7wT6O0fKg7q+MN1mMOKR3wO258LQW20n+DQJK1hxcJOkk+xf\n6iOa2d6Ywox9yT9B/V555BEZ2fYbnNb4gw+oo3/YyJcZ1Ooadt5kvEyDb67+\ntOaOMBXZh7/FR8EghwBBqlOndWCD4OQYVXVCwIdf/PFyUtB1+Obd4/1H+Dkw\nmg01pi7jYHG8mKLIMNdjggWi966nJ3fZqBDLW0eRtFzCbe+8VSOPKGRSHHnI\n99SD9oY/dgmimcakZ3hInuGlMGf0UrSLjHwi24y0ae1O0AlnQz0bXU0qHAH2\nH/Npr3zjOcAULwvHqtu2l1mNv/PX8GtafyybiMaXBcERearcUN/48tbvcbFX\nZmBP\r\n=z5fE\r\n-----END PGP SIGNATURE-----\r\n"},"engines":{"node":">=6"}}},"name":"is-pnp","dist-tags":{"latest":"1.0.2"},"modified":"2018-11-01T01:11:07.975Z"} \ No newline at end of file diff --git a/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/is-pnp/-/is-pnp-1.0.2.tgz.bin b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/is-pnp/-/is-pnp-1.0.2.tgz.bin new file mode 100644 index 0000000000000000000000000000000000000000..1735a12b865b9fce72f8b46fc9e9aad7511983a0 GIT binary patch literal 2033 zcmYLHX;f3^79}9+iv|JF8Wp*yU|I+n$po=NlzAqiKm~7ZZU%uQTrMbrO#>n*5OAOY z6jPCBTa;QH0_&-WAZdU`K>E2v8u_9|{|wkU3-u$CnHQNAhu4o?M2)GL*>ARanWOLXj?o zKvXK1k>n5xqr@x}fx(Q=ya-q(MkO48LMPKcp)wenrgMNa7*-I$bZHKZ!^T@Hd2)H0 z)Y>yO6%q-vr9!Td&0@oB2oyjd#0FuQEMQQCu!tgH(CGr9h{>k-`qF7kDuvAU1K@26 zDFSD4gAl1zK8;69$` zCK*mnC+SvrtUB9r*LlPw#4Iu>|9JVr=q9v`#6_aHGk^3cGT47*xmT5MYv|ZiQ&daotFLt@7?0dkz1jy}CoPzm=l)R5vAO`Q%SxXaCl)qrtX!&3FG~Wv zYmcqO9JU)P8lW1&^@M|+wHkH_>&HZQ`jc1JZ+ItnxOUr*n~nGC3DnFjwJ4i;xnLjN zY38`+&FZb9qO_88Z~F_!x16QM?C>h9CK-)?oQSA6pgdeT&+do*i@ZJuhxYxBuZIWE zh-lwzw%xKKgym#T#FRc%uR-OLXGV5b^6F1*)%sV}5PIVl2ixzfs<|y}5{A~LbUc+@ z8QDkvJ+ErBt1L4*@I3JRR-T-hcm-O%Gq%`gZbP2kgH>N)LBdUL+c!bpxrgHtuFv~o zhR8qiZCHW6YFPq3ay+mhtkhnwBCUzV4nVAheIpJ=iRLGVYmswWg}V9zV9IDLe{CEQ zk-GOb&>JXVLHj)euHu118Y9m?c>--Cm|9Xzt#CHz@^LB+yKKQB--9>tN#&t!4S@rp zJV)xLPXCIH0nV->wtKs{`Uiq`zAILpKU4_{6d3J7r}gF)WrXDL-oS;i&k~#WAoYge zz-QG#59gIG)=(Q8Luj?mnBqlvy63As`n|THAqQ+>AbUwwm^*2XVK)I;9PY+h+$F6E zaafWara2Q-*}L9$Cz^yUTk~Q7?4HlrA6?(k(<2S*3M}3kcH{b~_E7g#>!a2?Mck$o zY-ab{t-JHp(5#&GM;tx&|A+X<<;&ZH1*Dc0mt2fJhul}jra7y>j4x?HlE)&NPuFb> zgxB8ZFNl=sBt6fKC4YsNox_hLcn_5R9W$KdvC!$7b{|JPyBx2+Q%caxRTQ5p5WJ%u z_A-oyl<(wNT*7Pw@sR%`y!=KiP3GvechUp zI&0#$OjCrXX_iT?=iRu(zVR4GdjHF$^OelHiDELE$vk?!Z^L(Iy>8Tmh*zw7h*L zbItc+0!B23)iEEoPrczNQXK*pz3FT#+&|$!J$))<X zf-zrJYI)xd+5GoXy@&Kr;L$B6+=bevc}wCzJaKFrO`{xfW4=G|64Z%{wgkgkA7PO;c!DO8FhhU$3>J zCnFelE$@8uUCJu{XKCiVuv|RvJbc%ZKXc?~7r`&rZsgneskZl-QyEmiN1wH5be = [], 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(); + + let stderrOutput = ''; + await getStream(stderr).then(e => { + stderrOutput = e; + }); + + expect(stderrOutput.toString()).not.toMatch(/EPIPE/); +}); + +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(); + + proc.on('exit', function(code, signal) { + expect(code).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(); + + proc.on('exit', function(code, signal) { + expect(code).toEqual(1); + }); +}); diff --git a/package.json b/package.json index ff76755f08..8fb70a1bc1 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "execa": "^0.11.0", "fancy-log": "^1.3.2", "flow-bin": "^0.66.0", + "get-stream": "^5.0.0", "git-release-notes": "^3.0.0", "gulp": "^4.0.0", "gulp-babel": "^7.0.0", 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; diff --git a/yarn.lock b/yarn.lock index a270478e36..6891e95ff9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3281,6 +3281,13 @@ get-stream@^4.0.0: dependencies: pump "^3.0.0" +get-stream@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.0.0.tgz#7d1f137576da207fa83d843f44824a70ef999b5f" + integrity sha512-frc9F97ehylll1YI31eJRw5M21M86GCItj5U3S3hOEUa6JG6LtcvRKYDAtV/9E5lVasY8QJSPVsrMg/mbCSP5w== + dependencies: + pump "^3.0.0" + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"