import semver from 'semver'; import addLeadingSlash from '../utils/addLeadingSlash.js'; import createPackageURL from '../utils/createPackageURL.js'; import createSearch from '../utils/createSearch.js'; import { getPackageInfo as getNpmPackageInfo } from '../utils/npm.js'; function tagRedirect(req, res) { const version = req.packageInfo['dist-tags'][req.packageVersion]; res .set({ 'Cache-Control': 'public, s-maxage=600, max-age=60', // 10 mins on CDN, 1 min on clients 'Cache-Tag': 'redirect, tag-redirect' }) .redirect( 302, createPackageURL(req.packageName, version, req.filename, req.search) ); } function semverRedirect(req, res) { const maxVersion = semver.maxSatisfying( Object.keys(req.packageInfo.versions), req.packageVersion ); if (maxVersion) { res .set({ 'Cache-Control': 'public, s-maxage=600, max-age=60', // 10 mins on CDN, 1 min on clients 'Cache-Tag': 'redirect, semver-redirect' }) .redirect( 302, createPackageURL(req.packageName, maxVersion, req.filename, req.search) ); } else { res .status(404) .type('text') .send(`Cannot find package ${req.packageSpec}`); } } function filenameRedirect(req, res) { let filename; if (req.query.module != null) { // See https://github.com/rollup/rollup/wiki/pkg.module filename = req.packageConfig.module || req.packageConfig['jsnext:main']; if (!filename) { // https://nodejs.org/api/esm.html#esm_code_package_json_code_code_type_code_field if (req.packageConfig.type === 'module') { // Use whatever is in pkg.main or index.js filename = req.packageConfig.main || '/index.js'; } else if ( req.packageConfig.main && /\.mjs$/.test(req.packageConfig.main) ) { // Use .mjs file in pkg.main filename = req.packageConfig.main; } } if (!filename) { return res .status(404) .type('text') .send(`Package ${req.packageSpec} does not contain an ES module`); } } else if ( req.query.main && req.packageConfig[req.query.main] && typeof req.packageConfig[req.query.main] === 'string' ) { // Deprecated, see #63 filename = req.packageConfig[req.query.main]; } else if ( req.packageConfig.unpkg && typeof req.packageConfig.unpkg === 'string' ) { filename = req.packageConfig.unpkg; } else if ( req.packageConfig.browser && typeof req.packageConfig.browser === 'string' ) { // Deprecated, see #63 filename = req.packageConfig.browser; } else { filename = req.packageConfig.main || '/index.js'; } // Redirect to the exact filename so relative imports // and URLs resolve correctly. res .set({ 'Cache-Control': 'public, max-age=31536000', // 1 year 'Cache-Tag': 'redirect, filename-redirect' }) .redirect( 302, createPackageURL( req.packageName, req.packageVersion, addLeadingSlash(filename), createSearch(req.query) ) ); } /** * Fetch the package metadata and tarball from npm. Redirect to the exact * version if the request targets a tag or uses a semver version, or to the * exact filename if the request omits the filename. */ export default async function fetchPackage(req, res, next) { let packageInfo; try { packageInfo = await getNpmPackageInfo(req.packageName); } catch (error) { console.error(error); return res .status(500) .type('text') .send(`Cannot get info for package "${req.packageName}"`); } if (packageInfo == null || packageInfo.versions == null) { return res .status(404) .type('text') .send(`Cannot find package "${req.packageName}"`); } req.packageInfo = packageInfo; req.packageConfig = req.packageInfo.versions[req.packageVersion]; if (!req.packageConfig) { // Redirect to a fully-resolved version. if (req.packageVersion in req.packageInfo['dist-tags']) { return tagRedirect(req, res); } else { return semverRedirect(req, res); } } if (!req.filename) { return filenameRedirect(req, res); } next(); }