From 421d3c20a5be78129edc717a3395975d851613db Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Thu, 2 Mar 2017 19:40:37 +0000 Subject: [PATCH 01/11] Set default network concurrency to 8 and switched to tar-fs instead of node-tar --- package.json | 1 + src/fetchers/tarball-fetcher.js | 10 +++++++--- yarn.lock | 26 +++++++++++++++++++++++--- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 20f35bd154..8fd32fa9a2 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "semver": "^5.1.0", "strip-bom": "^3.0.0", "tar": "^2.2.1", + "tar-fs": "^1.15.1", "tar-stream": "^1.5.2", "v8-compile-cache": "^1.0.0", "validate-npm-package-license": "^3.0.1" diff --git a/src/fetchers/tarball-fetcher.js b/src/fetchers/tarball-fetcher.js index 40084323bc..04780ab070 100644 --- a/src/fetchers/tarball-fetcher.js +++ b/src/fetchers/tarball-fetcher.js @@ -11,7 +11,7 @@ import * as fsUtil from '../util/fs.js'; const invariant = require('invariant'); const path = require('path'); -const tar = require('tar'); +const tarFs = require('tar-fs'); const url = require('url'); const fs = require('fs'); @@ -77,7 +77,11 @@ export default class TarballFetcher extends BaseFetcher { } { const validateStream = new crypto.HashStream(); const extractorStream = new UnpackStream(); - const untarStream = tar.Extract({path: this.dest, strip: 1}); + const untarStream = tarFs.extract(this.dest, { + strip: 1, + dmode: parseInt(555, 8), // all dirs should be readable + fmode: parseInt(444, 8) // all files should be readable + }); extractorStream .pipe(untarStream) @@ -88,7 +92,7 @@ export default class TarballFetcher extends BaseFetcher { entry.props.gid = entry.gid = 0; } }) - .on('end', () => { + .on('finish', () => { const expectHash = this.hash; const actualHash = validateStream.getHash(); if (!expectHash || expectHash === actualHash) { diff --git a/yarn.lock b/yarn.lock index 7f611a5f91..0f0a3af20a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -980,6 +980,10 @@ chokidar@^1.4.3, chokidar@^1.6.1: optionalDependencies: fsevents "^1.0.0" +chownr@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181" + ci-info@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.0.0.tgz#dc5285f2b4e251821683681c381c3388f46ec534" @@ -1372,7 +1376,7 @@ end-of-stream@1.0.0: dependencies: once "~1.3.0" -end-of-stream@^1.0.0: +end-of-stream@^1.0.0, end-of-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.1.0.tgz#e9353258baa9108965efc41cb0ef8ade2f3cfb07" dependencies: @@ -3386,7 +3390,7 @@ object.omit@^2.0.0: for-own "^0.1.4" is-extendable "^0.1.1" -once@^1.3.0, once@^1.4.0: +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" dependencies: @@ -3651,6 +3655,13 @@ public-encrypt@^4.0.0: parse-asn1 "^5.0.0" randombytes "^2.0.1" +pump@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.2.tgz#3b3ee6512f94f0e575538c17995f9f16990a5d51" + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + punycode@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" @@ -4244,6 +4255,15 @@ tapable@^0.2.5, tapable@~0.2.5: version "0.2.6" resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.6.tgz#206be8e188860b514425375e6f1ae89bfb01fd8d" +tar-fs@^1.15.1: + version "1.15.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-1.15.1.tgz#f4622f5d5e250742b3679a9a8463acfc12cdefd1" + dependencies: + chownr "^1.0.1" + mkdirp "^0.5.0" + pump "^1.0.0" + tar-stream "^1.1.2" + tar-pack@~3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.3.0.tgz#30931816418f55afc4d21775afdd6720cee45dae" @@ -4257,7 +4277,7 @@ tar-pack@~3.3.0: tar "~2.2.1" uid-number "~0.0.6" -tar-stream@^1.5.2: +tar-stream@^1.1.2, tar-stream@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.5.2.tgz#fbc6c6e83c1a19d4cb48c7d96171fc248effc7bf" dependencies: From 4d17831bdc86dd4578c67897cfdc1067490d0cb6 Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Thu, 2 Mar 2017 19:57:37 +0000 Subject: [PATCH 02/11] migrated pack test --- __tests__/commands/pack.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/__tests__/commands/pack.js b/__tests__/commands/pack.js index 79e901f678..c4edcad3b9 100644 --- a/__tests__/commands/pack.js +++ b/__tests__/commands/pack.js @@ -14,7 +14,7 @@ const os = require('os'); const stream = require('stream'); const zlib = require('zlib'); -const tar = require('tar'); +const tarFs = require('tar-fs'); const fs2 = require('fs'); const fixturesLoc = path.join(__dirname, '..', 'fixtures', 'pack'); @@ -83,8 +83,12 @@ export async function getFilesFromArchive(source, destination): Promise { fs2.createReadStream(source) .pipe(new zlib.Gunzip()) - .pipe(tar.Extract({path: destination, strip: 1})) - .on('end', resolve) + .pipe(tarFs.extract(destination, { + strip: 1, + dmode: parseInt(555, 8), // all dirs should be readable + fmode: parseInt(444, 8) // all files should be readable + })) + .on('finish', resolve) .on('error', reject); }); await unzip; From 78ed4888ec678a6e4f28c0c175fe9afaba717867 Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Thu, 2 Mar 2017 23:30:59 +0000 Subject: [PATCH 03/11] another file migrated to tar-fs --- src/fetchers/git-fetcher.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/fetchers/git-fetcher.js b/src/fetchers/git-fetcher.js index e278b8272d..d65dc7d0bb 100644 --- a/src/fetchers/git-fetcher.js +++ b/src/fetchers/git-fetcher.js @@ -7,7 +7,7 @@ import Git from '../util/git.js'; import * as fsUtil from '../util/fs.js'; import * as crypto from '../util/crypto.js'; -const tar = require('tar'); +const tarFs = require('tar-fs'); const url = require('url'); const path = require('path'); const fs = require('fs'); @@ -34,7 +34,10 @@ export default class GitFetcher extends BaseFetcher { } return new Promise((resolve, reject) => { - const untarStream = tar.Extract({path: this.dest}); + const untarStream = tarFs.extract(this.dest, { + dmode: parseInt(555, 8), // all dirs should be readable + fmode: parseInt(444, 8) // all files should be readable + }); const hashStream = new crypto.HashStream(); @@ -42,7 +45,7 @@ export default class GitFetcher extends BaseFetcher { cachedStream .pipe(hashStream) .pipe(untarStream) - .on('end', () => { + .on('finish', () => { const expectHash = this.hash; const actualHash = hashStream.getHash(); if (!expectHash || expectHash === actualHash) { From e1fd3e0a7c399a00de7efb776713d3b0ff37b4e3 Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Fri, 3 Mar 2017 00:08:53 +0000 Subject: [PATCH 04/11] changed concurrency in another PR --- src/constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants.js b/src/constants.js index 0ba71c46c9..c75e948228 100644 --- a/src/constants.js +++ b/src/constants.js @@ -31,7 +31,7 @@ export const CACHE_VERSION = 1; export const LOCKFILE_VERSION = 1; // max amount of network requests to perform concurrently -export const NETWORK_CONCURRENCY = 8; +export const NETWORK_CONCURRENCY = 16; // max amount of child processes to execute concurrently export const CHILD_CONCURRENCY = 5; From c73e718f7ecd14f2369bee7a46534897ee0d59df Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Fri, 3 Mar 2017 00:24:56 +0000 Subject: [PATCH 05/11] wip migrating git to use tar-fs --- src/util/git.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/util/git.js b/src/util/git.js index 0167cf4703..57f7a67bf0 100644 --- a/src/util/git.js +++ b/src/util/git.js @@ -13,6 +13,7 @@ const invariant = require('invariant'); const semver = require('semver'); const url = require('url'); const tar = require('tar'); +const tarFs = require('tar-fs'); import {createWriteStream} from 'fs'; type GitRefs = { @@ -205,7 +206,11 @@ export default class Git { async _cloneViaRemoteArchive(dest: string): Promise { await child.spawn('git', ['archive', `--remote=${this.url}`, this.ref], { process(proc, update, reject, done) { - const extractor = tar.Extract({path: dest}); + // TODO should break tests + const extractor = tarFs.extract(dest, { + dmode: parseInt(555, 8), // all dirs should be readable + fmode: parseInt(444, 8) // all files should be readable + }); extractor.on('error', reject); extractor.on('end', done); @@ -219,9 +224,13 @@ export default class Git { await child.spawn('git', ['archive', this.hash], { cwd: this.cwd, process(proc, resolve, reject, done) { - const extractor = tar.Extract({path: dest}); + const extractor = tarFs.extract(dest, { + dmode: parseInt(555, 8), // all dirs should be readable + fmode: parseInt(444, 8) // all files should be readable + }); + extractor.on('error', reject); - extractor.on('end', done); + extractor.on('finish', done); proc.stdout.pipe(extractor); }, From ff73f31298cf6bba234c334e17c28403081bbdf2 Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Fri, 3 Mar 2017 02:02:16 +0000 Subject: [PATCH 06/11] removed tar dependency --- __tests__/commands/pack.js | 2 +- __tests__/package-resolver.js | 1 + package.json | 3 +-- src/fetchers/git-fetcher.js | 2 +- src/fetchers/tarball-fetcher.js | 2 +- src/util/git.js | 31 +++++++++++++++++++------------ yarn.lock | 4 ++-- 7 files changed, 26 insertions(+), 19 deletions(-) diff --git a/__tests__/commands/pack.js b/__tests__/commands/pack.js index c4edcad3b9..035dddceb8 100644 --- a/__tests__/commands/pack.js +++ b/__tests__/commands/pack.js @@ -86,7 +86,7 @@ export async function getFilesFromArchive(source, destination): Promise { const untarStream = tarFs.extract(this.dest, { dmode: parseInt(555, 8), // all dirs should be readable - fmode: parseInt(444, 8) // all files should be readable + fmode: parseInt(444, 8), // all files should be readable }); const hashStream = new crypto.HashStream(); diff --git a/src/fetchers/tarball-fetcher.js b/src/fetchers/tarball-fetcher.js index 04780ab070..6e9dda0d43 100644 --- a/src/fetchers/tarball-fetcher.js +++ b/src/fetchers/tarball-fetcher.js @@ -80,7 +80,7 @@ export default class TarballFetcher extends BaseFetcher { const untarStream = tarFs.extract(this.dest, { strip: 1, dmode: parseInt(555, 8), // all dirs should be readable - fmode: parseInt(444, 8) // all files should be readable + fmode: parseInt(444, 8), // all files should be readable }); extractorStream diff --git a/src/util/git.js b/src/util/git.js index 57f7a67bf0..401e0a71df 100644 --- a/src/util/git.js +++ b/src/util/git.js @@ -12,8 +12,8 @@ import map from './map.js'; const invariant = require('invariant'); const semver = require('semver'); const url = require('url'); -const tar = require('tar'); const tarFs = require('tar-fs'); +const tarStream = require('tar-stream'); import {createWriteStream} from 'fs'; type GitRefs = { @@ -55,8 +55,8 @@ export default class Git { */ static async hasArchiveCapability(gitUrl: string): Promise { - // USER@HOSTNAME:PATHNAME - const match = gitUrl.match(/^(.*?)@(.*?):(.*?)$/); + // USER@HOSTNAME + const match = gitUrl.match(/^(.*?)@(.*?)$/); if (!match) { return false; } @@ -206,13 +206,12 @@ export default class Git { async _cloneViaRemoteArchive(dest: string): Promise { await child.spawn('git', ['archive', `--remote=${this.url}`, this.ref], { process(proc, update, reject, done) { - // TODO should break tests const extractor = tarFs.extract(dest, { dmode: parseInt(555, 8), // all dirs should be readable - fmode: parseInt(444, 8) // all files should be readable + fmode: parseInt(444, 8), // all files should be readable }); extractor.on('error', reject); - extractor.on('end', done); + extractor.on('finish', done); proc.stdout.pipe(extractor); proc.on('error', reject); @@ -226,7 +225,7 @@ export default class Git { process(proc, resolve, reject, done) { const extractor = tarFs.extract(dest, { dmode: parseInt(555, 8), // all dirs should be readable - fmode: parseInt(444, 8) // all files should be readable + fmode: parseInt(444, 8), // all files should be readable }); extractor.on('error', reject); @@ -288,13 +287,21 @@ export default class Git { try { return await child.spawn('git', ['archive', `--remote=${this.url}`, this.ref, filename], { process(proc, update, reject, done) { - const parser = tar.Parse(); + const parser = tarStream.extract(); parser.on('error', reject); - parser.on('end', done); - - parser.on('data', (entry: Buffer) => { - update(entry.toString()); + parser.on('finish', done); + + parser.on('entry', (header, stream, next) => { + let string = ''; + stream.on('data', (buffer) => { + string += buffer.toString(); + }); + stream.on('end', () => { + update(string); + next(); + }); + stream.resume(); }); proc.stdout.pipe(parser); diff --git a/yarn.lock b/yarn.lock index 0f0a3af20a..1710be0b57 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4277,7 +4277,7 @@ tar-pack@~3.3.0: tar "~2.2.1" uid-number "~0.0.6" -tar-stream@^1.1.2, tar-stream@^1.5.2: +tar-stream@^1.1.2: version "1.5.2" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.5.2.tgz#fbc6c6e83c1a19d4cb48c7d96171fc248effc7bf" dependencies: @@ -4286,7 +4286,7 @@ tar-stream@^1.1.2, tar-stream@^1.5.2: readable-stream "^2.0.0" xtend "^4.0.0" -tar@^2.0.0, tar@^2.2.1, tar@~2.2.1: +tar@^2.0.0, tar@~2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" dependencies: From a80f9a78ff54c02be8a0783721f5c926def4b409 Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Fri, 3 Mar 2017 02:08:29 +0000 Subject: [PATCH 07/11] bump tar-stream --- package.json | 2 +- yarn.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 0d90586191..1428ad8dc9 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "semver": "^5.1.0", "strip-bom": "^3.0.0", "tar-fs": "^1.15.1", - "tar-stream": "^1.1.2", + "tar-stream": "^1.5.2", "v8-compile-cache": "^1.0.0", "validate-npm-package-license": "^3.0.1" }, diff --git a/yarn.lock b/yarn.lock index 1710be0b57..c8ffffc86d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4277,7 +4277,7 @@ tar-pack@~3.3.0: tar "~2.2.1" uid-number "~0.0.6" -tar-stream@^1.1.2: +tar-stream@^1.1.2, tar-stream@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.5.2.tgz#fbc6c6e83c1a19d4cb48c7d96171fc248effc7bf" dependencies: From c89e85522f0abef3564f57f79f2643b082c9546f Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Fri, 3 Mar 2017 10:51:14 +0000 Subject: [PATCH 08/11] reverted changes to url in hasArchiveCapability --- __tests__/package-resolver.js | 1 - src/util/git.js | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/__tests__/package-resolver.js b/__tests__/package-resolver.js index e998db7f49..1a993f2a7b 100644 --- a/__tests__/package-resolver.js +++ b/__tests__/package-resolver.js @@ -64,7 +64,6 @@ addTest('https://git@github.com/stevemao/left-pad.git'); // git url, with userna addTest('https://bitbucket.org/hgarcia/node-bitbucket-api.git'); // hosted git url addTest('https://github.com/yarnpkg/yarn/releases/download/v0.18.1/yarn-v0.18.1.tar.gz'); // tarball addTest('https://github.com/yarnpkg/e2e-test-repo.git#greenkeeper/cross-env-3.1.4'); // hash with slashes -addTest('ssh://git@bitbucket.org/bestander/test-repo.git'); // bitbucket with archive protocol supported addTest('gitlab:leanlabsio/kanban'); // gitlab addTest('gist:d59975ac23e26ad4e25b'); // gist url addTest('bitbucket:hgarcia/node-bitbucket-api'); // bitbucket url diff --git a/src/util/git.js b/src/util/git.js index 401e0a71df..68b192bc94 100644 --- a/src/util/git.js +++ b/src/util/git.js @@ -55,8 +55,8 @@ export default class Git { */ static async hasArchiveCapability(gitUrl: string): Promise { - // USER@HOSTNAME - const match = gitUrl.match(/^(.*?)@(.*?)$/); + // USER@HOSTNAME:PATHNAME + const match = gitUrl.match(/^(.*?)@(.*?):(.*?)$/); if (!match) { return false; } From f8d7f02c007503e9d8c6a48c9a58207874c9bf1a Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Tue, 7 Mar 2017 00:14:32 +0000 Subject: [PATCH 09/11] made changes according to feedback --- src/fetchers/git-fetcher.js | 4 ++-- src/fetchers/tarball-fetcher.js | 4 ++-- src/util/git.js | 21 +++++++++++++-------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/fetchers/git-fetcher.js b/src/fetchers/git-fetcher.js index 4af773cc8a..7e09d14c7c 100644 --- a/src/fetchers/git-fetcher.js +++ b/src/fetchers/git-fetcher.js @@ -35,8 +35,8 @@ export default class GitFetcher extends BaseFetcher { return new Promise((resolve, reject) => { const untarStream = tarFs.extract(this.dest, { - dmode: parseInt(555, 8), // all dirs should be readable - fmode: parseInt(444, 8), // all files should be readable + dmode: 0o555, // all dirs should be readable + fmode: 0o444, // all files should be readable }); const hashStream = new crypto.HashStream(); diff --git a/src/fetchers/tarball-fetcher.js b/src/fetchers/tarball-fetcher.js index 6e9dda0d43..1204d4bb78 100644 --- a/src/fetchers/tarball-fetcher.js +++ b/src/fetchers/tarball-fetcher.js @@ -79,8 +79,8 @@ export default class TarballFetcher extends BaseFetcher { const extractorStream = new UnpackStream(); const untarStream = tarFs.extract(this.dest, { strip: 1, - dmode: parseInt(555, 8), // all dirs should be readable - fmode: parseInt(444, 8), // all files should be readable + dmode: 0o555, // all dirs should be readable + fmode: 0o444, // all files should be readable }); extractorStream diff --git a/src/util/git.js b/src/util/git.js index 68b192bc94..59110b45aa 100644 --- a/src/util/git.js +++ b/src/util/git.js @@ -11,11 +11,13 @@ import map from './map.js'; const invariant = require('invariant'); const semver = require('semver'); -const url = require('url'); +const StringDecoder = require('string_decoder').StringDecoder; const tarFs = require('tar-fs'); const tarStream = require('tar-stream'); +const url = require('url'); import {createWriteStream} from 'fs'; + type GitRefs = { [name: string]: string }; @@ -207,8 +209,8 @@ export default class Git { await child.spawn('git', ['archive', `--remote=${this.url}`, this.ref], { process(proc, update, reject, done) { const extractor = tarFs.extract(dest, { - dmode: parseInt(555, 8), // all dirs should be readable - fmode: parseInt(444, 8), // all files should be readable + dmode: 0o555, // all dirs should be readable + fmode: 0o444, // all files should be readable }); extractor.on('error', reject); extractor.on('finish', done); @@ -224,8 +226,8 @@ export default class Git { cwd: this.cwd, process(proc, resolve, reject, done) { const extractor = tarFs.extract(dest, { - dmode: parseInt(555, 8), // all dirs should be readable - fmode: parseInt(444, 8), // all files should be readable + dmode: 0o555, // all dirs should be readable + fmode: 0o444, // all files should be readable }); extractor.on('error', reject); @@ -293,12 +295,15 @@ export default class Git { parser.on('finish', done); parser.on('entry', (header, stream, next) => { - let string = ''; + const decoder = new StringDecoder('utf8'); + let fileContent = ''; + stream.on('data', (buffer) => { - string += buffer.toString(); + fileContent += decoder.write(buffer); }); stream.on('end', () => { - update(string); + fileContent += decoder.end(); + update(fileContent); next(); }); stream.resume(); From 91b5857791b22b4e394e20901b57390b056bb6c0 Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Tue, 7 Mar 2017 01:09:36 +0000 Subject: [PATCH 10/11] 0o444 --- __tests__/commands/pack.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/__tests__/commands/pack.js b/__tests__/commands/pack.js index 035dddceb8..58608f7b74 100644 --- a/__tests__/commands/pack.js +++ b/__tests__/commands/pack.js @@ -85,8 +85,8 @@ export async function getFilesFromArchive(source, destination): Promise Date: Tue, 7 Mar 2017 13:36:24 +0000 Subject: [PATCH 11/11] added flow suppress --- src/util/git.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/util/git.js b/src/util/git.js index 59110b45aa..3d3f4ce542 100644 --- a/src/util/git.js +++ b/src/util/git.js @@ -17,7 +17,6 @@ const tarStream = require('tar-stream'); const url = require('url'); import {createWriteStream} from 'fs'; - type GitRefs = { [name: string]: string }; @@ -302,8 +301,9 @@ export default class Git { fileContent += decoder.write(buffer); }); stream.on('end', () => { - fileContent += decoder.end(); - update(fileContent); + // $FlowFixMe: suppressing this error due to bug https://github.com/facebook/flow/pull/3483 + const remaining: string = decoder.end(); + update(fileContent + remaining); next(); }); stream.resume();