New "browse" UI
Also, separated out browse, ?meta, and ?module request handlers. Fixes #82
This commit is contained in:
81
modules/actions/serveDirectoryBrowser.js
Normal file
81
modules/actions/serveDirectoryBrowser.js
Normal file
@ -0,0 +1,81 @@
|
||||
import path from 'path';
|
||||
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';
|
||||
import serveBrowsePage from './serveBrowsePage.js';
|
||||
|
||||
async function findMatchingEntries(stream, filename) {
|
||||
// filename = /some/dir/name
|
||||
return new Promise((accept, reject) => {
|
||||
const entries = {};
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
// Dynamically create "directory" entries for all subdirectories
|
||||
// in this entry's path. Some tarballs omit directory entries for
|
||||
// some reason, so this is the "brute force" method.
|
||||
let dir = path.dirname(entry.path);
|
||||
while (dir !== '/') {
|
||||
if (!entries[dir] && path.dirname(dir) === filename) {
|
||||
entries[dir] = { path: dir, type: 'directory' };
|
||||
}
|
||||
dir = path.dirname(dir);
|
||||
}
|
||||
|
||||
// Ignore non-files and files that aren't in this directory.
|
||||
if (entry.type !== 'file' || path.dirname(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.size = content.length;
|
||||
|
||||
entries[entry.path] = entry;
|
||||
|
||||
next();
|
||||
})
|
||||
.on('finish', () => {
|
||||
accept(entries);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default async function serveDirectoryBrowser(req, res) {
|
||||
const stream = await getPackage(req.packageName, req.packageVersion);
|
||||
|
||||
const filename = req.filename.slice(0, -1) || '/';
|
||||
const entries = await findMatchingEntries(stream, filename);
|
||||
|
||||
if (Object.keys(entries).length === 0) {
|
||||
return res.status(404).send(`Not found: ${req.packageSpec}${req.filename}`);
|
||||
}
|
||||
|
||||
req.browseTarget = {
|
||||
path: filename,
|
||||
type: 'directory',
|
||||
details: entries
|
||||
};
|
||||
|
||||
serveBrowsePage(req, res);
|
||||
}
|
Reference in New Issue
Block a user