require('isomorphic-fetch') const fs = require('fs') const path = require('path') const tmpdir = require('os-tmpdir') const gunzip = require('gunzip-maybe') const mkdirp = require('mkdirp') const tar = require('tar-fs') const createMutex = require('./createMutex') function createTempPath(name, version) { const normalName = name.replace(/\//g, '-') return path.join(tmpdir(), `unpkg-${normalName}-${version}`) } function stripNamePrefix(headers) { // Most packages have header names that look like "package/index.js" // so we shorten that to just "index.js" here. A few packages use a // prefix other than "package/". e.g. the firebase package uses the // "firebase_npm/" prefix. So we just strip the first dir name. headers.name = headers.name.replace(/^[^\/]+\//, '') return headers } function ignoreSymlinks(file, headers) { return headers.type === 'link' } function extractResponse(response, outputDir) { return new Promise(function (resolve, reject) { const extract = tar.extract(outputDir, { readable: true, // All dirs/files should be readable. map: stripNamePrefix, ignore: ignoreSymlinks }) response.body .pipe(gunzip()) .pipe(extract) .on('finish', resolve) .on('error', reject) }) } function fetchAndExtract(tarballURL, outputDir) { console.log(`info: Fetching ${tarballURL} and extracting to ${outputDir}`) return fetch(tarballURL).then(function (response) { return extractResponse(response, outputDir) }) } const fetchMutex = createMutex(function (payload, callback) { const { tarballURL, outputDir } = payload fs.access(outputDir, function (error) { if (error) { if (error.code === 'ENOENT' || error.code === 'ENOTDIR') { // ENOENT or ENOTDIR are to be expected when we haven't yet // fetched a package for the first time. Carry on! mkdirp(outputDir, function (error) { if (error) { callback(error) } else { fetchAndExtract(tarballURL, outputDir).then(function () { callback() }, callback) } }) } else { callback(error) } } else { // Best case: we already have this package cached on disk! callback() } }) }) function getPackage(packageConfig, callback) { const tarballURL = packageConfig.dist.tarball const outputDir = createTempPath(packageConfig.name, packageConfig.version) fetchMutex(tarballURL, { tarballURL, outputDir }, function (error) { callback(error, outputDir) }) } module.exports = getPackage