diff --git a/lib/utils/tar.js b/lib/utils/tar.js index 31f8bb089ed85..63ef6067acb90 100644 --- a/lib/utils/tar.js +++ b/lib/utils/tar.js @@ -59,7 +59,7 @@ const getContents = async (manifest, tarball) => { totalEntries++ totalEntrySize += entry.size const p = entry.path - if (p.startsWith('package/node_modules/')) { + if (p.startsWith('package/node_modules/') && p !== 'package/node_modules/') { const name = p.match(/^package\/node_modules\/((?:@[^/]+\/)?[^/]+)/)[1] bundled.add(name) } @@ -72,7 +72,7 @@ const getContents = async (manifest, tarball) => { }) stream.end(tarball) - const integrity = await ssri.fromData(tarball, { + const integrity = ssri.fromData(tarball, { algorithms: ['sha1', 'sha512'], }) diff --git a/test/lib/utils/tar.js b/test/lib/utils/tar.js index 45ba720ac54ed..a0a37a3356eea 100644 --- a/test/lib/utils/tar.js +++ b/test/lib/utils/tar.js @@ -1,6 +1,8 @@ const t = require('tap') +const tar = require('tar') const pack = require('libnpmpack') const ssri = require('ssri') +const { readFile } = require('fs/promises') const tmock = require('../../fixtures/tmock') const { cleanZlib } = require('../../fixtures/clean-snapshot') @@ -106,7 +108,7 @@ t.test('should log tarball contents with unicode', async (t) => { t.end() }) -t.test('should getContents of a tarball', async (t) => { +t.test('should getContents of a tarball with only a package.json', async (t) => { const testDir = t.testdir({ 'package.json': JSON.stringify({ name: 'my-cool-pkg', @@ -142,3 +144,82 @@ t.test('should getContents of a tarball', async (t) => { }, 'contents are correct') t.end() }) + +t.test('should getContents of a tarball with a node_modules directory included', async (t) => { + const testDir = t.testdir({ + package: { + 'package.json': JSON.stringify({ + name: 'my-cool-pkg', + version: '1.0.0', + }, null, 2), + node_modules: { + 'bundle-dep': { + 'package.json': JSON.stringify({ + name: 'bundle-dep', + version: '1.0.0', + }, null, 2), + }, + }, + }, + }) + + await tar.c({ + gzip: true, + file: 'npm-example-v1.tgz', + C: testDir, + }, ['package']) + + const tarball = await readFile(`npm-example-v1.tgz`) + + const tarballContents = await getContents({ + name: 'my-cool-pkg', + version: '1.0.0', + }, tarball) + + const integrity = ssri.fromData(tarball, { + algorithms: ['sha1', 'sha512'], + }) + + // zlib is nondeterministic + t.match(tarballContents.shasum, /^[0-9a-f]{40}$/) + delete tarballContents.shasum + + // assert mode differently according to platform + if (process.platform === 'win32') { + tarballContents.files[0].mode = 511 + tarballContents.files[1].mode = 511 + tarballContents.files[2].mode = 511 + tarballContents.files[3].mode = 438 + tarballContents.files[4].mode = 438 + } else { + tarballContents.files[0].mode = 493 + tarballContents.files[1].mode = 493 + tarballContents.files[2].mode = 493 + tarballContents.files[3].mode = 420 + tarballContents.files[4].mode = 420 + } + + tarballContents.files.forEach((file) => { + delete file.mode + }) + + t.same(tarballContents, { + id: 'my-cool-pkg@1.0.0', + name: 'my-cool-pkg', + version: '1.0.0', + size: tarball.length, + unpackedSize: 97, + integrity: ssri.parse(integrity.sha512[0]), + filename: 'my-cool-pkg-1.0.0.tgz', + files: [ + { path: '', size: 0 }, + { path: 'node_modules/', size: 0 }, + { path: 'node_modules/bundle-dep/', size: 0 }, + { path: 'node_modules/bundle-dep/package.json', size: 48 }, + { path: 'package.json', size: 49 }, + ], + entryCount: 5, + bundled: ['bundle-dep'], + }, 'contents are correct') + t.end() +})