diff --git a/test/package/smoke.cjs b/test/package/smoke.cjs index 33a4dee..b80e031 100644 --- a/test/package/smoke.cjs +++ b/test/package/smoke.cjs @@ -18,6 +18,7 @@ var cp = require('child_process'); var fs = require('fs'); var os = require('os'); var path = require('path'); +var zlib = require('zlib'); var tarball = process.argv[2]; @@ -40,12 +41,42 @@ var tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'bowser-smoke-')); var modules = path.join(tmp, 'node_modules'); fs.mkdirSync(modules); -// `npm install ` would work too, but plain tar keeps this dependency -// free and identical across the whole Node matrix, and sidesteps the -// npm.cmd-needs-a-shell quirks of spawning npm on Windows. tar itself is on -// PATH everywhere this runs: Linux, macOS, and Windows 10 1803+ (bsdtar). +/** + * Extracts an npm tarball using only Node built-ins, so the test needs no + * external `tar`/`npm` process and runs identically on every OS and every + * Node version in the support matrix. + * + * npm tarballs are gzipped ustar archives of regular files (npm rejects + * anything else at publish time), which is the only shape handled here: + * pax/global extended headers and directory entries are skipped, and parent + * directories are created per file instead. + */ +function extractNpmTarball(tgz, dest) { + var buf = zlib.gunzipSync(fs.readFileSync(tgz)); + var offset = 0; + while (offset + 512 <= buf.length) { + var header = buf.slice(offset, offset + 512); + offset += 512; + if (header[0] === 0) break; // zero block: end of archive + var name = header.slice(0, 100).toString('utf8').replace(/\0[\s\S]*$/, ''); + var prefix = header.slice(345, 500).toString('utf8').replace(/\0[\s\S]*$/, ''); + if (prefix) name = prefix + '/' + name; + var size = parseInt(header.slice(124, 136).toString('utf8'), 8) || 0; + var type = header[156]; + if (type === 48 /* '0' */ || type === 0) { + var target = path.join(dest, name); + if (target.indexOf(dest + path.sep) !== 0) { + throw new Error('tarball entry escapes destination: ' + name); + } + fs.mkdirSync(path.dirname(target), { recursive: true }); + fs.writeFileSync(target, buf.slice(offset, offset + size)); + } + offset += Math.ceil(size / 512) * 512; + } +} + // npm tarballs always unpack to a single top-level `package/` directory. -cp.execFileSync('tar', ['-xzf', tarball, '-C', modules], { stdio: 'inherit' }); +extractNpmTarball(tarball, modules); fs.renameSync(path.join(modules, 'package'), path.join(modules, 'bowser')); var runner = path.join(tmp, 'assertions.cjs');