Get tests passing again
This commit is contained in:
parent
2e3c9ff526
commit
f3ecddea47
|
@ -1,9 +1,9 @@
|
|||
module.exports = {
|
||||
moduleNameMapper: {
|
||||
"\\.css$": "<rootDir>/modules/__mocks__/styleMock.js"
|
||||
'entry-manifest': '<rootDir>/modules/__mocks__/entryManifest.js',
|
||||
'\\.png$': '<rootDir>/modules/__mocks__/imageMock.js',
|
||||
'\\.css$': '<rootDir>/modules/__mocks__/styleMock.js'
|
||||
},
|
||||
setupTestFrameworkScriptFile:
|
||||
"<rootDir>/modules/__tests__/setupTestFramework.js",
|
||||
testMatch: ["**/__tests__/*-test.js"],
|
||||
testURL: "http://localhost/"
|
||||
testMatch: ['**/__tests__/*-test.js'],
|
||||
testURL: 'http://localhost/'
|
||||
};
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"presets": [["@babel/preset-env", { "loose": true, "targets": "node 8" }]]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export default [];
|
|
@ -0,0 +1 @@
|
|||
export default '';
|
|
@ -0,0 +1,19 @@
|
|||
import request from 'supertest';
|
||||
|
||||
import createServer from '../createServer';
|
||||
|
||||
describe('Invalid package names', () => {
|
||||
let server;
|
||||
beforeEach(() => {
|
||||
server = createServer();
|
||||
});
|
||||
|
||||
it('are rejected', done => {
|
||||
request(server)
|
||||
.get('/_invalid/index.js')
|
||||
.end((err, res) => {
|
||||
expect(res.statusCode).toBe(403);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
import request from 'supertest';
|
||||
|
||||
import createServer from '../createServer';
|
||||
|
||||
describe('Invalid query params', () => {
|
||||
let server;
|
||||
beforeEach(() => {
|
||||
server = createServer();
|
||||
});
|
||||
|
||||
it('redirect to the same path w/out those params', done => {
|
||||
request(server)
|
||||
.get('/d3?module&invalid-param')
|
||||
.end((err, res) => {
|
||||
expect(res.statusCode).toBe(302);
|
||||
expect(res.headers.location).toBe('/d3?module');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,30 @@
|
|||
import request from 'supertest';
|
||||
|
||||
import createServer from '../createServer';
|
||||
|
||||
describe('Legacy URLs', () => {
|
||||
let server;
|
||||
beforeEach(() => {
|
||||
server = createServer();
|
||||
});
|
||||
|
||||
it('redirect /_meta to ?meta', done => {
|
||||
request(server)
|
||||
.get('/_meta/react')
|
||||
.end((err, res) => {
|
||||
expect(res.statusCode).toBe(301);
|
||||
expect(res.headers.location).toBe('/react?meta');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('redirect ?json to ?meta', done => {
|
||||
request(server)
|
||||
.get('/react?json')
|
||||
.end((err, res) => {
|
||||
expect(res.statusCode).toBe(301);
|
||||
expect(res.headers.location).toBe('/react?meta');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,49 +0,0 @@
|
|||
import request from 'supertest';
|
||||
|
||||
import createServer from '../createServer';
|
||||
|
||||
describe('The server', () => {
|
||||
let server;
|
||||
beforeEach(() => {
|
||||
server = createServer();
|
||||
});
|
||||
|
||||
it('redirects /_meta to ?meta', done => {
|
||||
request(server)
|
||||
.get('/_meta/react')
|
||||
.end((err, res) => {
|
||||
expect(res.statusCode).toBe(301);
|
||||
expect(res.headers.location).toBe('/react?meta');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('redirects ?json to ?meta', done => {
|
||||
request(server)
|
||||
.get('/react?json')
|
||||
.end((err, res) => {
|
||||
expect(res.statusCode).toBe(301);
|
||||
expect(res.headers.location).toBe('/react?meta');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('redirects invalid query params', done => {
|
||||
request(server)
|
||||
.get('/react?main=index&invalid')
|
||||
.end((err, res) => {
|
||||
expect(res.statusCode).toBe(302);
|
||||
expect(res.headers.location).toBe('/react?main=index');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects invalid package names', done => {
|
||||
request(server)
|
||||
.get('/_invalid/index.js')
|
||||
.end((err, res) => {
|
||||
expect(res.statusCode).toBe(403);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,14 +1,14 @@
|
|||
import { renderToString, renderToStaticMarkup } from 'react-dom/server';
|
||||
import semver from 'semver';
|
||||
|
||||
import AutoIndexApp from '../client/autoIndex/App';
|
||||
import AutoIndexApp from '../client/autoIndex/App.js';
|
||||
|
||||
import createElement from './utils/createElement';
|
||||
import createHTML from './utils/createHTML';
|
||||
import createScript from './utils/createScript';
|
||||
import getEntryPoint from './utils/getEntryPoint';
|
||||
import getGlobalScripts from './utils/getGlobalScripts';
|
||||
import MainTemplate from './utils/MainTemplate';
|
||||
import MainTemplate from './utils/MainTemplate.js';
|
||||
import createElement from './utils/createElement.js';
|
||||
import createHTML from './utils/createHTML.js';
|
||||
import createScript from './utils/createScript.js';
|
||||
import getEntryPoint from './utils/getEntryPoint.js';
|
||||
import getGlobalScripts from './utils/getGlobalScripts.js';
|
||||
|
||||
const doctype = '<!DOCTYPE html>';
|
||||
const globalURLs =
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import serveAutoIndexPage from './serveAutoIndexPage';
|
||||
import serveMetadata from './serveMetadata';
|
||||
import serveModule from './serveModule';
|
||||
import serveStaticFile from './serveStaticFile';
|
||||
import serveAutoIndexPage from './serveAutoIndexPage.js';
|
||||
import serveMetadata from './serveMetadata.js';
|
||||
import serveModule from './serveModule.js';
|
||||
import serveStaticFile from './serveStaticFile.js';
|
||||
|
||||
/**
|
||||
* Send the file, JSON metadata, or HTML directory listing.
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import etag from 'etag';
|
||||
import cheerio from 'cheerio';
|
||||
|
||||
import getContentTypeHeader from '../utils/getContentTypeHeader';
|
||||
import rewriteBareModuleIdentifiers from '../utils/rewriteBareModuleIdentifiers';
|
||||
import getContentTypeHeader from '../utils/getContentTypeHeader.js';
|
||||
import rewriteBareModuleIdentifiers from '../utils/rewriteBareModuleIdentifiers.js';
|
||||
|
||||
export default function serveHTMLModule(req, res) {
|
||||
try {
|
||||
|
@ -40,9 +40,7 @@ export default function serveHTMLModule(req, res) {
|
|||
.status(500)
|
||||
.type('text')
|
||||
.send(
|
||||
`Cannot generate module for ${req.packageSpec}${
|
||||
req.filename
|
||||
}\n\n${debugInfo}`
|
||||
`Cannot generate module for ${req.packageSpec}${req.filename}\n\n${debugInfo}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import etag from 'etag';
|
||||
|
||||
import getContentTypeHeader from '../utils/getContentTypeHeader';
|
||||
import rewriteBareModuleIdentifiers from '../utils/rewriteBareModuleIdentifiers';
|
||||
import getContentTypeHeader from '../utils/getContentTypeHeader.js';
|
||||
import rewriteBareModuleIdentifiers from '../utils/rewriteBareModuleIdentifiers.js';
|
||||
|
||||
export default function serveJavaScriptModule(req, res) {
|
||||
try {
|
||||
|
@ -34,9 +34,7 @@ export default function serveJavaScriptModule(req, res) {
|
|||
.status(500)
|
||||
.type('text')
|
||||
.send(
|
||||
`Cannot generate module for ${req.packageSpec}${
|
||||
req.filename
|
||||
}\n\n${debugInfo}`
|
||||
`Cannot generate module for ${req.packageSpec}${req.filename}\n\n${debugInfo}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { renderToString, renderToStaticMarkup } from 'react-dom/server';
|
||||
|
||||
import MainApp from '../client/main/App';
|
||||
import MainApp from '../client/main/App.js';
|
||||
|
||||
import createElement from './utils/createElement';
|
||||
import createHTML from './utils/createHTML';
|
||||
import createScript from './utils/createScript';
|
||||
import getEntryPoint from './utils/getEntryPoint';
|
||||
import getGlobalScripts from './utils/getGlobalScripts';
|
||||
import MainTemplate from './utils/MainTemplate';
|
||||
import MainTemplate from './utils/MainTemplate.js';
|
||||
import createElement from './utils/createElement.js';
|
||||
import createHTML from './utils/createHTML.js';
|
||||
import createScript from './utils/createScript.js';
|
||||
import getEntryPoint from './utils/getEntryPoint.js';
|
||||
import getGlobalScripts from './utils/getGlobalScripts.js';
|
||||
|
||||
const doctype = '<!DOCTYPE html>';
|
||||
const globalURLs =
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import path from 'path';
|
||||
|
||||
import addLeadingSlash from '../utils/addLeadingSlash';
|
||||
import addLeadingSlash from '../utils/addLeadingSlash.js';
|
||||
|
||||
function getMatchingEntries(entry, entries) {
|
||||
const dirname = entry.name || '.';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import serveHTMLModule from './serveHTMLModule';
|
||||
import serveJavaScriptModule from './serveJavaScriptModule';
|
||||
import serveHTMLModule from './serveHTMLModule.js';
|
||||
import serveJavaScriptModule from './serveJavaScriptModule.js';
|
||||
|
||||
export default function serveModule(req, res) {
|
||||
if (req.entry.contentType === 'application/javascript') {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import path from 'path';
|
||||
import etag from 'etag';
|
||||
|
||||
import getContentTypeHeader from '../utils/getContentTypeHeader';
|
||||
import getContentTypeHeader from '../utils/getContentTypeHeader.js';
|
||||
|
||||
export default function serveStaticFile(req, res) {
|
||||
const tags = ['file'];
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import e from './createElement';
|
||||
import h from './createHTML';
|
||||
import x from './createScript';
|
||||
import e from './createElement.js';
|
||||
import h from './createHTML.js';
|
||||
import x from './createScript.js';
|
||||
|
||||
const promiseShim =
|
||||
'window.Promise || document.write(\'\\x3Cscript src="/es6-promise@4.2.5/dist/es6-promise.min.js">\\x3C/script>\\x3Cscript>ES6Promise.polyfill()\\x3C/script>\')';
|
||||
|
@ -11,12 +11,12 @@ const fetchShim =
|
|||
'window.fetch || document.write(\'\\x3Cscript src="/whatwg-fetch@3.0.0/dist/fetch.umd.js">\\x3C/script>\')';
|
||||
|
||||
export default function MainTemplate({
|
||||
title,
|
||||
description,
|
||||
favicon,
|
||||
title = 'UNPKG',
|
||||
description = 'The CDN for everything on npm',
|
||||
favicon = '/favicon.ico',
|
||||
data,
|
||||
content,
|
||||
elements
|
||||
content = h(''),
|
||||
elements = []
|
||||
}) {
|
||||
return e(
|
||||
'html',
|
||||
|
@ -47,14 +47,6 @@ export default function MainTemplate({
|
|||
);
|
||||
}
|
||||
|
||||
MainTemplate.defaultProps = {
|
||||
title: 'UNPKG',
|
||||
description: 'The CDN for everything on npm',
|
||||
favicon: '/favicon.ico',
|
||||
content: h(''),
|
||||
elements: []
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const htmlType = PropTypes.shape({
|
||||
__html: PropTypes.string
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
import fetch from 'isomorphic-fetch';
|
||||
import invariant from 'invariant';
|
||||
|
||||
const cloudflareURL = 'https://api.cloudflare.com/client/v4';
|
||||
const cloudflareEmail = process.env.CLOUDFLARE_EMAIL;
|
||||
const cloudflareKey = process.env.CLOUDFLARE_KEY;
|
||||
|
||||
invariant(
|
||||
cloudflareEmail,
|
||||
'Missing the $CLOUDFLARE_EMAIL environment variable'
|
||||
);
|
||||
if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
|
||||
if (!cloudflareEmail) {
|
||||
throw new Error('Missing the $CLOUDFLARE_EMAIL environment variable');
|
||||
}
|
||||
|
||||
invariant(cloudflareKey, 'Missing the $CLOUDFLARE_KEY environment variable');
|
||||
if (!cloudflareKey) {
|
||||
throw new Error('Missing the $CLOUDFLARE_KEY environment variable');
|
||||
}
|
||||
}
|
||||
|
||||
function get(path, headers) {
|
||||
return fetch(`${cloudflareURL}${path}`, {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import createElement from './createElement';
|
||||
import createHTML from './createHTML';
|
||||
import createElement from './createElement.js';
|
||||
import createHTML from './createHTML.js';
|
||||
|
||||
export default function createScript(script) {
|
||||
return createElement('script', {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// Virtual module id; see rollup.config.js
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import entryManifest from 'entry-manifest';
|
||||
|
||||
export default function getEntryPoint(name, format) {
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import invariant from 'invariant';
|
||||
|
||||
import createElement from './createElement';
|
||||
import createElement from './createElement.js';
|
||||
|
||||
export default function getGlobalScripts(entryPoint, globalURLs) {
|
||||
return entryPoint.globalImports.map(id => {
|
||||
invariant(globalURLs[id], 'Missing global URL for id "%s"', id);
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (!globalURLs[id]) {
|
||||
throw new Error('Missing global URL for id "%s"', id);
|
||||
}
|
||||
}
|
||||
|
||||
return createElement('script', { src: globalURLs[id] });
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import * as cloudflare from './cloudflare.js';
|
||||
import { getZones, getZoneAnalyticsDashboard } from './cloudflare.js';
|
||||
|
||||
function extractPublicInfo(data) {
|
||||
return {
|
||||
|
@ -28,12 +28,8 @@ function extractPublicInfo(data) {
|
|||
const DomainNames = ['unpkg.com', 'npmcdn.com'];
|
||||
|
||||
export default async function getStats(since, until) {
|
||||
const zones = await cloudflare.getZones(DomainNames);
|
||||
const dashboard = await cloudflare.getZoneAnalyticsDashboard(
|
||||
zones,
|
||||
since,
|
||||
until
|
||||
);
|
||||
const zones = await getZones(DomainNames);
|
||||
const dashboard = await getZoneAnalyticsDashboard(zones, since, until);
|
||||
|
||||
return {
|
||||
timeseries: dashboard.timeseries.map(extractPublicInfo),
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
{
|
||||
"presets": [
|
||||
["@babel/preset-env", { "loose": true }],
|
||||
["@babel/preset-env", { "loose": true, "targets": "> 0.25%, not dead" }],
|
||||
"@babel/preset-react"
|
||||
],
|
||||
"plugins": [
|
||||
["@babel/plugin-proposal-class-properties", { "loose": true }]
|
||||
]
|
||||
"plugins": [["@babel/plugin-proposal-class-properties", { "loose": true }]]
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
"plugins": [
|
||||
"react"
|
||||
],
|
||||
|
@ -10,8 +13,5 @@
|
|||
"react": {
|
||||
"version": "16"
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"browser": true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
import express from 'express';
|
||||
|
||||
import serveFile from './actions/serveFile.js';
|
||||
import serveMainPage from './actions/serveMainPage.js';
|
||||
import serveStats from './actions/serveStats.js';
|
||||
|
||||
import cors from './middleware/cors.js';
|
||||
import fetchPackage from './middleware/fetchPackage.js';
|
||||
import findFile from './middleware/findFile.js';
|
||||
import logger from './middleware/logger.js';
|
||||
import redirectLegacyURLs from './middleware/redirectLegacyURLs.js';
|
||||
import staticFiles from './middleware/staticFiles.js';
|
||||
import validatePackageURL from './middleware/validatePackageURL.js';
|
||||
import validatePackageName from './middleware/validatePackageName.js';
|
||||
import validateQuery from './middleware/validateQuery.js';
|
||||
|
||||
export default function createServer() {
|
||||
const app = express();
|
||||
|
||||
app.disable('x-powered-by');
|
||||
app.enable('trust proxy');
|
||||
|
||||
app.use(logger);
|
||||
app.use(cors);
|
||||
app.use(staticFiles);
|
||||
|
||||
// Special startup request from App Engine
|
||||
// https://cloud.google.com/appengine/docs/standard/nodejs/how-instances-are-managed
|
||||
app.get('/_ah/start', (req, res) => {
|
||||
res.status(200).end();
|
||||
});
|
||||
|
||||
app.get('/', serveMainPage);
|
||||
app.get('/api/stats', serveStats);
|
||||
|
||||
app.use(redirectLegacyURLs);
|
||||
|
||||
app.get(
|
||||
'*',
|
||||
validatePackageURL,
|
||||
validatePackageName,
|
||||
validateQuery,
|
||||
fetchPackage,
|
||||
findFile,
|
||||
serveFile
|
||||
);
|
||||
|
||||
return app;
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
import semver from 'semver';
|
||||
|
||||
import addLeadingSlash from '../utils/addLeadingSlash';
|
||||
import createPackageURL from '../utils/createPackageURL';
|
||||
import createSearch from '../utils/createSearch';
|
||||
import { getPackageInfo as getNpmPackageInfo } from '../utils/npm';
|
||||
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];
|
||||
|
@ -114,41 +114,41 @@ function filenameRedirect(req, res) {
|
|||
* 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 function fetchPackage(req, res, next) {
|
||||
getNpmPackageInfo(req.packageName).then(
|
||||
packageInfo => {
|
||||
if (packageInfo == null || packageInfo.versions == null) {
|
||||
return res
|
||||
.status(404)
|
||||
.type('text')
|
||||
.send(`Cannot find package "${req.packageName}"`);
|
||||
}
|
||||
export default async function fetchPackage(req, res, next) {
|
||||
let packageInfo;
|
||||
try {
|
||||
packageInfo = await getNpmPackageInfo(req.packageName);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
req.packageInfo = packageInfo;
|
||||
req.packageConfig = req.packageInfo.versions[req.packageVersion];
|
||||
return res
|
||||
.status(500)
|
||||
.type('text')
|
||||
.send(`Cannot get info for package "${req.packageName}"`);
|
||||
}
|
||||
|
||||
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 (packageInfo == null || packageInfo.versions == null) {
|
||||
return res
|
||||
.status(404)
|
||||
.type('text')
|
||||
.send(`Cannot find package "${req.packageName}"`);
|
||||
}
|
||||
|
||||
if (!req.filename) {
|
||||
return filenameRedirect(req, res);
|
||||
}
|
||||
req.packageInfo = packageInfo;
|
||||
req.packageConfig = req.packageInfo.versions[req.packageVersion];
|
||||
|
||||
next();
|
||||
},
|
||||
error => {
|
||||
console.error(error);
|
||||
|
||||
return res
|
||||
.status(500)
|
||||
.type('text')
|
||||
.send(`Cannot get info for package "${req.packageName}"`);
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import path from 'path';
|
||||
|
||||
import addLeadingSlash from '../utils/addLeadingSlash';
|
||||
import createPackageURL from '../utils/createPackageURL';
|
||||
import createSearch from '../utils/createSearch';
|
||||
import { fetchPackage as fetchNpmPackage } from '../utils/npm';
|
||||
import getIntegrity from '../utils/getIntegrity';
|
||||
import getContentType from '../utils/getContentType';
|
||||
import addLeadingSlash from '../utils/addLeadingSlash.js';
|
||||
import createPackageURL from '../utils/createPackageURL.js';
|
||||
import createSearch from '../utils/createSearch.js';
|
||||
import { fetchPackage as fetchNpmPackage } from '../utils/npm.js';
|
||||
import getIntegrity from '../utils/getIntegrity.js';
|
||||
import getContentType from '../utils/getContentType.js';
|
||||
|
||||
function indexRedirect(req, res, entry) {
|
||||
// Redirect to the index file so relative imports
|
||||
|
@ -143,62 +143,59 @@ const trailingSlash = /\/$/;
|
|||
* Fetch and search the archive to try and find the requested file.
|
||||
* Redirect to the "index" file if a directory was requested.
|
||||
*/
|
||||
export default function findFile(req, res, next) {
|
||||
fetchNpmPackage(req.packageConfig).then(tarballStream => {
|
||||
const wantsIndex = trailingSlash.test(req.filename);
|
||||
export default async function findFile(req, res, next) {
|
||||
const wantsIndex = trailingSlash.test(req.filename);
|
||||
|
||||
// The name of the file/directory we're looking for.
|
||||
const entryName = req.filename
|
||||
.replace(multipleSlash, '/')
|
||||
.replace(trailingSlash, '')
|
||||
.replace(leadingSlash, '');
|
||||
// The name of the file/directory we're looking for.
|
||||
const entryName = req.filename
|
||||
.replace(multipleSlash, '/')
|
||||
.replace(trailingSlash, '')
|
||||
.replace(leadingSlash, '');
|
||||
|
||||
searchEntries(tarballStream, entryName, wantsIndex).then(
|
||||
({ entries, foundEntry }) => {
|
||||
if (!foundEntry) {
|
||||
return res
|
||||
.status(404)
|
||||
.set({
|
||||
'Cache-Control': 'public, max-age=31536000', // 1 year
|
||||
'Cache-Tag': 'missing, missing-entry'
|
||||
})
|
||||
.type('text')
|
||||
.send(`Cannot find "${req.filename}" in ${req.packageSpec}`);
|
||||
}
|
||||
const tarballStream = await fetchNpmPackage(req.packageConfig);
|
||||
const { entries, foundEntry } = await searchEntries(
|
||||
tarballStream,
|
||||
entryName,
|
||||
wantsIndex
|
||||
);
|
||||
|
||||
// If the foundEntry is a directory and there is no trailing slash
|
||||
// on the request path, we need to redirect to some "index" file
|
||||
// inside that directory. This is so our URLs work in a similar way
|
||||
// to require("lib") in node where it searches for `lib/index.js`
|
||||
// and `lib/index.json` when `lib` is a directory.
|
||||
if (foundEntry.type === 'directory' && !wantsIndex) {
|
||||
const indexEntry =
|
||||
entries[path.join(entryName, 'index.js')] ||
|
||||
entries[path.join(entryName, 'index.json')];
|
||||
if (!foundEntry) {
|
||||
return res
|
||||
.status(404)
|
||||
.set({
|
||||
'Cache-Control': 'public, max-age=31536000', // 1 year
|
||||
'Cache-Tag': 'missing, missing-entry'
|
||||
})
|
||||
.type('text')
|
||||
.send(`Cannot find "${req.filename}" in ${req.packageSpec}`);
|
||||
}
|
||||
|
||||
if (indexEntry && indexEntry.type === 'file') {
|
||||
return indexRedirect(req, res, indexEntry);
|
||||
} else {
|
||||
return res
|
||||
.status(404)
|
||||
.set({
|
||||
'Cache-Control': 'public, max-age=31536000', // 1 year
|
||||
'Cache-Tag': 'missing, missing-index'
|
||||
})
|
||||
.type('text')
|
||||
.send(
|
||||
`Cannot find an index in "${req.filename}" in ${
|
||||
req.packageSpec
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
||||
// If the foundEntry is a directory and there is no trailing slash
|
||||
// on the request path, we need to redirect to some "index" file
|
||||
// inside that directory. This is so our URLs work in a similar way
|
||||
// to require("lib") in node where it searches for `lib/index.js`
|
||||
// and `lib/index.json` when `lib` is a directory.
|
||||
if (foundEntry.type === 'directory' && !wantsIndex) {
|
||||
const indexEntry =
|
||||
entries[path.join(entryName, 'index.js')] ||
|
||||
entries[path.join(entryName, 'index.json')];
|
||||
|
||||
req.entries = entries;
|
||||
req.entry = foundEntry;
|
||||
if (indexEntry && indexEntry.type === 'file') {
|
||||
return indexRedirect(req, res, indexEntry);
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
);
|
||||
});
|
||||
return res
|
||||
.status(404)
|
||||
.set({
|
||||
'Cache-Control': 'public, max-age=31536000', // 1 year
|
||||
'Cache-Tag': 'missing, missing-index'
|
||||
})
|
||||
.type('text')
|
||||
.send(`Cannot find an index in "${req.filename}" in ${req.packageSpec}`);
|
||||
}
|
||||
|
||||
req.entries = entries;
|
||||
req.entry = foundEntry;
|
||||
|
||||
next();
|
||||
}
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
import createSearch from '../utils/createSearch.js';
|
||||
|
||||
/**
|
||||
* Redirect old URLs that we no longer support.
|
||||
*/
|
||||
export default function redirectLegacyURLs(req, res, next) {
|
||||
// Permanently redirect /_meta/path to /_metadata/path
|
||||
// Permanently redirect /_meta/path to /path?meta
|
||||
if (req.path.match(/^\/_meta\//)) {
|
||||
return res.redirect(301, '/_metadata' + req.path.substr(6));
|
||||
req.query.meta = '';
|
||||
return res.redirect(301, req.path.substr(6) + createSearch(req.query));
|
||||
}
|
||||
|
||||
// Permanently redirect /path?json => /path?meta
|
||||
if (req.query.json != null) {
|
||||
return res.redirect(301, '/_metadata' + req.path);
|
||||
delete req.query.json;
|
||||
req.query.meta = '';
|
||||
return res.redirect(301, req.path + createSearch(req.query));
|
||||
}
|
||||
|
||||
next();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import parsePackageURL from '../utils/parsePackageURL';
|
||||
import parsePackageURL from '../utils/parsePackageURL.js';
|
||||
|
||||
/**
|
||||
* Parse the URL and add various properties to the request object to
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import createSearch from '../utils/createSearch';
|
||||
import createSearch from '../utils/createSearch.js';
|
||||
|
||||
const knownQueryParams = {
|
||||
main: true, // Deprecated, see #63
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import babel from 'babel-core';
|
||||
import * as babel from '@babel/core';
|
||||
|
||||
import unpkgRewrite from '../unpkgRewrite';
|
||||
import unpkgRewrite from '../unpkgRewrite.js';
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
|
@ -66,6 +66,7 @@ const testCases = [
|
|||
}
|
||||
];
|
||||
|
||||
const origin = 'https://unpkg.com';
|
||||
const dependencies = {
|
||||
react: '15.6.1',
|
||||
'@angular/router': '4.3.5',
|
||||
|
@ -75,9 +76,9 @@ const dependencies = {
|
|||
|
||||
describe('Rewriting imports/exports', () => {
|
||||
testCases.forEach(testCase => {
|
||||
it(`successfully rewrites '${testCase.before}'`, () => {
|
||||
it(`rewrites '${testCase.before}' => '${testCase.after}'`, () => {
|
||||
const result = babel.transform(testCase.before, {
|
||||
plugins: [unpkgRewrite(dependencies)]
|
||||
plugins: [unpkgRewrite(origin, dependencies)]
|
||||
});
|
||||
|
||||
expect(result.code).toEqual(testCase.after);
|
||||
|
|
|
@ -1,59 +1,8 @@
|
|||
import express from 'express';
|
||||
|
||||
import serveFile from './actions/serveFile';
|
||||
import serveMainPage from './actions/serveMainPage';
|
||||
import serveStats from './actions/serveStats';
|
||||
|
||||
import cors from './middleware/cors';
|
||||
import fetchPackage from './middleware/fetchPackage';
|
||||
import findFile from './middleware/findFile';
|
||||
import logger from './middleware/logger';
|
||||
import redirectLegacyURLs from './middleware/redirectLegacyURLs';
|
||||
import staticFiles from './middleware/staticFiles';
|
||||
import validatePackageURL from './middleware/validatePackageURL';
|
||||
import validatePackageName from './middleware/validatePackageName';
|
||||
import validateQuery from './middleware/validateQuery';
|
||||
|
||||
import createRouter from './utils/createRouter';
|
||||
import createServer from './createServer.js';
|
||||
|
||||
const server = createServer();
|
||||
const port = process.env.PORT || '8080';
|
||||
|
||||
const app = express();
|
||||
|
||||
app.disable('x-powered-by');
|
||||
app.enable('trust proxy');
|
||||
|
||||
app.use(logger);
|
||||
app.use(cors);
|
||||
app.use(staticFiles);
|
||||
|
||||
// Special startup request from App Engine
|
||||
// https://cloud.google.com/appengine/docs/standard/nodejs/how-instances-are-managed
|
||||
app.get('/_ah/start', (req, res) => {
|
||||
res.status(200).end();
|
||||
});
|
||||
|
||||
app.get('/', serveMainPage);
|
||||
|
||||
app.use(redirectLegacyURLs);
|
||||
|
||||
app.use(
|
||||
'/api',
|
||||
createRouter(app => {
|
||||
app.get('/stats', serveStats);
|
||||
})
|
||||
);
|
||||
|
||||
app.get(
|
||||
'*',
|
||||
validatePackageURL,
|
||||
validatePackageName,
|
||||
validateQuery,
|
||||
fetchPackage,
|
||||
findFile,
|
||||
serveFile
|
||||
);
|
||||
|
||||
app.listen(port, () => {
|
||||
server.listen(port, () => {
|
||||
console.log('Server listening on port %s, Ctrl+C to quit', port);
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import createSearch from '../createSearch';
|
||||
import createSearch from '../createSearch.js';
|
||||
|
||||
describe('createSearch', () => {
|
||||
it('omits the trailing = for empty string values', () => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import getContentType from '../getContentType';
|
||||
import getContentType from '../getContentType.js';
|
||||
|
||||
it('gets a content type of text/plain for LICENSE|README|CHANGES|AUTHORS|Makefile', () => {
|
||||
expect(getContentType('AUTHORS')).toBe('text/plain');
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import parsePackageURL from '../parsePackageURL';
|
||||
import parsePackageURL from '../parsePackageURL.js';
|
||||
|
||||
describe('parsePackageURL', () => {
|
||||
it('parses plain packages', () => {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
export default function bufferStream(stream) {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise((accept, reject) => {
|
||||
const chunks = [];
|
||||
|
||||
stream
|
||||
.on('error', reject)
|
||||
.on('data', chunk => chunks.push(chunk))
|
||||
.on('end', () => resolve(Buffer.concat(chunks)));
|
||||
.on('end', () => accept(Buffer.concat(chunks)));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
import express from 'express';
|
||||
|
||||
export default function createRouter(configureRouter) {
|
||||
const router = express.Router();
|
||||
configureRouter(router);
|
||||
return router;
|
||||
}
|
|
@ -3,9 +3,7 @@ export default function createSearch(query) {
|
|||
const params = keys.reduce(
|
||||
(memo, key) =>
|
||||
memo.concat(
|
||||
query[key] === ''
|
||||
? key // Omit the trailing "=" from key=
|
||||
: `${key}=${encodeURIComponent(query[key])}`
|
||||
query[key] ? `${key}=${encodeURIComponent(query[key])}` : key
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
|
|
@ -4,8 +4,8 @@ import gunzip from 'gunzip-maybe';
|
|||
import tar from 'tar-stream';
|
||||
import LRUCache from 'lru-cache';
|
||||
|
||||
import debug from './debug';
|
||||
import bufferStream from './bufferStream';
|
||||
import debug from './debug.js';
|
||||
import bufferStream from './bufferStream.js';
|
||||
|
||||
const npmRegistryURL =
|
||||
process.env.NPM_REGISTRY_URL || 'https://registry.npmjs.org';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import babel from '@babel/core';
|
||||
|
||||
import unpkgRewrite from '../plugins/unpkgRewrite';
|
||||
import unpkgRewrite from '../plugins/unpkgRewrite.js';
|
||||
|
||||
const origin = process.env.ORIGIN || 'https://unpkg.com';
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
"etag": "^1.8.1",
|
||||
"express": "^4.16.4",
|
||||
"gunzip-maybe": "^1.4.1",
|
||||
"invariant": "^2.2.4",
|
||||
"isomorphic-fetch": "^2.2.1",
|
||||
"lru-cache": "^5.1.1",
|
||||
"mime": "^2.4.0",
|
||||
|
|
|
@ -3142,7 +3142,7 @@ inquirer@^3.0.6:
|
|||
strip-ansi "^4.0.0"
|
||||
through "^2.3.6"
|
||||
|
||||
invariant@^2.2.0, invariant@^2.2.2, invariant@^2.2.4:
|
||||
invariant@^2.2.0, invariant@^2.2.2:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
|
||||
integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
|
||||
|
|
Loading…
Reference in New Issue