import gunzip from 'gunzip-maybe'; import tar from 'tar-stream'; import bufferStream from '../utils/bufferStream.js'; import getContentType from '../utils/getContentType.js'; import getIntegrity from '../utils/getIntegrity.js'; import { getPackage } from '../utils/npm.js'; async function findEntry(stream, filename) { // filename = /some/file/name.js return new Promise((accept, reject) => { let foundEntry = null; stream .pipe(gunzip()) .pipe(tar.extract()) .on('error', reject) .on('entry', async (header, stream, next) => { const entry = { // 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. path: header.name.replace(/^[^/]+/, ''), type: header.type }; // Ignore non-files and files that don't match the name. if (entry.type !== 'file' || entry.path !== filename) { stream.resume(); stream.on('end', next); return; } const content = await bufferStream(stream); entry.contentType = getContentType(entry.path); entry.integrity = getIntegrity(content); entry.lastModified = header.mtime.toUTCString(); entry.size = content.length; foundEntry = entry; next(); }) .on('finish', () => { accept(foundEntry); }); }); } export default async function serveFileMetadata(req, res) { const stream = await getPackage(req.packageName, req.packageVersion); const entry = await findEntry(stream, req.filename); if (!entry) { // TODO: 404 } res.send(entry); }