From 957376bc1a5409620501910bbdc4aae60e612f7d Mon Sep 17 00:00:00 2001 From: Jiacheng Date: Mon, 17 Nov 2025 06:18:32 +0000 Subject: [PATCH 1/9] feat: support strip for all archive --- lib/utils.js | 5 +++-- lib/zip/uncompress_stream.js | 8 +------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index b31e37a..7d8dd3f 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -88,6 +88,8 @@ exports.makeUncompressFn = StreamClass => { throw error; } + const strip = opts.strip ? Number(opts.strip) : 0; + return new Promise((resolve, reject) => { fs.mkdir(destDir, { recursive: true }, err => { if (err) return reject(err); @@ -108,8 +110,7 @@ exports.makeUncompressFn = StreamClass => { .on('error', reject) .on('entry', (header, stream, next) => { stream.on('end', next); - const destFilePath = path.join(destDir, header.name); - + const destFilePath = path.join(destDir, stripFileName(strip, header.name, header.type)); if (header.type === 'file') { const dir = path.dirname(destFilePath); fs.mkdir(dir, { recursive: true }, err => { diff --git a/lib/zip/uncompress_stream.js b/lib/zip/uncompress_stream.js index eb6f195..6111e69 100644 --- a/lib/zip/uncompress_stream.js +++ b/lib/zip/uncompress_stream.js @@ -12,7 +12,6 @@ const utils = require('../utils'); let iconv; const YAUZL_CALLBACK = Symbol('ZipUncompressStream#yauzlCallback'); -const STRIP_NAME = Symbol('ZipUncompressStream#stripName'); // don't decodeStrings on yauzl, we should handle fileName by ourself // see validateFileName on https://github.com/thejoshwolfe/yauzl/blob/51010ce4e8c7e6345efe195e1b4150518f37b393/index.js#L607 @@ -34,7 +33,6 @@ class ZipUncompressStream extends UncompressBaseStream { super(opts); this._chunks = []; - this._strip = Number(opts.strip) || 0; this._zipFileNameEncoding = opts.zipFileNameEncoding || 'utf8'; if (this._zipFileNameEncoding === 'utf-8') { this._zipFileNameEncoding = 'utf8'; @@ -108,7 +106,7 @@ class ZipUncompressStream extends UncompressBaseStream { } // directory file names end with '/' (for Linux and macOS) or '\' (for Windows) const type = /[\\\/]$/.test(entry.fileName) ? 'directory' : 'file'; - const name = entry.fileName = this[STRIP_NAME](entry.fileName, type); + const name = entry.fileName; const header = { name, type, yauzl: entry, mode }; @@ -135,10 +133,6 @@ class ZipUncompressStream extends UncompressBaseStream { zipFile.readEntry(); } } - - [STRIP_NAME](fileName, type) { - return utils.stripFileName(this._strip, fileName, type); - } } module.exports = ZipUncompressStream; From 280ab0d6dd995b0899d6f981f416e6c26080e7ef Mon Sep 17 00:00:00 2001 From: Jiacheng Date: Mon, 17 Nov 2025 06:28:15 +0000 Subject: [PATCH 2/9] fix: ci --- lib/utils.js | 2 ++ lib/zip/uncompress_stream.js | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index 7d8dd3f..f5645b9 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -89,6 +89,8 @@ exports.makeUncompressFn = StreamClass => { } const strip = opts.strip ? Number(opts.strip) : 0; + // Strip is handled here in makeUncompressFn, so remove it from opts to avoid passing to UncompressStream + delete opts.strip; return new Promise((resolve, reject) => { fs.mkdir(destDir, { recursive: true }, err => { diff --git a/lib/zip/uncompress_stream.js b/lib/zip/uncompress_stream.js index 6111e69..eb6f195 100644 --- a/lib/zip/uncompress_stream.js +++ b/lib/zip/uncompress_stream.js @@ -12,6 +12,7 @@ const utils = require('../utils'); let iconv; const YAUZL_CALLBACK = Symbol('ZipUncompressStream#yauzlCallback'); +const STRIP_NAME = Symbol('ZipUncompressStream#stripName'); // don't decodeStrings on yauzl, we should handle fileName by ourself // see validateFileName on https://github.com/thejoshwolfe/yauzl/blob/51010ce4e8c7e6345efe195e1b4150518f37b393/index.js#L607 @@ -33,6 +34,7 @@ class ZipUncompressStream extends UncompressBaseStream { super(opts); this._chunks = []; + this._strip = Number(opts.strip) || 0; this._zipFileNameEncoding = opts.zipFileNameEncoding || 'utf8'; if (this._zipFileNameEncoding === 'utf-8') { this._zipFileNameEncoding = 'utf8'; @@ -106,7 +108,7 @@ class ZipUncompressStream extends UncompressBaseStream { } // directory file names end with '/' (for Linux and macOS) or '\' (for Windows) const type = /[\\\/]$/.test(entry.fileName) ? 'directory' : 'file'; - const name = entry.fileName; + const name = entry.fileName = this[STRIP_NAME](entry.fileName, type); const header = { name, type, yauzl: entry, mode }; @@ -133,6 +135,10 @@ class ZipUncompressStream extends UncompressBaseStream { zipFile.readEntry(); } } + + [STRIP_NAME](fileName, type) { + return utils.stripFileName(this._strip, fileName, type); + } } module.exports = ZipUncompressStream; From 6170c39b414583803368e56ad29da0c961d9dabc Mon Sep 17 00:00:00 2001 From: Jiacheng Date: Mon, 17 Nov 2025 06:41:47 +0000 Subject: [PATCH 3/9] chore: update test --- test/zip/index.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/zip/index.test.js b/test/zip/index.test.js index f969b39..7145cc3 100644 --- a/test/zip/index.test.js +++ b/test/zip/index.test.js @@ -288,7 +288,7 @@ describe('test/zip/index.test.js', () => { fs.mkdirSync(destDir, { recursive: true }); await compressing.zip.uncompress(destFile, destDir); const stat = fs.statSync(path.join(destDir, 'bin')); - assert(stat.mode === originStat.mode); + assert.equal(stat.mode, originStat.mode, 'file mode should be same after uncompress'); // console.log(destDir); }); }); From eb260800f7530370a3d853889b6ed18f65e113a0 Mon Sep 17 00:00:00 2001 From: Jiacheng Date: Mon, 17 Nov 2025 06:55:45 +0000 Subject: [PATCH 4/9] fix: test --- test/zip/uncompress_stream.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/zip/uncompress_stream.test.js b/test/zip/uncompress_stream.test.js index cb2272a..d3fc8bf 100644 --- a/test/zip/uncompress_stream.test.js +++ b/test/zip/uncompress_stream.test.js @@ -194,7 +194,7 @@ describe('test/zip/uncompress_stream.test.js', () => { assert(!err); const res = dircompare.compareSync(originalDir, destDir); assert(res.distinct === 0); - assert(res.equal === 5); + assert.equal(res.equal, 5, 'equal files count mismatch'); assert(res.totalFiles === 4); assert(res.totalDirs === 1); done(); From 50eb8437395c5cf7ae0f366574e9588e2c605aeb Mon Sep 17 00:00:00 2001 From: Jiacheng Date: Mon, 17 Nov 2025 06:55:45 +0000 Subject: [PATCH 5/9] fix: test --- test/zip/uncompress_stream.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/zip/uncompress_stream.test.js b/test/zip/uncompress_stream.test.js index cb2272a..d3fc8bf 100644 --- a/test/zip/uncompress_stream.test.js +++ b/test/zip/uncompress_stream.test.js @@ -194,7 +194,7 @@ describe('test/zip/uncompress_stream.test.js', () => { assert(!err); const res = dircompare.compareSync(originalDir, destDir); assert(res.distinct === 0); - assert(res.equal === 5); + assert.equal(res.equal, 5, 'equal files count mismatch'); assert(res.totalFiles === 4); assert(res.totalDirs === 1); done(); From 9e2694eac80f37bb847c1bc40d03759fa88f0fdd Mon Sep 17 00:00:00 2001 From: Jiacheng Date: Mon, 17 Nov 2025 06:57:29 +0000 Subject: [PATCH 6/9] fix: test --- test/zip/uncompress_stream.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/zip/uncompress_stream.test.js b/test/zip/uncompress_stream.test.js index d3fc8bf..6dc32bd 100644 --- a/test/zip/uncompress_stream.test.js +++ b/test/zip/uncompress_stream.test.js @@ -193,7 +193,7 @@ describe('test/zip/uncompress_stream.test.js', () => { pump(sourceStream, uncompressStream, err => { assert(!err); const res = dircompare.compareSync(originalDir, destDir); - assert(res.distinct === 0); + assert.equal(res.distinct, 0, 'distinct files count mismatch'); assert.equal(res.equal, 5, 'equal files count mismatch'); assert(res.totalFiles === 4); assert(res.totalDirs === 1); From 33e921e3fbfff4c4abec83521f49abdaa2ef0050 Mon Sep 17 00:00:00 2001 From: Jiacheng Date: Mon, 17 Nov 2025 07:05:40 +0000 Subject: [PATCH 7/9] fix: ci --- test/zip/uncompress_stream.test.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/test/zip/uncompress_stream.test.js b/test/zip/uncompress_stream.test.js index d3fc8bf..beb543d 100644 --- a/test/zip/uncompress_stream.test.js +++ b/test/zip/uncompress_stream.test.js @@ -192,12 +192,14 @@ describe('test/zip/uncompress_stream.test.js', () => { fs.mkdirSync(destDir, { recursive: true }); pump(sourceStream, uncompressStream, err => { assert(!err); - const res = dircompare.compareSync(originalDir, destDir); - assert(res.distinct === 0); - assert.equal(res.equal, 5, 'equal files count mismatch'); - assert(res.totalFiles === 4); - assert(res.totalDirs === 1); - done(); + setTimeout(() => { + const res = dircompare.compareSync(originalDir, destDir); + assert(res.distinct === 0); + assert.equal(res.equal, 5, 'equal files count mismatch'); + assert(res.totalFiles === 4); + assert(res.totalDirs === 1); + done(); + }, 0); }); uncompressStream.on('entry', (header, stream, next) => { From 7c601482a331b698294e5565985452434a8e5245 Mon Sep 17 00:00:00 2001 From: Jiacheng Date: Mon, 17 Nov 2025 07:12:57 +0000 Subject: [PATCH 8/9] fix: testcase --- test/zip/uncompress_stream.test.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/test/zip/uncompress_stream.test.js b/test/zip/uncompress_stream.test.js index 6dc32bd..0e4053e 100644 --- a/test/zip/uncompress_stream.test.js +++ b/test/zip/uncompress_stream.test.js @@ -201,15 +201,16 @@ describe('test/zip/uncompress_stream.test.js', () => { }); uncompressStream.on('entry', (header, stream, next) => { - stream.on('end', next); - if (header.type === 'file') { - stream.pipe(fs.createWriteStream(path.join(destDir, header.name))); + pipelinePromise(stream, fs.createWriteStream(path.join(destDir, header.name))) + .then(next) + .catch(done); } else { // directory fs.mkdir(path.join(destDir, header.name), { recursive: true }, err => { if (err) return done(err); stream.resume(); }); + stream.on('end', next); } }); }); @@ -231,15 +232,16 @@ describe('test/zip/uncompress_stream.test.js', () => { }); uncompressStream.on('entry', (header, stream, next) => { - stream.on('end', next); - if (header.type === 'file') { - stream.pipe(fs.createWriteStream(path.join(destDir, header.name))); + pipelinePromise(stream, fs.createWriteStream(path.join(destDir, header.name))) + .then(next) + .catch(done); } else { // directory fs.mkdir(path.join(destDir, header.name), { recursive: true }, err => { if (err) return done(err); stream.resume(); }); + stream.on('end', next); } }); }); From d1e2c108c09b4dc057348a7c437c24abf4a0b057 Mon Sep 17 00:00:00 2001 From: Jiacheng Date: Mon, 17 Nov 2025 08:18:02 +0000 Subject: [PATCH 9/9] test: add testcase --- test/tar/index.test.js | 23 +++++++++++++++++++++++ test/tgz/index.test.js | 23 +++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/test/tar/index.test.js b/test/tar/index.test.js index 990b611..1bd2249 100644 --- a/test/tar/index.test.js +++ b/test/tar/index.test.js @@ -226,5 +226,28 @@ describe('test/tar/index.test.js', () => { assert(originStat.mode); assert(destStat.mode); }); + + it('tar.uncompress(sourceFile, destDir) with strip 1', async () => { + const sourceFile = path.join(__dirname, '..', 'fixtures', 'xxx.tar'); + const destDir = path.join(os.tmpdir(), uuid.v4()); + const originalDir = path.join(__dirname, '..', 'fixtures', 'xxx'); + await compressing.tar.uncompress(sourceFile, destDir, { strip: 1 }); + const res = dircompare.compareSync(originalDir, destDir); + assert.equal(res.distinct, 0, 'distinct files count mismatch'); + assert.equal(res.equal, 5, 'equal files count mismatch'); + assert(res.totalFiles === 4); + assert(res.totalDirs === 1); + }); + + it('tar.uncompress(sourceFile, destDir) with strip 2', async () => { + const sourceFile = path.join(__dirname, '..', 'fixtures', 'xxx.tar'); + const destDir = path.join(os.tmpdir(), uuid.v4()); + await compressing.tar.uncompress(sourceFile, destDir, { strip: 2 }); + const res = dircompare.compareSync(path.join(__dirname, '..', 'fixtures', 'xxx-strip2'), destDir); + assert.equal(res.distinct, 0); + assert.equal(res.equal, 4); + assert(res.totalFiles === 4); + assert(res.totalDirs === 0); + }); }); }); diff --git a/test/tgz/index.test.js b/test/tgz/index.test.js index 74d01a2..f150c79 100644 --- a/test/tgz/index.test.js +++ b/test/tgz/index.test.js @@ -221,5 +221,28 @@ describe('test/tgz/index.test.js', () => { assert(res.totalFiles === 4); assert(res.totalDirs === 1); }); + + it('tgz.uncompress(sourceFile, destDir) with strip 1', async () => { + const sourceFile = path.join(__dirname, '..', 'fixtures', 'xxx.tgz'); + const destDir = path.join(os.tmpdir(), uuid.v4()); + const originalDir = path.join(__dirname, '..', 'fixtures', 'xxx'); + await compressing.tgz.uncompress(sourceFile, destDir, { strip: 1 }); + const res = dircompare.compareSync(originalDir, destDir); + assert.equal(res.distinct, 0, 'distinct files count mismatch'); + assert.equal(res.equal, 5, 'equal files count mismatch'); + assert(res.totalFiles === 4); + assert(res.totalDirs === 1); + }); + + it('tgz.uncompress(sourceFile, destDir) with strip 2', async () => { + const sourceFile = path.join(__dirname, '..', 'fixtures', 'xxx.tgz'); + const destDir = path.join(os.tmpdir(), uuid.v4()); + await compressing.tgz.uncompress(sourceFile, destDir, { strip: 2 }); + const res = dircompare.compareSync(path.join(__dirname, '..', 'fixtures', 'xxx-strip2'), destDir); + assert.equal(res.distinct, 0); + assert.equal(res.equal, 4); + assert(res.totalFiles === 4); + assert(res.totalDirs === 0); + }); }); });