Get tests passing again
This commit is contained in:
parent
2e3c9ff526
commit
f3ecddea47
|
@ -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/"
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 { 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 =
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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}`
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}`
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 =
|
||||||
|
|
|
@ -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 || '.';
|
||||||
|
|
|
@ -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') {
|
||||||
|
|
|
@ -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'];
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}`, {
|
||||||
|
|
|
@ -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', {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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] });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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 }]
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
{
|
{
|
||||||
|
"env": {
|
||||||
|
"browser": true
|
||||||
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"react"
|
"react"
|
||||||
],
|
],
|
||||||
|
@ -10,8 +13,5 @@
|
||||||
"react": {
|
"react": {
|
||||||
"version": "16"
|
"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 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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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', () => {
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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', () => {
|
||||||
|
|
|
@ -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)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
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])}`
|
|
||||||
),
|
),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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';
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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==
|
||||||
|
|
Loading…
Reference in New Issue