Get tests passing again

This commit is contained in:
Michael Jackson 2019-07-09 17:21:25 -07:00
parent 2e3c9ff526
commit f3ecddea47
42 changed files with 309 additions and 305 deletions

View File

@ -1,9 +1,9 @@
module.exports = { module.exports = {
moduleNameMapper: { 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: testMatch: ['**/__tests__/*-test.js'],
"<rootDir>/modules/__tests__/setupTestFramework.js", testURL: 'http://localhost/'
testMatch: ["**/__tests__/*-test.js"],
testURL: "http://localhost/"
}; };

3
modules/.babelrc Normal file
View File

@ -0,0 +1,3 @@
{
"presets": [["@babel/preset-env", { "loose": true, "targets": "node 8" }]]
}

View File

@ -0,0 +1 @@
export default [];

View File

@ -0,0 +1 @@
export default '';

View File

@ -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();
});
});
});

View File

@ -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();
});
});
});

View File

@ -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();
});
});
});

View File

@ -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();
});
});
});

View File

@ -1,14 +1,14 @@
import { renderToString, renderToStaticMarkup } from 'react-dom/server'; import { renderToString, renderToStaticMarkup } from 'react-dom/server';
import semver from 'semver'; import semver from 'semver';
import AutoIndexApp from '../client/autoIndex/App'; import AutoIndexApp from '../client/autoIndex/App.js';
import createElement from './utils/createElement'; import MainTemplate from './utils/MainTemplate.js';
import createHTML from './utils/createHTML'; import createElement from './utils/createElement.js';
import createScript from './utils/createScript'; import createHTML from './utils/createHTML.js';
import getEntryPoint from './utils/getEntryPoint'; import createScript from './utils/createScript.js';
import getGlobalScripts from './utils/getGlobalScripts'; import getEntryPoint from './utils/getEntryPoint.js';
import MainTemplate from './utils/MainTemplate'; import getGlobalScripts from './utils/getGlobalScripts.js';
const doctype = '<!DOCTYPE html>'; const doctype = '<!DOCTYPE html>';
const globalURLs = const globalURLs =

View File

@ -1,7 +1,7 @@
import serveAutoIndexPage from './serveAutoIndexPage'; import serveAutoIndexPage from './serveAutoIndexPage.js';
import serveMetadata from './serveMetadata'; import serveMetadata from './serveMetadata.js';
import serveModule from './serveModule'; import serveModule from './serveModule.js';
import serveStaticFile from './serveStaticFile'; import serveStaticFile from './serveStaticFile.js';
/** /**
* Send the file, JSON metadata, or HTML directory listing. * Send the file, JSON metadata, or HTML directory listing.

View File

@ -1,8 +1,8 @@
import etag from 'etag'; import etag from 'etag';
import cheerio from 'cheerio'; import cheerio from 'cheerio';
import getContentTypeHeader from '../utils/getContentTypeHeader'; import getContentTypeHeader from '../utils/getContentTypeHeader.js';
import rewriteBareModuleIdentifiers from '../utils/rewriteBareModuleIdentifiers'; import rewriteBareModuleIdentifiers from '../utils/rewriteBareModuleIdentifiers.js';
export default function serveHTMLModule(req, res) { export default function serveHTMLModule(req, res) {
try { try {
@ -40,9 +40,7 @@ export default function serveHTMLModule(req, res) {
.status(500) .status(500)
.type('text') .type('text')
.send( .send(
`Cannot generate module for ${req.packageSpec}${ `Cannot generate module for ${req.packageSpec}${req.filename}\n\n${debugInfo}`
req.filename
}\n\n${debugInfo}`
); );
} }
} }

View File

@ -1,7 +1,7 @@
import etag from 'etag'; import etag from 'etag';
import getContentTypeHeader from '../utils/getContentTypeHeader'; import getContentTypeHeader from '../utils/getContentTypeHeader.js';
import rewriteBareModuleIdentifiers from '../utils/rewriteBareModuleIdentifiers'; import rewriteBareModuleIdentifiers from '../utils/rewriteBareModuleIdentifiers.js';
export default function serveJavaScriptModule(req, res) { export default function serveJavaScriptModule(req, res) {
try { try {
@ -34,9 +34,7 @@ export default function serveJavaScriptModule(req, res) {
.status(500) .status(500)
.type('text') .type('text')
.send( .send(
`Cannot generate module for ${req.packageSpec}${ `Cannot generate module for ${req.packageSpec}${req.filename}\n\n${debugInfo}`
req.filename
}\n\n${debugInfo}`
); );
} }
} }

View File

@ -1,13 +1,13 @@
import { renderToString, renderToStaticMarkup } from 'react-dom/server'; 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 MainTemplate from './utils/MainTemplate.js';
import createHTML from './utils/createHTML'; import createElement from './utils/createElement.js';
import createScript from './utils/createScript'; import createHTML from './utils/createHTML.js';
import getEntryPoint from './utils/getEntryPoint'; import createScript from './utils/createScript.js';
import getGlobalScripts from './utils/getGlobalScripts'; import getEntryPoint from './utils/getEntryPoint.js';
import MainTemplate from './utils/MainTemplate'; import getGlobalScripts from './utils/getGlobalScripts.js';
const doctype = '<!DOCTYPE html>'; const doctype = '<!DOCTYPE html>';
const globalURLs = const globalURLs =

View File

@ -1,6 +1,6 @@
import path from 'path'; import path from 'path';
import addLeadingSlash from '../utils/addLeadingSlash'; import addLeadingSlash from '../utils/addLeadingSlash.js';
function getMatchingEntries(entry, entries) { function getMatchingEntries(entry, entries) {
const dirname = entry.name || '.'; const dirname = entry.name || '.';

View File

@ -1,5 +1,5 @@
import serveHTMLModule from './serveHTMLModule'; import serveHTMLModule from './serveHTMLModule.js';
import serveJavaScriptModule from './serveJavaScriptModule'; import serveJavaScriptModule from './serveJavaScriptModule.js';
export default function serveModule(req, res) { export default function serveModule(req, res) {
if (req.entry.contentType === 'application/javascript') { if (req.entry.contentType === 'application/javascript') {

View File

@ -1,7 +1,7 @@
import path from 'path'; import path from 'path';
import etag from 'etag'; import etag from 'etag';
import getContentTypeHeader from '../utils/getContentTypeHeader'; import getContentTypeHeader from '../utils/getContentTypeHeader.js';
export default function serveStaticFile(req, res) { export default function serveStaticFile(req, res) {
const tags = ['file']; const tags = ['file'];

View File

@ -1,8 +1,8 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import e from './createElement'; import e from './createElement.js';
import h from './createHTML'; import h from './createHTML.js';
import x from './createScript'; import x from './createScript.js';
const promiseShim = 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>\')'; '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>\')'; 'window.fetch || document.write(\'\\x3Cscript src="/whatwg-fetch@3.0.0/dist/fetch.umd.js">\\x3C/script>\')';
export default function MainTemplate({ export default function MainTemplate({
title, title = 'UNPKG',
description, description = 'The CDN for everything on npm',
favicon, favicon = '/favicon.ico',
data, data,
content, content = h(''),
elements elements = []
}) { }) {
return e( return e(
'html', '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') { if (process.env.NODE_ENV !== 'production') {
const htmlType = PropTypes.shape({ const htmlType = PropTypes.shape({
__html: PropTypes.string __html: PropTypes.string

View File

@ -1,16 +1,18 @@
import fetch from 'isomorphic-fetch'; import fetch from 'isomorphic-fetch';
import invariant from 'invariant';
const cloudflareURL = 'https://api.cloudflare.com/client/v4'; const cloudflareURL = 'https://api.cloudflare.com/client/v4';
const cloudflareEmail = process.env.CLOUDFLARE_EMAIL; const cloudflareEmail = process.env.CLOUDFLARE_EMAIL;
const cloudflareKey = process.env.CLOUDFLARE_KEY; const cloudflareKey = process.env.CLOUDFLARE_KEY;
invariant( if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
cloudflareEmail, if (!cloudflareEmail) {
'Missing the $CLOUDFLARE_EMAIL environment variable' 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) { function get(path, headers) {
return fetch(`${cloudflareURL}${path}`, { return fetch(`${cloudflareURL}${path}`, {

View File

@ -1,5 +1,5 @@
import createElement from './createElement'; import createElement from './createElement.js';
import createHTML from './createHTML'; import createHTML from './createHTML.js';
export default function createScript(script) { export default function createScript(script) {
return createElement('script', { return createElement('script', {

View File

@ -1,4 +1,5 @@
// Virtual module id; see rollup.config.js // Virtual module id; see rollup.config.js
// eslint-disable-next-line import/no-unresolved
import entryManifest from 'entry-manifest'; import entryManifest from 'entry-manifest';
export default function getEntryPoint(name, format) { export default function getEntryPoint(name, format) {

View File

@ -1,10 +1,13 @@
import invariant from 'invariant'; import createElement from './createElement.js';
import createElement from './createElement';
export default function getGlobalScripts(entryPoint, globalURLs) { export default function getGlobalScripts(entryPoint, globalURLs) {
return entryPoint.globalImports.map(id => { 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] }); return createElement('script', { src: globalURLs[id] });
}); });
} }

View File

@ -1,4 +1,4 @@
import * as cloudflare from './cloudflare.js'; import { getZones, getZoneAnalyticsDashboard } from './cloudflare.js';
function extractPublicInfo(data) { function extractPublicInfo(data) {
return { return {
@ -28,12 +28,8 @@ function extractPublicInfo(data) {
const DomainNames = ['unpkg.com', 'npmcdn.com']; const DomainNames = ['unpkg.com', 'npmcdn.com'];
export default async function getStats(since, until) { export default async function getStats(since, until) {
const zones = await cloudflare.getZones(DomainNames); const zones = await getZones(DomainNames);
const dashboard = await cloudflare.getZoneAnalyticsDashboard( const dashboard = await getZoneAnalyticsDashboard(zones, since, until);
zones,
since,
until
);
return { return {
timeseries: dashboard.timeseries.map(extractPublicInfo), timeseries: dashboard.timeseries.map(extractPublicInfo),

View File

@ -1,9 +1,7 @@
{ {
"presets": [ "presets": [
["@babel/preset-env", { "loose": true }], ["@babel/preset-env", { "loose": true, "targets": "> 0.25%, not dead" }],
"@babel/preset-react" "@babel/preset-react"
], ],
"plugins": [ "plugins": [["@babel/plugin-proposal-class-properties", { "loose": true }]]
["@babel/plugin-proposal-class-properties", { "loose": true }]
]
} }

View File

@ -1,4 +1,7 @@
{ {
"env": {
"browser": true
},
"plugins": [ "plugins": [
"react" "react"
], ],
@ -10,8 +13,5 @@
"react": { "react": {
"version": "16" "version": "16"
} }
},
"env": {
"browser": true
} }
} }

49
modules/createServer.js Normal file
View File

@ -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;
}

View File

@ -1,9 +1,9 @@
import semver from 'semver'; import semver from 'semver';
import addLeadingSlash from '../utils/addLeadingSlash'; import addLeadingSlash from '../utils/addLeadingSlash.js';
import createPackageURL from '../utils/createPackageURL'; import createPackageURL from '../utils/createPackageURL.js';
import createSearch from '../utils/createSearch'; import createSearch from '../utils/createSearch.js';
import { getPackageInfo as getNpmPackageInfo } from '../utils/npm'; import { getPackageInfo as getNpmPackageInfo } from '../utils/npm.js';
function tagRedirect(req, res) { function tagRedirect(req, res) {
const version = req.packageInfo['dist-tags'][req.packageVersion]; 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 * version if the request targets a tag or uses a semver version, or to the
* exact filename if the request omits the filename. * exact filename if the request omits the filename.
*/ */
export default function fetchPackage(req, res, next) { export default async function fetchPackage(req, res, next) {
getNpmPackageInfo(req.packageName).then( let packageInfo;
packageInfo => { try {
if (packageInfo == null || packageInfo.versions == null) { packageInfo = await getNpmPackageInfo(req.packageName);
return res } catch (error) {
.status(404) console.error(error);
.type('text')
.send(`Cannot find package "${req.packageName}"`);
}
req.packageInfo = packageInfo; return res
req.packageConfig = req.packageInfo.versions[req.packageVersion]; .status(500)
.type('text')
.send(`Cannot get info for package "${req.packageName}"`);
}
if (!req.packageConfig) { if (packageInfo == null || packageInfo.versions == null) {
// Redirect to a fully-resolved version. return res
if (req.packageVersion in req.packageInfo['dist-tags']) { .status(404)
return tagRedirect(req, res); .type('text')
} else { .send(`Cannot find package "${req.packageName}"`);
return semverRedirect(req, res); }
}
}
if (!req.filename) { req.packageInfo = packageInfo;
return filenameRedirect(req, res); req.packageConfig = req.packageInfo.versions[req.packageVersion];
}
next(); if (!req.packageConfig) {
}, // Redirect to a fully-resolved version.
error => { if (req.packageVersion in req.packageInfo['dist-tags']) {
console.error(error); return tagRedirect(req, res);
} else {
return res return semverRedirect(req, res);
.status(500)
.type('text')
.send(`Cannot get info for package "${req.packageName}"`);
} }
); }
if (!req.filename) {
return filenameRedirect(req, res);
}
next();
} }

View File

@ -1,11 +1,11 @@
import path from 'path'; import path from 'path';
import addLeadingSlash from '../utils/addLeadingSlash'; import addLeadingSlash from '../utils/addLeadingSlash.js';
import createPackageURL from '../utils/createPackageURL'; import createPackageURL from '../utils/createPackageURL.js';
import createSearch from '../utils/createSearch'; import createSearch from '../utils/createSearch.js';
import { fetchPackage as fetchNpmPackage } from '../utils/npm'; import { fetchPackage as fetchNpmPackage } from '../utils/npm.js';
import getIntegrity from '../utils/getIntegrity'; import getIntegrity from '../utils/getIntegrity.js';
import getContentType from '../utils/getContentType'; import getContentType from '../utils/getContentType.js';
function indexRedirect(req, res, entry) { function indexRedirect(req, res, entry) {
// Redirect to the index file so relative imports // 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. * Fetch and search the archive to try and find the requested file.
* Redirect to the "index" file if a directory was requested. * Redirect to the "index" file if a directory was requested.
*/ */
export default function findFile(req, res, next) { export default async function findFile(req, res, next) {
fetchNpmPackage(req.packageConfig).then(tarballStream => { const wantsIndex = trailingSlash.test(req.filename);
const wantsIndex = trailingSlash.test(req.filename);
// The name of the file/directory we're looking for. // The name of the file/directory we're looking for.
const entryName = req.filename const entryName = req.filename
.replace(multipleSlash, '/') .replace(multipleSlash, '/')
.replace(trailingSlash, '') .replace(trailingSlash, '')
.replace(leadingSlash, ''); .replace(leadingSlash, '');
searchEntries(tarballStream, entryName, wantsIndex).then( const tarballStream = await fetchNpmPackage(req.packageConfig);
({ entries, foundEntry }) => { const { entries, foundEntry } = await searchEntries(
if (!foundEntry) { tarballStream,
return res entryName,
.status(404) wantsIndex
.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 the foundEntry is a directory and there is no trailing slash if (!foundEntry) {
// on the request path, we need to redirect to some "index" file return res
// inside that directory. This is so our URLs work in a similar way .status(404)
// to require("lib") in node where it searches for `lib/index.js` .set({
// and `lib/index.json` when `lib` is a directory. 'Cache-Control': 'public, max-age=31536000', // 1 year
if (foundEntry.type === 'directory' && !wantsIndex) { 'Cache-Tag': 'missing, missing-entry'
const indexEntry = })
entries[path.join(entryName, 'index.js')] || .type('text')
entries[path.join(entryName, 'index.json')]; .send(`Cannot find "${req.filename}" in ${req.packageSpec}`);
}
if (indexEntry && indexEntry.type === 'file') { // If the foundEntry is a directory and there is no trailing slash
return indexRedirect(req, res, indexEntry); // on the request path, we need to redirect to some "index" file
} else { // inside that directory. This is so our URLs work in a similar way
return res // to require("lib") in node where it searches for `lib/index.js`
.status(404) // and `lib/index.json` when `lib` is a directory.
.set({ if (foundEntry.type === 'directory' && !wantsIndex) {
'Cache-Control': 'public, max-age=31536000', // 1 year const indexEntry =
'Cache-Tag': 'missing, missing-index' entries[path.join(entryName, 'index.js')] ||
}) entries[path.join(entryName, 'index.json')];
.type('text')
.send(
`Cannot find an index in "${req.filename}" in ${
req.packageSpec
}`
);
}
}
req.entries = entries; if (indexEntry && indexEntry.type === 'file') {
req.entry = foundEntry; 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();
} }

View File

@ -1,15 +1,20 @@
import createSearch from '../utils/createSearch.js';
/** /**
* Redirect old URLs that we no longer support. * Redirect old URLs that we no longer support.
*/ */
export default function redirectLegacyURLs(req, res, next) { 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\//)) { 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 // Permanently redirect /path?json => /path?meta
if (req.query.json != null) { 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(); next();

View File

@ -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 * Parse the URL and add various properties to the request object to

View File

@ -1,4 +1,4 @@
import createSearch from '../utils/createSearch'; import createSearch from '../utils/createSearch.js';
const knownQueryParams = { const knownQueryParams = {
main: true, // Deprecated, see #63 main: true, // Deprecated, see #63

View File

@ -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 = [ const testCases = [
{ {
@ -66,6 +66,7 @@ const testCases = [
} }
]; ];
const origin = 'https://unpkg.com';
const dependencies = { const dependencies = {
react: '15.6.1', react: '15.6.1',
'@angular/router': '4.3.5', '@angular/router': '4.3.5',
@ -75,9 +76,9 @@ const dependencies = {
describe('Rewriting imports/exports', () => { describe('Rewriting imports/exports', () => {
testCases.forEach(testCase => { testCases.forEach(testCase => {
it(`successfully rewrites '${testCase.before}'`, () => { it(`rewrites '${testCase.before}' => '${testCase.after}'`, () => {
const result = babel.transform(testCase.before, { const result = babel.transform(testCase.before, {
plugins: [unpkgRewrite(dependencies)] plugins: [unpkgRewrite(origin, dependencies)]
}); });
expect(result.code).toEqual(testCase.after); expect(result.code).toEqual(testCase.after);

View File

@ -1,59 +1,8 @@
import express from 'express'; import createServer from './createServer.js';
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';
const server = createServer();
const port = process.env.PORT || '8080'; const port = process.env.PORT || '8080';
const app = express(); server.listen(port, () => {
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, () => {
console.log('Server listening on port %s, Ctrl+C to quit', port); console.log('Server listening on port %s, Ctrl+C to quit', port);
}); });

View File

@ -1,4 +1,4 @@
import createSearch from '../createSearch'; import createSearch from '../createSearch.js';
describe('createSearch', () => { describe('createSearch', () => {
it('omits the trailing = for empty string values', () => { it('omits the trailing = for empty string values', () => {

View File

@ -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', () => { it('gets a content type of text/plain for LICENSE|README|CHANGES|AUTHORS|Makefile', () => {
expect(getContentType('AUTHORS')).toBe('text/plain'); expect(getContentType('AUTHORS')).toBe('text/plain');

View File

@ -1,4 +1,4 @@
import parsePackageURL from '../parsePackageURL'; import parsePackageURL from '../parsePackageURL.js';
describe('parsePackageURL', () => { describe('parsePackageURL', () => {
it('parses plain packages', () => { it('parses plain packages', () => {

View File

@ -1,10 +1,10 @@
export default function bufferStream(stream) { export default function bufferStream(stream) {
return new Promise((resolve, reject) => { return new Promise((accept, reject) => {
const chunks = []; const chunks = [];
stream stream
.on('error', reject) .on('error', reject)
.on('data', chunk => chunks.push(chunk)) .on('data', chunk => chunks.push(chunk))
.on('end', () => resolve(Buffer.concat(chunks))); .on('end', () => accept(Buffer.concat(chunks)));
}); });
} }

View File

@ -1,7 +0,0 @@
import express from 'express';
export default function createRouter(configureRouter) {
const router = express.Router();
configureRouter(router);
return router;
}

View File

@ -3,9 +3,7 @@ export default function createSearch(query) {
const params = keys.reduce( const params = keys.reduce(
(memo, key) => (memo, key) =>
memo.concat( memo.concat(
query[key] === '' query[key] ? `${key}=${encodeURIComponent(query[key])}` : key
? key // Omit the trailing "=" from key=
: `${key}=${encodeURIComponent(query[key])}`
), ),
[] []
); );

View File

@ -4,8 +4,8 @@ import gunzip from 'gunzip-maybe';
import tar from 'tar-stream'; import tar from 'tar-stream';
import LRUCache from 'lru-cache'; import LRUCache from 'lru-cache';
import debug from './debug'; import debug from './debug.js';
import bufferStream from './bufferStream'; import bufferStream from './bufferStream.js';
const npmRegistryURL = const npmRegistryURL =
process.env.NPM_REGISTRY_URL || 'https://registry.npmjs.org'; process.env.NPM_REGISTRY_URL || 'https://registry.npmjs.org';

View File

@ -1,6 +1,6 @@
import babel from '@babel/core'; import babel from '@babel/core';
import unpkgRewrite from '../plugins/unpkgRewrite'; import unpkgRewrite from '../plugins/unpkgRewrite.js';
const origin = process.env.ORIGIN || 'https://unpkg.com'; const origin = process.env.ORIGIN || 'https://unpkg.com';

View File

@ -23,7 +23,6 @@
"etag": "^1.8.1", "etag": "^1.8.1",
"express": "^4.16.4", "express": "^4.16.4",
"gunzip-maybe": "^1.4.1", "gunzip-maybe": "^1.4.1",
"invariant": "^2.2.4",
"isomorphic-fetch": "^2.2.1", "isomorphic-fetch": "^2.2.1",
"lru-cache": "^5.1.1", "lru-cache": "^5.1.1",
"mime": "^2.4.0", "mime": "^2.4.0",

View File

@ -3142,7 +3142,7 @@ inquirer@^3.0.6:
strip-ansi "^4.0.0" strip-ansi "^4.0.0"
through "^2.3.6" 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" version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==