diff --git a/client/About.js b/client/About.js index bb4981d..088141e 100644 --- a/client/About.js +++ b/client/About.js @@ -1,6 +1,8 @@ -import React from "react" -import contentHTML from "./About.md" +import React from "react"; +import contentHTML from "./About.md"; -const About = () =>
+const About = () => ( +
+); -export default About +export default About; diff --git a/client/App.js b/client/App.js index 54daba4..19fd6b5 100644 --- a/client/App.js +++ b/client/App.js @@ -1,11 +1,11 @@ -import React from "react" -import { HashRouter } from "react-router-dom" -import Layout from "./Layout" +import React from "react"; +import { HashRouter } from "react-router-dom"; +import Layout from "./Layout"; const App = () => ( -) +); -export default App +export default App; diff --git a/client/Home.js b/client/Home.js index a02b49e..7d4de6a 100644 --- a/client/Home.js +++ b/client/Home.js @@ -1,6 +1,8 @@ -import React from "react" -import contentHTML from "./Home.md" +import React from "react"; +import contentHTML from "./Home.md"; -const Home = () =>
+const Home = () => ( +
+); -export default Home +export default Home; diff --git a/client/Layout.js b/client/Layout.js index 40f42f0..12d8f08 100644 --- a/client/Layout.js +++ b/client/Layout.js @@ -1,78 +1,81 @@ -import React from "react" -import PropTypes from "prop-types" -import { Motion, spring } from "react-motion" -import { Switch, Route, Link, withRouter } from "react-router-dom" -import WindowSize from "./WindowSize" -import About from "./About" -import Stats from "./Stats" -import Home from "./Home" +import React from "react"; +import PropTypes from "prop-types"; +import { Motion, spring } from "react-motion"; +import { Switch, Route, Link, withRouter } from "react-router-dom"; +import WindowSize from "./WindowSize"; +import About from "./About"; +import Stats from "./Stats"; +import Home from "./Home"; class Layout extends React.Component { static propTypes = { location: PropTypes.object, children: PropTypes.node - } + }; state = { underlineLeft: 0, underlineWidth: 0, useSpring: false, stats: null - } + }; adjustUnderline = (useSpring = false) => { - let itemIndex + let itemIndex; switch (this.props.location.pathname) { case "/stats": - itemIndex = 1 - break + itemIndex = 1; + break; case "/about": - itemIndex = 2 - break + itemIndex = 2; + break; case "/": default: - itemIndex = 0 + itemIndex = 0; } - const itemNodes = this.listNode.querySelectorAll("li") - const currentNode = itemNodes[itemIndex] + const itemNodes = this.listNode.querySelectorAll("li"); + const currentNode = itemNodes[itemIndex]; this.setState({ underlineLeft: currentNode.offsetLeft, underlineWidth: currentNode.offsetWidth, useSpring - }) - } + }); + }; componentDidMount() { - this.adjustUnderline() + this.adjustUnderline(); fetch("/_stats?period=last-month") .then(res => res.json()) - .then(stats => this.setState({ stats })) + .then(stats => this.setState({ stats })); if (window.localStorage) { - const savedStats = window.localStorage.savedStats + const savedStats = window.localStorage.savedStats; - if (savedStats) this.setState({ stats: JSON.parse(savedStats) }) + if (savedStats) this.setState({ stats: JSON.parse(savedStats) }); window.onbeforeunload = () => { - localStorage.savedStats = JSON.stringify(this.state.stats) - } + localStorage.savedStats = JSON.stringify(this.state.stats); + }; } } componentDidUpdate(prevProps) { - if (prevProps.location.pathname !== this.props.location.pathname) this.adjustUnderline(true) + if (prevProps.location.pathname !== this.props.location.pathname) + this.adjustUnderline(true); } render() { - const { underlineLeft, underlineWidth, useSpring } = this.state + const { underlineLeft, underlineWidth, useSpring } = this.state; const style = { - left: useSpring ? spring(underlineLeft, { stiffness: 220 }) : underlineLeft, + left: useSpring + ? spring(underlineLeft, { stiffness: 220 }) + : underlineLeft, width: useSpring ? spring(underlineWidth) : underlineWidth - } + }; return (
@@ -81,7 +84,10 @@ class Layout extends React.Component {

unpkg

- } /> + } + />
- ) + ); } } -export default withRouter(Layout) +export default withRouter(Layout); diff --git a/client/Stats.js b/client/Stats.js index b464138..c1efb03 100644 --- a/client/Stats.js +++ b/client/Stats.js @@ -1,51 +1,55 @@ -import React from "react" -import PropTypes from "prop-types" -import formatBytes from "pretty-bytes" -import formatDate from "date-fns/format" -import parseDate from "date-fns/parse" -import formatNumber from "./utils/formatNumber" -import formatPercent from "./utils/formatPercent" +import React from "react"; +import PropTypes from "prop-types"; +import formatBytes from "pretty-bytes"; +import formatDate from "date-fns/format"; +import parseDate from "date-fns/parse"; +import formatNumber from "./utils/formatNumber"; +import formatPercent from "./utils/formatPercent"; -import { continents, countries } from "countries-list" +import { continents, countries } from "countries-list"; const getCountriesByContinent = continent => - Object.keys(countries).filter(country => countries[country].continent === continent) + Object.keys(countries).filter( + country => countries[country].continent === continent + ); -const sumKeyValues = (hash, keys) => keys.reduce((n, key) => n + (hash[key] || 0), 0) +const sumKeyValues = (hash, keys) => + keys.reduce((n, key) => n + (hash[key] || 0), 0); -const sumValues = hash => Object.keys(hash).reduce((memo, key) => memo + hash[key], 0) +const sumValues = hash => + Object.keys(hash).reduce((memo, key) => memo + hash[key], 0); class Stats extends React.Component { static propTypes = { data: PropTypes.object - } + }; state = { minPackageRequests: 1000000, minCountryRequests: 1000000 - } + }; render() { - const { data } = this.props + const { data } = this.props; - if (data == null) return null + if (data == null) return null; - const totals = data.totals + const totals = data.totals; // Summary data - const since = parseDate(totals.since) - const until = parseDate(totals.until) + const since = parseDate(totals.since); + const until = parseDate(totals.until); // Packages - const packageRows = [] + const packageRows = []; Object.keys(totals.requests.package) .sort((a, b) => { - return totals.requests.package[b] - totals.requests.package[a] + return totals.requests.package[b] - totals.requests.package[a]; }) .forEach(packageName => { - const requests = totals.requests.package[packageName] - const bandwidth = totals.bandwidth.package[packageName] + const requests = totals.requests.package[packageName]; + const bandwidth = totals.bandwidth.package[packageName]; if (requests >= this.state.minPackageRequests) { packageRows.push( @@ -59,44 +63,51 @@ class Stats extends React.Component { - {formatNumber(requests)} ({formatPercent(requests / totals.requests.all)}%) + {formatNumber(requests)} ({formatPercent( + requests / totals.requests.all + )}%) {bandwidth ? ( - {formatBytes(bandwidth)} ({formatPercent(bandwidth / totals.bandwidth.all)}%) + {formatBytes(bandwidth)} ({formatPercent( + bandwidth / totals.bandwidth.all + )}%) ) : ( - )} - ) + ); } - }) + }); // Regions - const regionRows = [] + const regionRows = []; const continentsData = Object.keys(continents).reduce((memo, continent) => { - const localCountries = getCountriesByContinent(continent) + const localCountries = getCountriesByContinent(continent); memo[continent] = { countries: localCountries, requests: sumKeyValues(totals.requests.country, localCountries), bandwidth: sumKeyValues(totals.bandwidth.country, localCountries) - } + }; - return memo - }, {}) + return memo; + }, {}); const topContinents = Object.keys(continentsData).sort((a, b) => { - return continentsData[b].requests - continentsData[a].requests - }) + return continentsData[b].requests - continentsData[a].requests; + }); topContinents.forEach(continent => { - const continentName = continents[continent] - const continentData = continentsData[continent] + const continentName = continents[continent]; + const continentData = continentsData[continent]; - if (continentData.requests > this.state.minCountryRequests && continentData.bandwidth !== 0) { + if ( + continentData.requests > this.state.minCountryRequests && + continentData.bandwidth !== 0 + ) { regionRows.push( {continentName} @@ -111,15 +122,15 @@ class Stats extends React.Component { )}%) - ) + ); const topCountries = continentData.countries.sort((a, b) => { - return totals.requests.country[b] - totals.requests.country[a] - }) + return totals.requests.country[b] - totals.requests.country[a]; + }); topCountries.forEach(country => { - const countryRequests = totals.requests.country[country] - const countryBandwidth = totals.bandwidth.country[country] + const countryRequests = totals.requests.country[country]; + const countryBandwidth = totals.bandwidth.country[country]; if (countryRequests > this.state.minCountryRequests) { regionRows.push( @@ -136,19 +147,19 @@ class Stats extends React.Component { )}%) - ) + ); } - }) + }); } - }) + }); // Protocols const protocolRows = Object.keys(totals.requests.protocol) .sort((a, b) => { - return totals.requests.protocol[b] - totals.requests.protocol[a] + return totals.requests.protocol[b] - totals.requests.protocol[a]; }) .map(protocol => { - const requests = totals.requests.protocol[protocol] + const requests = totals.requests.protocol[protocol]; return ( @@ -159,19 +170,22 @@ class Stats extends React.Component { )}%) - ) - }) + ); + }); return (

From {formatDate(since, "MMM D")} to{" "} {formatDate(until, "MMM D")} unpkg served{" "} - {formatNumber(totals.requests.all)} requests and a total of{" "} - {formatBytes(totals.bandwidth.all)} of data to{" "} - {formatNumber(totals.uniques.all)} unique visitors,{" "} - {formatPercent(totals.requests.cached / totals.requests.all, 0)}% of - which were served from the cache. + {formatNumber(totals.requests.all)} requests and a + total of {formatBytes(totals.bandwidth.all)} of data + to {formatNumber(totals.uniques.all)} unique + visitors,{" "} + + {formatPercent(totals.requests.cached / totals.requests.all, 0)}% + {" "} + of which were served from the cache.

Packages

@@ -241,7 +255,12 @@ class Stats extends React.Component { requests.

- +
@@ -270,8 +289,8 @@ class Stats extends React.Component { {protocolRows}
Region
- ) + ); } } -export default Stats +export default Stats; diff --git a/client/WindowSize.js b/client/WindowSize.js index 3d5f978..f63e089 100644 --- a/client/WindowSize.js +++ b/client/WindowSize.js @@ -1,34 +1,34 @@ -import React from "react" -import PropTypes from "prop-types" -import addEvent from "./utils/addEvent" -import removeEvent from "./utils/removeEvent" +import React from "react"; +import PropTypes from "prop-types"; +import addEvent from "./utils/addEvent"; +import removeEvent from "./utils/removeEvent"; -const ResizeEvent = "resize" +const ResizeEvent = "resize"; class WindowSize extends React.Component { static propTypes = { onChange: PropTypes.func - } + }; handleWindowResize = () => { if (this.props.onChange) this.props.onChange({ width: window.innerWidth, height: window.innerHeight - }) - } + }); + }; componentDidMount() { - addEvent(window, ResizeEvent, this.handleWindowResize) + addEvent(window, ResizeEvent, this.handleWindowResize); } componentWillUnmount() { - removeEvent(window, ResizeEvent, this.handleWindowResize) + removeEvent(window, ResizeEvent, this.handleWindowResize); } render() { - return null + return null; } } -export default WindowSize +export default WindowSize; diff --git a/client/main.css b/client/main.css index c8c6b9d..f67225f 100644 --- a/client/main.css +++ b/client/main.css @@ -1,12 +1,7 @@ body { font-size: 16px; - font-family: -apple-system, - BlinkMacSystemFont, - "Segoe UI", - Roboto, - Helvetica, - Arial, - sans-serif; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, + Arial, sans-serif; line-height: 1.5; padding: 5px 20px; } @@ -50,7 +45,8 @@ th { text-align: left; background-color: #eee; } -th, td { +th, +td { padding: 5px; } th { diff --git a/client/main.js b/client/main.js index c67f2fd..44a1e4b 100644 --- a/client/main.js +++ b/client/main.js @@ -1,6 +1,6 @@ -import React from "react" -import ReactDOM from "react-dom" -import App from "./App" -import "./main.css" +import React from "react"; +import ReactDOM from "react-dom"; +import App from "./App"; +import "./main.css"; -ReactDOM.render(, document.getElementById("app")) +ReactDOM.render(, document.getElementById("app")); diff --git a/client/utils/addEvent.js b/client/utils/addEvent.js index 34277f2..8d14db8 100644 --- a/client/utils/addEvent.js +++ b/client/utils/addEvent.js @@ -1,9 +1,9 @@ const addEvent = (node, type, handler) => { if (node.addEventListener) { - node.addEventListener(type, handler, false) + node.addEventListener(type, handler, false); } else if (node.attachEvent) { - node.attachEvent("on" + type, handler) + node.attachEvent("on" + type, handler); } -} +}; -export default addEvent +export default addEvent; diff --git a/client/utils/formatNumber.js b/client/utils/formatNumber.js index 597c55e..94b33f9 100644 --- a/client/utils/formatNumber.js +++ b/client/utils/formatNumber.js @@ -1,10 +1,10 @@ const formatNumber = n => { - const digits = String(n).split("") - const groups = [] + const digits = String(n).split(""); + const groups = []; - while (digits.length) groups.unshift(digits.splice(-3).join("")) + while (digits.length) groups.unshift(digits.splice(-3).join("")); - return groups.join(",") -} + return groups.join(","); +}; -export default formatNumber +export default formatNumber; diff --git a/client/utils/formatPercent.js b/client/utils/formatPercent.js index 561ad5b..9f68ac9 100644 --- a/client/utils/formatPercent.js +++ b/client/utils/formatPercent.js @@ -1,3 +1,4 @@ -const formatPercent = (n, fixed = 1) => String((n.toPrecision(2) * 100).toFixed(fixed)) +const formatPercent = (n, fixed = 1) => + String((n.toPrecision(2) * 100).toFixed(fixed)); -export default formatPercent +export default formatPercent; diff --git a/client/utils/parseNumber.js b/client/utils/parseNumber.js index 7c14a9f..9ab1a6d 100644 --- a/client/utils/parseNumber.js +++ b/client/utils/parseNumber.js @@ -1,3 +1,3 @@ -const parseNumber = s => parseInt(s.replace(/,/g, ""), 10) || 0 +const parseNumber = s => parseInt(s.replace(/,/g, ""), 10) || 0; -export default parseNumber +export default parseNumber; diff --git a/client/utils/removeEvent.js b/client/utils/removeEvent.js index 0071417..3b53108 100644 --- a/client/utils/removeEvent.js +++ b/client/utils/removeEvent.js @@ -1,9 +1,9 @@ const removeEvent = (node, type, handler) => { if (node.removeEventListener) { - node.removeEventListener(type, handler, false) + node.removeEventListener(type, handler, false); } else if (node.detachEvent) { - node.detachEvent("on" + type, handler) + node.detachEvent("on" + type, handler); } -} +}; -export default removeEvent +export default removeEvent; diff --git a/docs/api.md b/docs/api.md index 12bf81e..bba4c74 100644 --- a/docs/api.md +++ b/docs/api.md @@ -4,8 +4,8 @@ Some API methods require an authentication token. This token is a [JSON web toke Once you obtain an API token (see below) you can pass it to the server in one of two ways: - - For GET/HEAD requests, use the `?token` query parameter - - For all other requests, use the `{token}` parameter as part of the JSON in the request body +* For GET/HEAD requests, use the `?token` query parameter +* For all other requests, use the `{token}` parameter as part of the JSON in the request body ### POST /\_auth @@ -40,7 +40,7 @@ Required scope: none Query parameters: - - `token` - The auth token to verify and decode +* `token` - The auth token to verify and decode Example: @@ -102,8 +102,8 @@ Required scope: `blacklist.add` Body parameters: - - `token` - The auth token - - `packageName` - The package to add to the blacklist +* `token` - The auth token +* `packageName` - The package to add to the blacklist Example: @@ -122,7 +122,7 @@ Required scope: `blacklist.remove` Body parameters: - - `token` - The auth token +* `token` - The auth token Example: diff --git a/scripts/create-token.js b/scripts/create-token.js index 0fcfbcc..70e4bfa 100644 --- a/scripts/create-token.js +++ b/scripts/create-token.js @@ -1,23 +1,23 @@ -const AuthAPI = require("../server/AuthAPI") +const AuthAPI = require("../server/AuthAPI"); const scopes = { blacklist: { read: true } -} +}; AuthAPI.createToken(scopes).then( token => { // Verify it, just to be sure. AuthAPI.verifyToken(token).then(payload => { - console.log(token, "\n") - console.log(JSON.stringify(payload, null, 2), "\n") - console.log(AuthAPI.getPublicKey()) - process.exit() - }) + console.log(token, "\n"); + console.log(JSON.stringify(payload, null, 2), "\n"); + console.log(AuthAPI.getPublicKey()); + process.exit(); + }); }, error => { - console.error(error) - process.exit(1) + console.error(error); + process.exit(1); } -) +); diff --git a/scripts/show-log.js b/scripts/show-log.js index 92ec044..637474b 100644 --- a/scripts/show-log.js +++ b/scripts/show-log.js @@ -1,15 +1,21 @@ -require("isomorphic-fetch") -const invariant = require("invariant") +require("isomorphic-fetch"); +const invariant = require("invariant"); -const CloudflareEmail = process.env.CLOUDFLARE_EMAIL -const CloudflareKey = process.env.CLOUDFLARE_KEY -const RayID = process.argv[2] +const CloudflareEmail = process.env.CLOUDFLARE_EMAIL; +const CloudflareKey = process.env.CLOUDFLARE_KEY; +const RayID = process.argv[2]; -invariant(CloudflareEmail, "Missing the $CLOUDFLARE_EMAIL environment variable") +invariant( + CloudflareEmail, + "Missing the $CLOUDFLARE_EMAIL environment variable" +); -invariant(CloudflareKey, "Missing the $CLOUDFLARE_KEY environment variable") +invariant(CloudflareKey, "Missing the $CLOUDFLARE_KEY environment variable"); -invariant(RayID, "Missing the RAY_ID argument; use `heroku run node show-log.js RAY_ID`") +invariant( + RayID, + "Missing the RAY_ID argument; use `heroku run node show-log.js RAY_ID`" +); function getZones(domain) { return fetch(`https://api.cloudflare.com/client/v4/zones?name=${domain}`, { @@ -20,21 +26,24 @@ function getZones(domain) { } }) .then(res => res.json()) - .then(data => data.result) + .then(data => data.result); } function getLog(zoneId, rayId) { - return fetch(`https://api.cloudflare.com/client/v4/zones/${zoneId}/logs/requests/${rayId}`, { - method: "GET", - headers: { - "X-Auth-Email": CloudflareEmail, - "X-Auth-Key": CloudflareKey + return fetch( + `https://api.cloudflare.com/client/v4/zones/${zoneId}/logs/requests/${rayId}`, + { + method: "GET", + headers: { + "X-Auth-Email": CloudflareEmail, + "X-Auth-Key": CloudflareKey + } } - }).then(res => (res.status === 404 ? "NOT FOUND" : res.json())) + ).then(res => (res.status === 404 ? "NOT FOUND" : res.json())); } getZones("unpkg.com").then(zones => { getLog(zones[0].id, RayID).then(entry => { - console.log(entry) - }) -}) + console.log(entry); + }); +}); diff --git a/scripts/show-stats.js b/scripts/show-stats.js index e15cf7d..78c982b 100644 --- a/scripts/show-stats.js +++ b/scripts/show-stats.js @@ -1,48 +1,58 @@ -const subDays = require("date-fns/sub_days") -const prettyBytes = require("pretty-bytes") -const table = require("text-table") +const subDays = require("date-fns/sub_days"); +const prettyBytes = require("pretty-bytes"); +const table = require("text-table"); -const StatsAPI = require("../server/StatsAPI") -const now = new Date() +const StatsAPI = require("../server/StatsAPI"); +const now = new Date(); function createRange(start, end) { - const range = [] - while (start < end) range.push(start++) - return range + const range = []; + while (start < end) range.push(start++); + return range; } function createPastDays(n) { return createRange(1, n + 1) .map(days => subDays(now, days)) - .reverse() + .reverse(); } -const pastSevenDays = createPastDays(7) -const pastThirtyDays = createPastDays(30) +const pastSevenDays = createPastDays(7); +const pastThirtyDays = createPastDays(30); Promise.all([ - StatsAPI.sumKeys(pastSevenDays.map(date => `stats-requests-${StatsAPI.createDayKey(date)}`)), - StatsAPI.sumKeys(pastSevenDays.map(date => `stats-bandwidth-${StatsAPI.createDayKey(date)}`)), - StatsAPI.sumKeys(pastThirtyDays.map(date => `stats-requests-${StatsAPI.createDayKey(date)}`)), - StatsAPI.sumKeys(pastThirtyDays.map(date => `stats-bandwidth-${StatsAPI.createDayKey(date)}`)) + StatsAPI.sumKeys( + pastSevenDays.map(date => `stats-requests-${StatsAPI.createDayKey(date)}`) + ), + StatsAPI.sumKeys( + pastSevenDays.map(date => `stats-bandwidth-${StatsAPI.createDayKey(date)}`) + ), + StatsAPI.sumKeys( + pastThirtyDays.map(date => `stats-requests-${StatsAPI.createDayKey(date)}`) + ), + StatsAPI.sumKeys( + pastThirtyDays.map(date => `stats-bandwidth-${StatsAPI.createDayKey(date)}`) + ) ]).then(results => { - console.log("\n## Summary") - console.log("Requests this week: %s", results[0].toLocaleString()) - console.log("Bandwidth this week: %s", prettyBytes(results[1])) - console.log("Requests this month: %s", results[2].toLocaleString()) - console.log("Bandwidth this month: %s", prettyBytes(results[3])) + console.log("\n## Summary"); + console.log("Requests this week: %s", results[0].toLocaleString()); + console.log("Bandwidth this week: %s", prettyBytes(results[1])); + console.log("Requests this month: %s", results[2].toLocaleString()); + console.log("Bandwidth this month: %s", prettyBytes(results[3])); StatsAPI.sumTopScores( - pastSevenDays.map(date => `stats-packageRequests-${StatsAPI.createDayKey(date)}`) + pastSevenDays.map( + date => `stats-packageRequests-${StatsAPI.createDayKey(date)}` + ) ).then(topPackages => { - console.log("\n## Top Packages This Week") + console.log("\n## Top Packages This Week"); topPackages.forEach(result => { - result[1] = result[1].toLocaleString() - }) + result[1] = result[1].toLocaleString(); + }); - console.log(table(topPackages)) + console.log(table(topPackages)); - process.exit() - }) -}) + process.exit(); + }); +}); diff --git a/server/AuthAPI.js b/server/AuthAPI.js index 68e79c4..931efa3 100644 --- a/server/AuthAPI.js +++ b/server/AuthAPI.js @@ -1,35 +1,35 @@ -const fs = require("fs") -const path = require("path") -const crypto = require("crypto") -const jwt = require("jsonwebtoken") -const invariant = require("invariant") -const forge = require("node-forge") -const db = require("./RedisClient") +const fs = require("fs"); +const path = require("path"); +const crypto = require("crypto"); +const jwt = require("jsonwebtoken"); +const invariant = require("invariant"); +const forge = require("node-forge"); +const db = require("./RedisClient"); -let keys +let keys; if (process.env.NODE_ENV === "production") { keys = { public: fs.readFileSync(path.resolve(__dirname, "../public.key"), "utf8"), private: process.env.PRIVATE_KEY - } + }; - invariant(keys.private, "Missing $PRIVATE_KEY environment variable") + invariant(keys.private, "Missing $PRIVATE_KEY environment variable"); } else { // Generate a random keypair for dev/testing. // See https://gist.github.com/sebadoom/2b70969e70db5da9a203bebd9cff099f - const keypair = forge.rsa.generateKeyPair({ bits: 2048 }) + const keypair = forge.rsa.generateKeyPair({ bits: 2048 }); keys = { public: forge.pki.publicKeyToPem(keypair.publicKey, 72), private: forge.pki.privateKeyToPem(keypair.privateKey, 72) - } + }; } function getCurrentSeconds() { - return Math.floor(Date.now() / 1000) + return Math.floor(Date.now() / 1000); } function createTokenId() { - return crypto.randomBytes(16).toString("hex") + return crypto.randomBytes(16).toString("hex"); } function createToken(scopes = {}) { @@ -39,42 +39,42 @@ function createToken(scopes = {}) { iss: "https://unpkg.com", iat: getCurrentSeconds(), scopes - } + }; jwt.sign(payload, keys.private, { algorithm: "RS256" }, (error, token) => { if (error) { - reject(error) + reject(error); } else { - resolve(token) + resolve(token); } - }) - }) + }); + }); } -const RevokedTokensSet = "revoked-tokens" +const RevokedTokensSet = "revoked-tokens"; function verifyToken(token) { return new Promise((resolve, reject) => { - const options = { algorithms: ["RS256"] } + const options = { algorithms: ["RS256"] }; jwt.verify(token, keys.public, options, (error, payload) => { if (error) { - reject(error) + reject(error); } else { if (payload.jti) { db.sismember(RevokedTokensSet, payload.jti, (error, value) => { if (error) { - reject(error) + reject(error); } else { - resolve(value === 0 ? payload : null) + resolve(value === 0 ? payload : null); } - }) + }); } else { - resolve(null) + resolve(null); } } - }) - }) + }); + }); } function revokeToken(token) { @@ -83,30 +83,30 @@ function revokeToken(token) { return new Promise((resolve, reject) => { db.sadd(RevokedTokensSet, payload.jti, error => { if (error) { - reject(error) + reject(error); } else { - resolve() + resolve(); } - }) - }) + }); + }); } - }) + }); } function removeAllRevokedTokens() { return new Promise((resolve, reject) => { db.del(RevokedTokensSet, error => { if (error) { - reject(error) + reject(error); } else { - resolve() + resolve(); } - }) - }) + }); + }); } function getPublicKey() { - return keys.public + return keys.public; } module.exports = { @@ -115,4 +115,4 @@ module.exports = { revokeToken, removeAllRevokedTokens, getPublicKey -} +}; diff --git a/server/BlacklistAPI.js b/server/BlacklistAPI.js index dbef237..e1d9d6f 100644 --- a/server/BlacklistAPI.js +++ b/server/BlacklistAPI.js @@ -1,65 +1,65 @@ -const db = require("./RedisClient") +const db = require("./RedisClient"); -const BlacklistSet = "blacklisted-packages" +const BlacklistSet = "blacklisted-packages"; function addPackage(packageName) { return new Promise((resolve, reject) => { db.sadd(BlacklistSet, packageName, (error, value) => { if (error) { - reject(error) + reject(error); } else { - resolve(value === 1) + resolve(value === 1); } - }) - }) + }); + }); } function removePackage(packageName) { return new Promise((resolve, reject) => { db.srem(BlacklistSet, packageName, (error, value) => { if (error) { - reject(error) + reject(error); } else { - resolve(value === 1) + resolve(value === 1); } - }) - }) + }); + }); } function removeAllPackages() { return new Promise((resolve, reject) => { db.del(BlacklistSet, error => { if (error) { - reject(error) + reject(error); } else { - resolve() + resolve(); } - }) - }) + }); + }); } function getPackages() { return new Promise((resolve, reject) => { db.smembers(BlacklistSet, (error, value) => { if (error) { - reject(error) + reject(error); } else { - resolve(value) + resolve(value); } - }) - }) + }); + }); } function includesPackage(packageName) { return new Promise((resolve, reject) => { db.sismember(BlacklistSet, packageName, (error, value) => { if (error) { - reject(error) + reject(error); } else { - resolve(value === 1) + resolve(value === 1); } - }) - }) + }); + }); } module.exports = { @@ -68,4 +68,4 @@ module.exports = { removeAllPackages, getPackages, includesPackage -} +}; diff --git a/server/CloudflareAPI.js b/server/CloudflareAPI.js index e60b364..f732b65 100644 --- a/server/CloudflareAPI.js +++ b/server/CloudflareAPI.js @@ -1,15 +1,18 @@ -require("isomorphic-fetch") -const invariant = require("invariant") -const gunzip = require("gunzip-maybe") -const ndjson = require("ndjson") +require("isomorphic-fetch"); +const invariant = require("invariant"); +const gunzip = require("gunzip-maybe"); +const ndjson = require("ndjson"); -const CloudflareAPIURL = "https://api.cloudflare.com" -const CloudflareEmail = process.env.CLOUDFLARE_EMAIL -const CloudflareKey = process.env.CLOUDFLARE_KEY +const CloudflareAPIURL = "https://api.cloudflare.com"; +const CloudflareEmail = process.env.CLOUDFLARE_EMAIL; +const CloudflareKey = process.env.CLOUDFLARE_KEY; -invariant(CloudflareEmail, "Missing the $CLOUDFLARE_EMAIL environment variable") +invariant( + CloudflareEmail, + "Missing the $CLOUDFLARE_EMAIL environment variable" +); -invariant(CloudflareKey, "Missing the $CLOUDFLARE_KEY environment variable") +invariant(CloudflareKey, "Missing the $CLOUDFLARE_KEY environment variable"); function get(path, headers) { return fetch(`${CloudflareAPIURL}/client/v4${path}`, { @@ -17,49 +20,49 @@ function get(path, headers) { "X-Auth-Email": CloudflareEmail, "X-Auth-Key": CloudflareKey }) - }) + }); } function getJSON(path, headers) { return get(path, headers) .then(res => { - return res.json() + return res.json(); }) .then(data => { if (!data.success) { - console.error(`CloudflareAPI.getJSON failed at ${path}`) - console.error(data) - throw new Error("Failed to getJSON from Cloudflare") + console.error(`CloudflareAPI.getJSON failed at ${path}`); + console.error(data); + throw new Error("Failed to getJSON from Cloudflare"); } - return data.result - }) + return data.result; + }); } function getZones(domains) { return Promise.all( (Array.isArray(domains) ? domains : [domains]).map(domain => { - return getJSON(`/zones?name=${domain}`) + return getJSON(`/zones?name=${domain}`); }) ).then(results => { return results.reduce((memo, zones) => { - return memo.concat(zones) - }) - }) + return memo.concat(zones); + }); + }); } function reduceResults(target, values) { Object.keys(values).forEach(key => { - const value = values[key] + const value = values[key]; if (typeof value === "object" && value) { - target[key] = reduceResults(target[key] || {}, value) + target[key] = reduceResults(target[key] || {}, value); } else if (typeof value === "number") { - target[key] = (target[key] || 0) + values[key] + target[key] = (target[key] || 0) + values[key]; } - }) + }); - return target + return target; } function getZoneAnalyticsDashboard(zones, since, until) { @@ -69,29 +72,31 @@ function getZoneAnalyticsDashboard(zones, since, until) { `/zones/${ zone.id }/analytics/dashboard?since=${since.toISOString()}&until=${until.toISOString()}` - ) + ); }) ).then(results => { - return results.reduce(reduceResults) - }) + return results.reduce(reduceResults); + }); } function getJSONStream(path, headers) { const acceptGzipHeaders = Object.assign({}, headers, { "Accept-Encoding": "gzip" - }) + }); return get(path, acceptGzipHeaders) .then(res => { - return res.body.pipe(gunzip()) + return res.body.pipe(gunzip()); }) .then(stream => { - return stream.pipe(ndjson.parse()) - }) + return stream.pipe(ndjson.parse()); + }); } function getLogs(zoneId, startTime, endTime) { - return getJSONStream(`/zones/${zoneId}/logs/requests?start=${startTime}&end=${endTime}`) + return getJSONStream( + `/zones/${zoneId}/logs/requests?start=${startTime}&end=${endTime}` + ); } module.exports = { @@ -101,4 +106,4 @@ module.exports = { getZoneAnalyticsDashboard, getJSONStream, getLogs -} +}; diff --git a/server/RedisClient.js b/server/RedisClient.js index 15b9509..d5ee386 100644 --- a/server/RedisClient.js +++ b/server/RedisClient.js @@ -1,9 +1,12 @@ -const redis = require("redis") +const redis = require("redis"); -redis.debug_mode = process.env.DEBUG_REDIS != null +redis.debug_mode = process.env.DEBUG_REDIS != null; -const RedisURL = process.env.OPENREDIS_URL || process.env.REDIS_URL || "redis://localhost:6379" +const RedisURL = + process.env.OPENREDIS_URL || + process.env.REDIS_URL || + "redis://localhost:6379"; -const client = redis.createClient(RedisURL) +const client = redis.createClient(RedisURL); -module.exports = client +module.exports = client; diff --git a/server/StatsAPI.js b/server/StatsAPI.js index a176c0a..edb8a10 100644 --- a/server/StatsAPI.js +++ b/server/StatsAPI.js @@ -1,108 +1,114 @@ -const db = require("./RedisClient") -const CloudflareAPI = require("./CloudflareAPI") -const BlacklistAPI = require("./BlacklistAPI") +const db = require("./RedisClient"); +const CloudflareAPI = require("./CloudflareAPI"); +const BlacklistAPI = require("./BlacklistAPI"); function prunePackages(packagesMap) { return Promise.all( Object.keys(packagesMap).map(packageName => BlacklistAPI.includesPackage(packageName).then(blacklisted => { if (blacklisted) { - delete packagesMap[packageName] + delete packagesMap[packageName]; } }) ) - ).then(() => packagesMap) + ).then(() => packagesMap); } function createDayKey(date) { - return `${date.getUTCFullYear()}-${date.getUTCMonth()}-${date.getUTCDate()}` + return `${date.getUTCFullYear()}-${date.getUTCMonth()}-${date.getUTCDate()}`; } function createHourKey(date) { - return `${createDayKey(date)}-${date.getUTCHours()}` + return `${createDayKey(date)}-${date.getUTCHours()}`; } function createMinuteKey(date) { - return `${createHourKey(date)}-${date.getUTCMinutes()}` + return `${createHourKey(date)}-${date.getUTCMinutes()}`; } function createScoresMap(array) { - const map = {} + const map = {}; for (let i = 0; i < array.length; i += 2) { - map[array[i]] = parseInt(array[i + 1], 10) + map[array[i]] = parseInt(array[i + 1], 10); } - return map + return map; } function getScoresMap(key, n = 100) { return new Promise((resolve, reject) => { db.zrevrange(key, 0, n, "withscores", (error, value) => { if (error) { - reject(error) + reject(error); } else { - resolve(createScoresMap(value)) + resolve(createScoresMap(value)); } - }) - }) + }); + }); } function getPackageRequests(date, n = 100) { - return getScoresMap(`stats-packageRequests-${createDayKey(date)}`, n).then(prunePackages) + return getScoresMap(`stats-packageRequests-${createDayKey(date)}`, n).then( + prunePackages + ); } function getPackageBandwidth(date, n = 100) { - return getScoresMap(`stats-packageBytes-${createDayKey(date)}`, n).then(prunePackages) + return getScoresMap(`stats-packageBytes-${createDayKey(date)}`, n).then( + prunePackages + ); } function getProtocolRequests(date) { - return getScoresMap(`stats-protocolRequests-${createDayKey(date)}`) + return getScoresMap(`stats-protocolRequests-${createDayKey(date)}`); } function addDailyMetricsToTimeseries(timeseries) { - const since = new Date(timeseries.since) + const since = new Date(timeseries.since); return Promise.all([ getPackageRequests(since), getPackageBandwidth(since), getProtocolRequests(since) ]).then(results => { - timeseries.requests.package = results[0] - timeseries.bandwidth.package = results[1] - timeseries.requests.protocol = results[2] - return timeseries - }) + timeseries.requests.package = results[0]; + timeseries.bandwidth.package = results[1]; + timeseries.requests.protocol = results[2]; + return timeseries; + }); } function sumMaps(maps) { return maps.reduce((memo, map) => { Object.keys(map).forEach(key => { - memo[key] = (memo[key] || 0) + map[key] - }) + memo[key] = (memo[key] || 0) + map[key]; + }); - return memo - }, {}) + return memo; + }, {}); } function addDailyMetrics(result) { - return Promise.all(result.timeseries.map(addDailyMetricsToTimeseries)).then(() => { - result.totals.requests.package = sumMaps( - result.timeseries.map(timeseries => { - return timeseries.requests.package - }) - ) + return Promise.all(result.timeseries.map(addDailyMetricsToTimeseries)).then( + () => { + result.totals.requests.package = sumMaps( + result.timeseries.map(timeseries => { + return timeseries.requests.package; + }) + ); - result.totals.bandwidth.package = sumMaps( - result.timeseries.map(timeseries => timeseries.bandwidth.package) - ) + result.totals.bandwidth.package = sumMaps( + result.timeseries.map(timeseries => timeseries.bandwidth.package) + ); - result.totals.requests.protocol = sumMaps( - result.timeseries.map(timeseries => timeseries.requests.protocol) - ) + result.totals.requests.protocol = sumMaps( + result.timeseries.map(timeseries => timeseries.requests.protocol) + ); - return result - }) + return result; + } + ); } function extractPublicInfo(data) { @@ -131,29 +137,31 @@ function extractPublicInfo(data) { uniques: { all: data.uniques.all } - } + }; } -const DomainNames = ["unpkg.com", "npmcdn.com"] +const DomainNames = ["unpkg.com", "npmcdn.com"]; function fetchStats(since, until) { return CloudflareAPI.getZones(DomainNames).then(zones => { - return CloudflareAPI.getZoneAnalyticsDashboard(zones, since, until).then(dashboard => { - return { - timeseries: dashboard.timeseries.map(extractPublicInfo), - totals: extractPublicInfo(dashboard.totals) + return CloudflareAPI.getZoneAnalyticsDashboard(zones, since, until).then( + dashboard => { + return { + timeseries: dashboard.timeseries.map(extractPublicInfo), + totals: extractPublicInfo(dashboard.totals) + }; } - }) - }) + ); + }); } -const oneMinute = 1000 * 60 -const oneHour = oneMinute * 60 -const oneDay = oneHour * 24 +const oneMinute = 1000 * 60; +const oneHour = oneMinute * 60; +const oneDay = oneHour * 24; function getStats(since, until) { - const promise = fetchStats(since, until) - return until - since > oneDay ? promise.then(addDailyMetrics) : promise + const promise = fetchStats(since, until); + return until - since > oneDay ? promise.then(addDailyMetrics) : promise; } module.exports = { @@ -161,4 +169,4 @@ module.exports = { createHourKey, createMinuteKey, getStats -} +}; diff --git a/server/__tests__/AuthAPI-test.js b/server/__tests__/AuthAPI-test.js index 67a0335..536b1bf 100644 --- a/server/__tests__/AuthAPI-test.js +++ b/server/__tests__/AuthAPI-test.js @@ -1,9 +1,9 @@ -const AuthAPI = require("../AuthAPI") +const AuthAPI = require("../AuthAPI"); describe("Auth API", () => { beforeEach(done => { - AuthAPI.removeAllRevokedTokens().then(() => done(), done) - }) + AuthAPI.removeAllRevokedTokens().then(() => done(), done); + }); it("creates tokens with the right scopes", done => { const scopes = { @@ -11,29 +11,29 @@ describe("Auth API", () => { add: true, remove: true } - } + }; AuthAPI.createToken(scopes).then(token => { AuthAPI.verifyToken(token).then(payload => { - expect(payload.jti).toEqual(expect.any(String)) - expect(payload.iss).toEqual(expect.any(String)) - expect(payload.iat).toEqual(expect.any(Number)) - expect(payload.scopes).toMatchObject(scopes) - done() - }) - }) - }) + expect(payload.jti).toEqual(expect.any(String)); + expect(payload.iss).toEqual(expect.any(String)); + expect(payload.iat).toEqual(expect.any(Number)); + expect(payload.scopes).toMatchObject(scopes); + done(); + }); + }); + }); it("refuses to verify revoked tokens", done => { - const scopes = {} + const scopes = {}; AuthAPI.createToken(scopes).then(token => { AuthAPI.revokeToken(token).then(() => { AuthAPI.verifyToken(token).then(payload => { - expect(payload).toBe(null) - done() - }) - }) - }) - }) -}) + expect(payload).toBe(null); + done(); + }); + }); + }); + }); +}); diff --git a/server/__tests__/BlacklistAPI-test.js b/server/__tests__/BlacklistAPI-test.js index b00372d..4d2ef48 100644 --- a/server/__tests__/BlacklistAPI-test.js +++ b/server/__tests__/BlacklistAPI-test.js @@ -1,24 +1,24 @@ -const BlacklistAPI = require("../BlacklistAPI") +const BlacklistAPI = require("../BlacklistAPI"); describe("Blacklist API", () => { beforeEach(done => { - BlacklistAPI.removeAllPackages().then(() => done(), done) - }) + BlacklistAPI.removeAllPackages().then(() => done(), done); + }); it("adds and removes packages to/from the blacklist", done => { - const packageName = "bad-package" + const packageName = "bad-package"; BlacklistAPI.addPackage(packageName).then(() => { BlacklistAPI.getPackages().then(packageNames => { - expect(packageNames).toEqual([packageName]) + expect(packageNames).toEqual([packageName]); BlacklistAPI.removePackage(packageName).then(() => { BlacklistAPI.getPackages().then(packageNames => { - expect(packageNames).toEqual([]) - done() - }) - }) - }) - }) - }) -}) + expect(packageNames).toEqual([]); + done(); + }); + }); + }); + }); + }); +}); diff --git a/server/__tests__/server-test.js b/server/__tests__/server-test.js index 7dc0eec..f109d67 100644 --- a/server/__tests__/server-test.js +++ b/server/__tests__/server-test.js @@ -1,66 +1,66 @@ -const request = require("supertest") -const createServer = require("../createServer") -const clearBlacklist = require("./utils/clearBlacklist") -const withBlacklist = require("./utils/withBlacklist") -const withRevokedToken = require("./utils/withRevokedToken") -const withToken = require("./utils/withToken") +const request = require("supertest"); +const createServer = require("../createServer"); +const clearBlacklist = require("./utils/clearBlacklist"); +const withBlacklist = require("./utils/withBlacklist"); +const withRevokedToken = require("./utils/withRevokedToken"); +const withToken = require("./utils/withToken"); describe("The server", () => { - let server + let server; beforeEach(() => { - server = createServer() - }) + server = createServer(); + }); it("rejects invalid package names", done => { request(server) .get("/_invalid/index.js") .end((err, res) => { - expect(res.statusCode).toBe(403) - done() - }) - }) + expect(res.statusCode).toBe(403); + 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() - }) - }) + expect(res.statusCode).toBe(302); + expect(res.headers.location).toBe("/react?main=index"); + done(); + }); + }); it("redirects /_meta to ?meta", done => { request(server) .get("/_meta/react?main=index") .end((err, res) => { - expect(res.statusCode).toBe(302) - expect(res.headers.location).toBe("/react?main=index&meta") - done() - }) - }) + expect(res.statusCode).toBe(302); + expect(res.headers.location).toBe("/react?main=index&meta"); + done(); + }); + }); it("does not serve blacklisted packages", done => { withBlacklist(["bad-package"], () => { request(server) .get("/bad-package/index.js") .end((err, res) => { - expect(res.statusCode).toBe(403) - done() - }) - }) - }) + expect(res.statusCode).toBe(403); + done(); + }); + }); + }); describe("POST /_auth", () => { it("creates a new auth token", done => { request(server) .post("/_auth") .end((err, res) => { - expect(res.body).toHaveProperty("token") - done() - }) - }) - }) + expect(res.body).toHaveProperty("token"); + done(); + }); + }); + }); describe("GET /_auth", () => { describe("with no auth", () => { @@ -68,12 +68,12 @@ describe("The server", () => { request(server) .get("/_auth") .end((err, res) => { - expect(res.body).toHaveProperty("auth") - expect(res.body.auth).toBe(null) - done() - }) - }) - }) + expect(res.body).toHaveProperty("auth"); + expect(res.body.auth).toBe(null); + done(); + }); + }); + }); describe("with a revoked auth token", () => { it("echoes back null", done => { @@ -81,13 +81,13 @@ describe("The server", () => { request(server) .get("/_auth?token=" + token) .end((err, res) => { - expect(res.body).toHaveProperty("auth") - expect(res.body.auth).toBe(null) - done() - }) - }) - }) - }) + expect(res.body).toHaveProperty("auth"); + expect(res.body.auth).toBe(null); + done(); + }); + }); + }); + }); describe("with a valid auth token", () => { it("echoes back the auth payload", done => { @@ -95,39 +95,39 @@ describe("The server", () => { request(server) .get("/_auth?token=" + token) .end((err, res) => { - expect(res.body).toHaveProperty("auth") - expect(typeof res.body.auth).toBe("object") - done() - }) - }) - }) - }) - }) + expect(res.body).toHaveProperty("auth"); + expect(typeof res.body.auth).toBe("object"); + done(); + }); + }); + }); + }); + }); describe("GET /_publicKey", () => { it("echoes the public key", done => { request(server) .get("/_publicKey") .end((err, res) => { - expect(res.text).toMatch(/PUBLIC KEY/) - done() - }) - }) - }) + expect(res.text).toMatch(/PUBLIC KEY/); + done(); + }); + }); + }); describe("POST /_blacklist", () => { - afterEach(clearBlacklist) + afterEach(clearBlacklist); describe("with no auth", () => { it("is forbidden", done => { request(server) .post("/_blacklist") .end((err, res) => { - expect(res.statusCode).toBe(403) - done() - }) - }) - }) + expect(res.statusCode).toBe(403); + done(); + }); + }); + }); describe('with the "blacklist.add" scope', () => { it("can add to the blacklist", done => { @@ -136,15 +136,17 @@ describe("The server", () => { .post("/_blacklist") .send({ token, packageName: "bad-package" }) .end((err, res) => { - expect(res.statusCode).toBe(200) - expect(res.headers["content-location"]).toEqual("/_blacklist/bad-package") - expect(res.body.ok).toBe(true) - done() - }) - }) - }) - }) - }) + expect(res.statusCode).toBe(200); + expect(res.headers["content-location"]).toEqual( + "/_blacklist/bad-package" + ); + expect(res.body.ok).toBe(true); + done(); + }); + }); + }); + }); + }); describe("GET /_blacklist", () => { describe("with no auth", () => { @@ -152,11 +154,11 @@ describe("The server", () => { request(server) .get("/_blacklist") .end((err, res) => { - expect(res.statusCode).toBe(403) - done() - }) - }) - }) + expect(res.statusCode).toBe(403); + done(); + }); + }); + }); describe('with the "blacklist.read" scope', () => { it("can read the blacklist", done => { @@ -164,13 +166,13 @@ describe("The server", () => { request(server) .get("/_blacklist?token=" + token) .end((err, res) => { - expect(res.statusCode).toBe(200) - done() - }) - }) - }) - }) - }) + expect(res.statusCode).toBe(200); + done(); + }); + }); + }); + }); + }); describe("DELETE /_blacklist/:packageName", () => { describe("with no auth", () => { @@ -178,11 +180,11 @@ describe("The server", () => { request(server) .delete("/_blacklist/bad-package") .end((err, res) => { - expect(res.statusCode).toBe(403) - done() - }) - }) - }) + expect(res.statusCode).toBe(403); + done(); + }); + }); + }); describe('with the "blacklist.remove" scope', () => { it("can remove a package from the blacklist", done => { @@ -191,12 +193,12 @@ describe("The server", () => { .delete("/_blacklist/bad-package") .send({ token }) .end((err, res) => { - expect(res.statusCode).toBe(200) - expect(res.body.ok).toBe(true) - done() - }) - }) - }) + expect(res.statusCode).toBe(200); + expect(res.body.ok).toBe(true); + done(); + }); + }); + }); it("can remove a scoped package from the blacklist", done => { withToken({ blacklist: { remove: true } }, token => { @@ -204,12 +206,12 @@ describe("The server", () => { .delete("/_blacklist/@scope/bad-package") .send({ token }) .end((err, res) => { - expect(res.statusCode).toBe(200) - expect(res.body.ok).toBe(true) - done() - }) - }) - }) - }) - }) -}) + expect(res.statusCode).toBe(200); + expect(res.body.ok).toBe(true); + done(); + }); + }); + }); + }); + }); +}); diff --git a/server/__tests__/utils/clearBlacklist.js b/server/__tests__/utils/clearBlacklist.js index d4086e9..8bbbf31 100644 --- a/server/__tests__/utils/clearBlacklist.js +++ b/server/__tests__/utils/clearBlacklist.js @@ -1,7 +1,7 @@ -const BlacklistAPI = require("../../BlacklistAPI") +const BlacklistAPI = require("../../BlacklistAPI"); function clearBlacklist(done) { - BlacklistAPI.removeAllPackages().then(done, done) + BlacklistAPI.removeAllPackages().then(done, done); } -module.exports = clearBlacklist +module.exports = clearBlacklist; diff --git a/server/__tests__/utils/withBlacklist.js b/server/__tests__/utils/withBlacklist.js index cd666f8..87bbaa0 100644 --- a/server/__tests__/utils/withBlacklist.js +++ b/server/__tests__/utils/withBlacklist.js @@ -1,7 +1,7 @@ -const BlacklistAPI = require("../../BlacklistAPI") +const BlacklistAPI = require("../../BlacklistAPI"); function withBlacklist(blacklist, callback) { - return Promise.all(blacklist.map(BlacklistAPI.addPackage)).then(callback) + return Promise.all(blacklist.map(BlacklistAPI.addPackage)).then(callback); } -module.exports = withBlacklist +module.exports = withBlacklist; diff --git a/server/__tests__/utils/withRevokedToken.js b/server/__tests__/utils/withRevokedToken.js index efa0bf3..44b37d2 100644 --- a/server/__tests__/utils/withRevokedToken.js +++ b/server/__tests__/utils/withRevokedToken.js @@ -1,12 +1,12 @@ -const withToken = require("./withToken") -const AuthAPI = require("../../AuthAPI") +const withToken = require("./withToken"); +const AuthAPI = require("../../AuthAPI"); function withRevokedToken(scopes, callback) { withToken(scopes, token => { AuthAPI.revokeToken(token).then(() => { - callback(token) - }) - }) + callback(token); + }); + }); } -module.exports = withRevokedToken +module.exports = withRevokedToken; diff --git a/server/__tests__/utils/withToken.js b/server/__tests__/utils/withToken.js index fbaf4af..fde0fd1 100644 --- a/server/__tests__/utils/withToken.js +++ b/server/__tests__/utils/withToken.js @@ -1,7 +1,7 @@ -const AuthAPI = require("../../AuthAPI") +const AuthAPI = require("../../AuthAPI"); function withToken(scopes, callback) { - AuthAPI.createToken(scopes).then(callback) + AuthAPI.createToken(scopes).then(callback); } -module.exports = withToken +module.exports = withToken; diff --git a/server/actions/addToBlacklist.js b/server/actions/addToBlacklist.js index 0debb3e..b43d18b 100644 --- a/server/actions/addToBlacklist.js +++ b/server/actions/addToBlacklist.js @@ -1,42 +1,48 @@ -const validateNpmPackageName = require("validate-npm-package-name") -const BlacklistAPI = require("../BlacklistAPI") +const validateNpmPackageName = require("validate-npm-package-name"); +const BlacklistAPI = require("../BlacklistAPI"); function addToBlacklist(req, res) { - const packageName = req.body.packageName + const packageName = req.body.packageName; if (!packageName) { - return res.status(403).send({ error: 'Missing "packageName" body parameter' }) + return res + .status(403) + .send({ error: 'Missing "packageName" body parameter' }); } - const nameErrors = validateNpmPackageName(packageName).errors + const nameErrors = validateNpmPackageName(packageName).errors; // Disallow invalid package names. if (nameErrors) { - const reason = nameErrors.join(", ") + const reason = nameErrors.join(", "); return res.status(403).send({ error: `Invalid package name "${packageName}" (${reason})` - }) + }); } BlacklistAPI.addPackage(packageName).then( added => { if (added) { - const userId = req.user.jti - console.log(`Package "${packageName}" was added to the blacklist by ${userId}`) + const userId = req.user.jti; + console.log( + `Package "${packageName}" was added to the blacklist by ${userId}` + ); } res.set({ "Content-Location": `/_blacklist/${packageName}` }).send({ ok: true, - message: `Package "${packageName}" was ${added ? "added to" : "already in"} the blacklist` - }) + message: `Package "${packageName}" was ${ + added ? "added to" : "already in" + } the blacklist` + }); }, error => { - console.error(error) + console.error(error); res.status(500).send({ error: `Unable to add "${packageName}" to the blacklist` - }) + }); } - ) + ); } -module.exports = addToBlacklist +module.exports = addToBlacklist; diff --git a/server/actions/createAuth.js b/server/actions/createAuth.js index 2b611ac..4fd8086 100644 --- a/server/actions/createAuth.js +++ b/server/actions/createAuth.js @@ -1,24 +1,24 @@ -const AuthAPI = require("../AuthAPI") +const AuthAPI = require("../AuthAPI"); const defaultScopes = { blacklist: { read: true } -} +}; function createAuth(req, res) { AuthAPI.createToken(defaultScopes).then( token => { - res.send({ token }) + res.send({ token }); }, error => { - console.error(error) + console.error(error); res.status(500).send({ error: "Unable to generate auth token" - }) + }); } - ) + ); } -module.exports = createAuth +module.exports = createAuth; diff --git a/server/actions/removeFromBlacklist.js b/server/actions/removeFromBlacklist.js index ba4d2cb..663dac0 100644 --- a/server/actions/removeFromBlacklist.js +++ b/server/actions/removeFromBlacklist.js @@ -1,28 +1,32 @@ -const BlacklistAPI = require("../BlacklistAPI") +const BlacklistAPI = require("../BlacklistAPI"); function removeFromBlacklist(req, res) { - const packageName = req.packageName + const packageName = req.packageName; BlacklistAPI.removePackage(packageName).then( removed => { if (removed) { - const userId = req.user.jti - console.log(`Package "${packageName}" was removed from the blacklist by ${userId}`) + const userId = req.user.jti; + console.log( + `Package "${packageName}" was removed from the blacklist by ${userId}` + ); } res.send({ ok: true, - message: `Package "${packageName}" was ${removed ? "removed from" : "not in"} the blacklist` - }) + message: `Package "${packageName}" was ${ + removed ? "removed from" : "not in" + } the blacklist` + }); }, error => { - console.error(error) + console.error(error); res.status(500).send({ error: `Unable to remove "${packageName}" from the blacklist` - }) + }); } - ) + ); } -module.exports = removeFromBlacklist +module.exports = removeFromBlacklist; diff --git a/server/actions/showAuth.js b/server/actions/showAuth.js index 282ae17..dad71e5 100644 --- a/server/actions/showAuth.js +++ b/server/actions/showAuth.js @@ -1,5 +1,5 @@ function showAuth(req, res) { - res.send({ auth: req.user }) + res.send({ auth: req.user }); } -module.exports = showAuth +module.exports = showAuth; diff --git a/server/actions/showBlacklist.js b/server/actions/showBlacklist.js index 84d1d3c..8f8b355 100644 --- a/server/actions/showBlacklist.js +++ b/server/actions/showBlacklist.js @@ -1,17 +1,17 @@ -const BlacklistAPI = require("../BlacklistAPI") +const BlacklistAPI = require("../BlacklistAPI"); function showBlacklist(req, res) { BlacklistAPI.getPackages().then( blacklist => { - res.send({ blacklist }) + res.send({ blacklist }); }, error => { - console.error(error) + console.error(error); res.status(500).send({ error: "Unable to fetch blacklist" - }) + }); } - ) + ); } -module.exports = showBlacklist +module.exports = showBlacklist; diff --git a/server/actions/showPublicKey.js b/server/actions/showPublicKey.js index 4d88237..d5dd5b5 100644 --- a/server/actions/showPublicKey.js +++ b/server/actions/showPublicKey.js @@ -1,7 +1,7 @@ -const AuthAPI = require("../AuthAPI") +const AuthAPI = require("../AuthAPI"); function showPublicKey(req, res) { - res.send({ publicKey: AuthAPI.getPublicKey() }) + res.send({ publicKey: AuthAPI.getPublicKey() }); } -module.exports = showPublicKey +module.exports = showPublicKey; diff --git a/server/actions/showStats.js b/server/actions/showStats.js index 59fab0d..dc6acda 100644 --- a/server/actions/showStats.js +++ b/server/actions/showStats.js @@ -1,42 +1,46 @@ -const subDays = require("date-fns/sub_days") -const startOfDay = require("date-fns/start_of_day") -const startOfSecond = require("date-fns/start_of_second") -const StatsAPI = require("../StatsAPI") +const subDays = require("date-fns/sub_days"); +const startOfDay = require("date-fns/start_of_day"); +const startOfSecond = require("date-fns/start_of_second"); +const StatsAPI = require("../StatsAPI"); function showStats(req, res) { - let since, until + let since, until; switch (req.query.period) { case "last-day": - until = startOfDay(new Date()) - since = subDays(until, 1) - break + until = startOfDay(new Date()); + since = subDays(until, 1); + break; case "last-week": - until = startOfDay(new Date()) - since = subDays(until, 7) - break + until = startOfDay(new Date()); + since = subDays(until, 7); + break; case "last-month": - until = startOfDay(new Date()) - since = subDays(until, 30) - break + until = startOfDay(new Date()); + since = subDays(until, 30); + break; default: - until = req.query.until ? new Date(req.query.until) : startOfSecond(new Date()) - since = new Date(req.query.since) + until = req.query.until + ? new Date(req.query.until) + : startOfSecond(new Date()); + since = new Date(req.query.since); } if (isNaN(since.getTime())) { - return res.status(403).send({ error: "?since is not a valid date" }) + return res.status(403).send({ error: "?since is not a valid date" }); } if (isNaN(until.getTime())) { - return res.status(403).send({ error: "?until is not a valid date" }) + return res.status(403).send({ error: "?until is not a valid date" }); } if (until <= since) { - return res.status(403).send({ error: "?until date must come after ?since date" }) + return res + .status(403) + .send({ error: "?until date must come after ?since date" }); } if (until >= new Date()) { - return res.status(403).send({ error: "?until must be a date in the past" }) + return res.status(403).send({ error: "?until must be a date in the past" }); } StatsAPI.getStats(since, until).then( @@ -46,13 +50,13 @@ function showStats(req, res) { "Cache-Control": "public, max-age=60", "Cache-Tag": "stats" }) - .send(stats) + .send(stats); }, error => { - console.error(error) - res.status(500).send({ error: "Unable to fetch stats" }) + console.error(error); + res.status(500).send({ error: "Unable to fetch stats" }); } - ) + ); } -module.exports = showStats +module.exports = showStats; diff --git a/server/ingestLogs.js b/server/ingestLogs.js index 06964d0..7e872e6 100644 --- a/server/ingestLogs.js +++ b/server/ingestLogs.js @@ -1,11 +1,11 @@ -const parseURL = require("url").parse -const startOfDay = require("date-fns/start_of_day") -const addDays = require("date-fns/add_days") -const parsePackageURL = require("./utils/parsePackageURL") -const CloudflareAPI = require("./CloudflareAPI") -const StatsAPI = require("./StatsAPI") +const parseURL = require("url").parse; +const startOfDay = require("date-fns/start_of_day"); +const addDays = require("date-fns/add_days"); +const parsePackageURL = require("./utils/parsePackageURL"); +const CloudflareAPI = require("./CloudflareAPI"); +const StatsAPI = require("./StatsAPI"); -const db = require("./RedisClient") +const db = require("./RedisClient"); /** * Domains we want to analyze. @@ -13,98 +13,119 @@ const db = require("./RedisClient") const DomainNames = [ "unpkg.com" //'npmcdn.com' // We don't have log data on npmcdn.com yet :/ -] +]; /** * The window of time to download in a single fetch. */ -const LogWindowSeconds = 30 +const LogWindowSeconds = 30; function getSeconds(date) { - return Math.floor(date.getTime() / 1000) + return Math.floor(date.getTime() / 1000); } function stringifySeconds(seconds) { - return new Date(seconds * 1000).toISOString() + return new Date(seconds * 1000).toISOString(); } function toSeconds(millis) { - return Math.floor(millis / 1000) + return Math.floor(millis / 1000); } -const oneSecond = 1000 -const oneMinute = oneSecond * 60 -const oneHour = oneMinute * 60 +const oneSecond = 1000; +const oneMinute = oneSecond * 60; +const oneHour = oneMinute * 60; function computeCounters(stream) { return new Promise((resolve, reject) => { - const counters = {} - const expireat = {} + const counters = {}; + const expireat = {}; function incr(key, member, by, expiry) { - counters[key] = counters[key] || {} - counters[key][member] = (counters[key][member] || 0) + by - expireat[key] = expiry + counters[key] = counters[key] || {}; + counters[key][member] = (counters[key][member] || 0) + by; + expireat[key] = expiry; } stream .on("error", reject) .on("data", function(entry) { - const date = new Date(Math.round(entry.timestamp / 1000000)) + const date = new Date(Math.round(entry.timestamp / 1000000)); - const nextDay = startOfDay(addDays(date, 1)) - const sevenDaysLater = getSeconds(addDays(nextDay, 7)) - const thirtyDaysLater = getSeconds(addDays(nextDay, 30)) - const dayKey = StatsAPI.createDayKey(date) + const nextDay = startOfDay(addDays(date, 1)); + const sevenDaysLater = getSeconds(addDays(nextDay, 7)); + const thirtyDaysLater = getSeconds(addDays(nextDay, 30)); + const dayKey = StatsAPI.createDayKey(date); - const clientRequest = entry.clientRequest - const edgeResponse = entry.edgeResponse + const clientRequest = entry.clientRequest; + const edgeResponse = entry.edgeResponse; if (edgeResponse.status === 200) { // Q: How many requests do we serve for a package per day? // Q: How many bytes do we serve for a package per day? - const url = parsePackageURL(parseURL(clientRequest.uri).pathname) - const packageName = url && url.packageName + const url = parsePackageURL(parseURL(clientRequest.uri).pathname); + const packageName = url && url.packageName; if (packageName) { - incr(`stats-packageRequests-${dayKey}`, packageName, 1, thirtyDaysLater) - incr(`stats-packageBytes-${dayKey}`, packageName, edgeResponse.bytes, thirtyDaysLater) + incr( + `stats-packageRequests-${dayKey}`, + packageName, + 1, + thirtyDaysLater + ); + incr( + `stats-packageBytes-${dayKey}`, + packageName, + edgeResponse.bytes, + thirtyDaysLater + ); } } // Q: How many requests per day do we receive via a protocol? - const protocol = clientRequest.httpProtocol + const protocol = clientRequest.httpProtocol; - if (protocol) incr(`stats-protocolRequests-${dayKey}`, protocol, 1, thirtyDaysLater) + if (protocol) + incr( + `stats-protocolRequests-${dayKey}`, + protocol, + 1, + thirtyDaysLater + ); // Q: How many requests do we receive from a hostname per day? // Q: How many bytes do we serve to a hostname per day? - const referer = clientRequest.referer - const hostname = referer && parseURL(referer).hostname + const referer = clientRequest.referer; + const hostname = referer && parseURL(referer).hostname; if (hostname) { - incr(`stats-hostnameRequests-${dayKey}`, hostname, 1, sevenDaysLater) - incr(`stats-hostnameBytes-${dayKey}`, hostname, edgeResponse.bytes, sevenDaysLater) + incr(`stats-hostnameRequests-${dayKey}`, hostname, 1, sevenDaysLater); + incr( + `stats-hostnameBytes-${dayKey}`, + hostname, + edgeResponse.bytes, + sevenDaysLater + ); } }) .on("end", function() { - resolve({ counters, expireat }) - }) - }) + resolve({ counters, expireat }); + }); + }); } function processLogs(stream) { return computeCounters(stream).then(({ counters, expireat }) => { Object.keys(counters).forEach(key => { - const values = counters[key] + const values = counters[key]; Object.keys(values).forEach(member => { - db.zincrby(key, values[member], member) - }) + db.zincrby(key, values[member], member); + }); - if (expireat[key]) db.expireat(key, expireat[key]) - }) - }) + if (expireat[key]) db.expireat(key, expireat[key]); + }); + }); } function ingestLogs(zone, startSeconds, endSeconds) { @@ -114,62 +135,65 @@ function ingestLogs(zone, startSeconds, endSeconds) { zone.name, stringifySeconds(startSeconds), stringifySeconds(endSeconds) - ) + ); - const startFetchTime = Date.now() + const startFetchTime = Date.now(); resolve( CloudflareAPI.getLogs(zone.id, startSeconds, endSeconds).then(stream => { - const endFetchTime = Date.now() + const endFetchTime = Date.now(); console.log( "info: Fetched %ds worth of logs for %s in %dms", endSeconds - startSeconds, zone.name, endFetchTime - startFetchTime - ) + ); - const startProcessTime = Date.now() + const startProcessTime = Date.now(); return processLogs(stream).then(() => { - const endProcessTime = Date.now() + const endProcessTime = Date.now(); console.log( "info: Processed %ds worth of logs for %s in %dms", endSeconds - startSeconds, zone.name, endProcessTime - startProcessTime - ) - }) + ); + }); }) - ) - }) + ); + }); } function startZone(zone) { - const startSecondsKey = `ingestLogsWorker-nextStartSeconds-${zone.name.replace(".", "-")}` + const startSecondsKey = `ingestLogsWorker-nextStartSeconds-${zone.name.replace( + ".", + "-" + )}`; function takeATurn() { db.get(startSecondsKey, function(error, value) { - let startSeconds = value && parseInt(value, 10) + let startSeconds = value && parseInt(value, 10); - const now = Date.now() + const now = Date.now(); // Cloudflare keeps logs around for 72 hours. // https://support.cloudflare.com/hc/en-us/articles/216672448-Enterprise-Log-Share-REST-API - const minSeconds = toSeconds(now - oneHour * 72) + const minSeconds = toSeconds(now - oneHour * 72); if (startSeconds == null) { - startSeconds = minSeconds + startSeconds = minSeconds; } else if (startSeconds < minSeconds) { console.warn( "warning: Dropped logs for %s from %s to %s!", zone.name, stringifySeconds(startSeconds), stringifySeconds(minSeconds) - ) + ); - startSeconds = minSeconds + startSeconds = minSeconds; } // The log for a request is typically available within thirty (30) minutes @@ -180,34 +204,34 @@ function startZone(zone) { // set of logs. This will help ensure that any congestion in the log // pipeline has passed and a full set of logs can be ingested. // https://support.cloudflare.com/hc/en-us/articles/216672448-Enterprise-Log-Share-REST-API - const maxSeconds = toSeconds(now - oneMinute * 30) + const maxSeconds = toSeconds(now - oneMinute * 30); if (startSeconds < maxSeconds) { - const endSeconds = startSeconds + LogWindowSeconds + const endSeconds = startSeconds + LogWindowSeconds; ingestLogs(zone, startSeconds, endSeconds).then( function() { - db.set(startSecondsKey, endSeconds) - setTimeout(takeATurn) + db.set(startSecondsKey, endSeconds); + setTimeout(takeATurn); }, function(error) { - console.error(error.stack) - process.exit(1) + console.error(error.stack); + process.exit(1); } - ) + ); } else { - setTimeout(takeATurn, (startSeconds - maxSeconds) * 1000) + setTimeout(takeATurn, (startSeconds - maxSeconds) * 1000); } - }) + }); } - takeATurn() + takeATurn(); } Promise.all(DomainNames.map(CloudflareAPI.getZones)).then(results => { const zones = results.reduce((memo, zones) => { - return memo.concat(zones) - }) + return memo.concat(zones); + }); - zones.forEach(startZone) -}) + zones.forEach(startZone); +}); diff --git a/server/middleware/requireAuth.js b/server/middleware/requireAuth.js index abe6c5b..aa88a9d 100644 --- a/server/middleware/requireAuth.js +++ b/server/middleware/requireAuth.js @@ -3,37 +3,38 @@ * permissions. Otherwise rejects the request. */ function requireAuth(scope) { - let checkScopes + let checkScopes; if (scope.includes(".")) { - const parts = scope.split(".") - checkScopes = scopes => parts.reduce((memo, part) => memo && memo[part], scopes) != null + const parts = scope.split("."); + checkScopes = scopes => + parts.reduce((memo, part) => memo && memo[part], scopes) != null; } else { - checkScopes = scopes => scopes[scope] != null + checkScopes = scopes => scopes[scope] != null; } return function(req, res, next) { if (req.auth && req.auth.includes(scope)) { - return next() // Already auth'd + return next(); // Already auth'd } - const user = req.user + const user = req.user; if (!user) { - return res.status(403).send({ error: "Missing auth token" }) + return res.status(403).send({ error: "Missing auth token" }); } if (!user.scopes || !checkScopes(user.scopes)) { - return res.status(403).send({ error: "Insufficient scopes" }) + return res.status(403).send({ error: "Insufficient scopes" }); } if (req.auth) { - req.auth.push(scope) + req.auth.push(scope); } else { - req.auth = [scope] + req.auth = [scope]; } - next() - } + next(); + }; } -module.exports = requireAuth +module.exports = requireAuth; diff --git a/server/middleware/userToken.js b/server/middleware/userToken.js index ff0a621..79b4be9 100644 --- a/server/middleware/userToken.js +++ b/server/middleware/userToken.js @@ -1,41 +1,41 @@ -const AuthAPI = require("../AuthAPI") +const AuthAPI = require("../AuthAPI"); -const ReadMethods = { GET: true, HEAD: true } +const ReadMethods = { GET: true, HEAD: true }; /** * Sets req.user from the payload in the auth token in the request. */ function userToken(req, res, next) { if (req.user) { - return next() + return next(); } - const token = (ReadMethods[req.method] ? req.query : req.body).token + const token = (ReadMethods[req.method] ? req.query : req.body).token; if (!token) { - req.user = null - return next() + req.user = null; + return next(); } AuthAPI.verifyToken(token).then( payload => { - req.user = payload - next() + req.user = payload; + next(); }, error => { if (error.name === "JsonWebTokenError") { res.status(403).send({ error: `Bad auth token: ${error.message}` - }) + }); } else { - console.error(error) + console.error(error); res.status(500).send({ error: "Unable to verify auth" - }) + }); } } - ) + ); } -module.exports = userToken +module.exports = userToken; diff --git a/server/middleware/utils/createCache.js b/server/middleware/utils/createCache.js index da6d62b..0f4272e 100644 --- a/server/middleware/utils/createCache.js +++ b/server/middleware/utils/createCache.js @@ -1,29 +1,29 @@ -const db = require("../../RedisClient") +const db = require("../../RedisClient"); function createCache(keyPrefix) { function createKey(key) { - return keyPrefix + "-" + key + return keyPrefix + "-" + key; } function set(key, value, expiry, callback) { - db.setex(createKey(key), expiry, JSON.stringify(value), callback) + db.setex(createKey(key), expiry, JSON.stringify(value), callback); } function get(key, callback) { db.get(createKey(key), function(error, value) { - callback(error, value && JSON.parse(value)) - }) + callback(error, value && JSON.parse(value)); + }); } function del(key, callback) { - db.del(createKey(key), callback) + db.del(createKey(key), callback); } return { set, get, del - } + }; } -module.exports = createCache +module.exports = createCache; diff --git a/server/middleware/utils/createMutex.js b/server/middleware/utils/createMutex.js index 5a08e33..106276a 100644 --- a/server/middleware/utils/createMutex.js +++ b/server/middleware/utils/createMutex.js @@ -1,24 +1,24 @@ function createMutex(doWork) { - const mutex = {} + const mutex = {}; return function(key, payload, callback) { if (mutex[key]) { - mutex[key].push(callback) + mutex[key].push(callback); } else { mutex[key] = [ function() { - delete mutex[key] + delete mutex[key]; }, callback - ] + ]; doWork(payload, function(error, value) { mutex[key].forEach(callback => { - callback(error, value) - }) - }) + callback(error, value); + }); + }); } - } + }; } -module.exports = createMutex +module.exports = createMutex; diff --git a/server/middleware/utils/createSearch.js b/server/middleware/utils/createSearch.js index e97ae0a..b6c8114 100644 --- a/server/middleware/utils/createSearch.js +++ b/server/middleware/utils/createSearch.js @@ -1,17 +1,17 @@ function createSearch(query) { - const params = [] + const params = []; Object.keys(query).forEach(param => { if (query[param] === "") { - params.push(param) // Omit the trailing "=" from param= + params.push(param); // Omit the trailing "=" from param= } else { - params.push(`${param}=${encodeURIComponent(query[param])}`) + params.push(`${param}=${encodeURIComponent(query[param])}`); } - }) + }); - const search = params.join("&") + const search = params.join("&"); - return search ? `?${search}` : "" + return search ? `?${search}` : ""; } -module.exports = createSearch +module.exports = createSearch; diff --git a/server/middleware/utils/getPackage.js b/server/middleware/utils/getPackage.js index e881dd8..0328800 100644 --- a/server/middleware/utils/getPackage.js +++ b/server/middleware/utils/getPackage.js @@ -1,15 +1,15 @@ -require("isomorphic-fetch") -const fs = require("fs") -const path = require("path") -const tmpdir = require("os-tmpdir") -const gunzip = require("gunzip-maybe") -const mkdirp = require("mkdirp") -const tar = require("tar-fs") -const createMutex = require("./createMutex") +require("isomorphic-fetch"); +const fs = require("fs"); +const path = require("path"); +const tmpdir = require("os-tmpdir"); +const gunzip = require("gunzip-maybe"); +const mkdirp = require("mkdirp"); +const tar = require("tar-fs"); +const createMutex = require("./createMutex"); function createTempPath(name, version) { - const normalName = name.replace(/\//g, "-") - return path.join(tmpdir(), `unpkg-${normalName}-${version}`) + const normalName = name.replace(/\//g, "-"); + return path.join(tmpdir(), `unpkg-${normalName}-${version}`); } function stripNamePrefix(headers) { @@ -17,12 +17,12 @@ function stripNamePrefix(headers) { // so we shorten that to just "index.js" here. A few packages use a // prefix other than "package/". e.g. the firebase package uses the // "firebase_npm/" prefix. So we just strip the first dir name. - headers.name = headers.name.replace(/^[^/]+\//, "") - return headers + headers.name = headers.name.replace(/^[^/]+\//, ""); + return headers; } function ignoreSymlinks(file, headers) { - return headers.type === "link" + return headers.type === "link"; } function extractResponse(response, outputDir) { @@ -31,26 +31,26 @@ function extractResponse(response, outputDir) { readable: true, // All dirs/files should be readable. map: stripNamePrefix, ignore: ignoreSymlinks - }) + }); response.body .pipe(gunzip()) .pipe(extract) .on("finish", resolve) - .on("error", reject) - }) + .on("error", reject); + }); } function fetchAndExtract(tarballURL, outputDir) { - console.log(`info: Fetching ${tarballURL} and extracting to ${outputDir}`) + console.log(`info: Fetching ${tarballURL} and extracting to ${outputDir}`); return fetch(tarballURL).then(response => { - return extractResponse(response, outputDir) - }) + return extractResponse(response, outputDir); + }); } const fetchMutex = createMutex((payload, callback) => { - const { tarballURL, outputDir } = payload + const { tarballURL, outputDir } = payload; fs.access(outputDir, function(error) { if (error) { @@ -59,30 +59,30 @@ const fetchMutex = createMutex((payload, callback) => { // fetched a package for the first time. Carry on! mkdirp(outputDir, function(error) { if (error) { - callback(error) + callback(error); } else { fetchAndExtract(tarballURL, outputDir).then(() => { - callback() - }, callback) + callback(); + }, callback); } - }) + }); } else { - callback(error) + callback(error); } } else { // Best case: we already have this package cached on disk! - callback() + callback(); } - }) -}) + }); +}); function getPackage(packageConfig, callback) { - const tarballURL = packageConfig.dist.tarball - const outputDir = createTempPath(packageConfig.name, packageConfig.version) + const tarballURL = packageConfig.dist.tarball; + const outputDir = createTempPath(packageConfig.name, packageConfig.version); fetchMutex(tarballURL, { tarballURL, outputDir }, function(error) { - callback(error, outputDir) - }) + callback(error, outputDir); + }); } -module.exports = getPackage +module.exports = getPackage; diff --git a/server/middleware/utils/getPackageInfo.js b/server/middleware/utils/getPackageInfo.js index 602b099..7d2e177 100644 --- a/server/middleware/utils/getPackageInfo.js +++ b/server/middleware/utils/getPackageInfo.js @@ -1,33 +1,34 @@ -require("isomorphic-fetch") -const createCache = require("./createCache") -const createMutex = require("./createMutex") +require("isomorphic-fetch"); +const createCache = require("./createCache"); +const createMutex = require("./createMutex"); -const RegistryURL = process.env.NPM_REGISTRY_URL || "https://registry.npmjs.org" +const RegistryURL = + process.env.NPM_REGISTRY_URL || "https://registry.npmjs.org"; -const PackageInfoCache = createCache("packageInfo") +const PackageInfoCache = createCache("packageInfo"); function fetchPackageInfo(packageName) { - console.log(`info: Fetching package info for ${packageName}`) + console.log(`info: Fetching package info for ${packageName}`); - let encodedPackageName + let encodedPackageName; if (packageName.charAt(0) === "@") { - encodedPackageName = `@${encodeURIComponent(packageName.substring(1))}` + encodedPackageName = `@${encodeURIComponent(packageName.substring(1))}`; } else { - encodedPackageName = encodeURIComponent(packageName) + encodedPackageName = encodeURIComponent(packageName); } - const url = `${RegistryURL}/${encodedPackageName}` + const url = `${RegistryURL}/${encodedPackageName}`; return fetch(url, { headers: { Accept: "application/json" } }).then(res => { - return res.status === 404 ? null : res.json() - }) + return res.status === 404 ? null : res.json(); + }); } -const PackageNotFound = "PackageNotFound" +const PackageNotFound = "PackageNotFound"; // This mutex prevents multiple concurrent requests to // the registry for the same package info. @@ -40,32 +41,32 @@ const fetchMutex = createMutex((packageName, callback) => { // In the worst case, a brand new package's info will be // available within 5 minutes. PackageInfoCache.set(packageName, PackageNotFound, 300, function() { - callback(null, value) - }) + callback(null, value); + }); } else { // Cache valid package info for 1 minute. PackageInfoCache.set(packageName, value, 60, function() { - callback(null, value) - }) + callback(null, value); + }); } }, function(error) { // Do not cache errors. PackageInfoCache.del(packageName, function() { - callback(error) - }) + callback(error); + }); } - ) -}) + ); +}); function getPackageInfo(packageName, callback) { PackageInfoCache.get(packageName, function(error, value) { if (error || value != null) { - callback(error, value === PackageNotFound ? null : value) + callback(error, value === PackageNotFound ? null : value); } else { - fetchMutex(packageName, packageName, callback) + fetchMutex(packageName, packageName, callback); } - }) + }); } -module.exports = getPackageInfo +module.exports = getPackageInfo; diff --git a/server/middleware/utils/incrementCounter.js b/server/middleware/utils/incrementCounter.js index d81bec0..bf172fa 100644 --- a/server/middleware/utils/incrementCounter.js +++ b/server/middleware/utils/incrementCounter.js @@ -1,15 +1,15 @@ -const db = require("../../RedisClient") +const db = require("../../RedisClient"); function incrementCounter(counter, key, by) { return new Promise((resolve, reject) => { db.hincrby(counter, key, by, (error, value) => { if (error) { - reject(error) + reject(error); } else { - resolve(value) + resolve(value); } - }) - }) + }); + }); } -module.exports = incrementCounter +module.exports = incrementCounter; diff --git a/server/middleware/validatePackageURL.js b/server/middleware/validatePackageURL.js index e677cc2..8ed670e 100644 --- a/server/middleware/validatePackageURL.js +++ b/server/middleware/validatePackageURL.js @@ -1,25 +1,25 @@ -const parsePackageURL = require("../utils/parsePackageURL") +const parsePackageURL = require("../utils/parsePackageURL"); /** * Adds various properties to the request object to do with the * package/file being requested. */ function validatePackageURL(req, res, next) { - const url = parsePackageURL(req.url) + const url = parsePackageURL(req.url); if (url == null) { - return res.status(403).send({ error: `Invalid URL: ${req.url}` }) + return res.status(403).send({ error: `Invalid URL: ${req.url}` }); } - req.packageName = url.packageName - req.packageVersion = url.packageVersion - req.packageSpec = `${url.packageName}@${url.packageVersion}` - req.pathname = url.pathname - req.filename = url.filename - req.search = url.search - req.query = url.query + req.packageName = url.packageName; + req.packageVersion = url.packageVersion; + req.packageSpec = `${url.packageName}@${url.packageVersion}`; + req.pathname = url.pathname; + req.filename = url.filename; + req.search = url.search; + req.query = url.query; - next() + next(); } -module.exports = validatePackageURL +module.exports = validatePackageURL; diff --git a/server/utils/__tests__/getFileContentType-test.js b/server/utils/__tests__/getFileContentType-test.js index c10e0d6..b6d0473 100644 --- a/server/utils/__tests__/getFileContentType-test.js +++ b/server/utils/__tests__/getFileContentType-test.js @@ -1,35 +1,35 @@ -const getFileContentType = require("../getFileContentType") +const getFileContentType = require("../getFileContentType"); it("gets a content type of text/plain for LICENSE|README|CHANGES|AUTHORS|Makefile", () => { - expect(getFileContentType("AUTHORS")).toBe("text/plain") - expect(getFileContentType("CHANGES")).toBe("text/plain") - expect(getFileContentType("LICENSE")).toBe("text/plain") - expect(getFileContentType("Makefile")).toBe("text/plain") - expect(getFileContentType("PATENTS")).toBe("text/plain") - expect(getFileContentType("README")).toBe("text/plain") -}) + expect(getFileContentType("AUTHORS")).toBe("text/plain"); + expect(getFileContentType("CHANGES")).toBe("text/plain"); + expect(getFileContentType("LICENSE")).toBe("text/plain"); + expect(getFileContentType("Makefile")).toBe("text/plain"); + expect(getFileContentType("PATENTS")).toBe("text/plain"); + expect(getFileContentType("README")).toBe("text/plain"); +}); it("gets a content type of text/plain for .*rc files", () => { - expect(getFileContentType(".eslintrc")).toBe("text/plain") - expect(getFileContentType(".babelrc")).toBe("text/plain") - expect(getFileContentType(".anythingrc")).toBe("text/plain") -}) + expect(getFileContentType(".eslintrc")).toBe("text/plain"); + expect(getFileContentType(".babelrc")).toBe("text/plain"); + expect(getFileContentType(".anythingrc")).toBe("text/plain"); +}); it("gets a content type of text/plain for .git* files", () => { - expect(getFileContentType(".gitignore")).toBe("text/plain") - expect(getFileContentType(".gitanything")).toBe("text/plain") -}) + expect(getFileContentType(".gitignore")).toBe("text/plain"); + expect(getFileContentType(".gitanything")).toBe("text/plain"); +}); it("gets a content type of text/plain for .*ignore files", () => { - expect(getFileContentType(".eslintignore")).toBe("text/plain") - expect(getFileContentType(".anythingignore")).toBe("text/plain") -}) + expect(getFileContentType(".eslintignore")).toBe("text/plain"); + expect(getFileContentType(".anythingignore")).toBe("text/plain"); +}); it("gets a content type of text/plain for .ts files", () => { - expect(getFileContentType("app.ts")).toBe("text/plain") - expect(getFileContentType("app.d.ts")).toBe("text/plain") -}) + expect(getFileContentType("app.ts")).toBe("text/plain"); + expect(getFileContentType("app.d.ts")).toBe("text/plain"); +}); it("gets a content type of text/plain for .flow files", () => { - expect(getFileContentType("app.js.flow")).toBe("text/plain") -}) + expect(getFileContentType("app.js.flow")).toBe("text/plain"); +}); diff --git a/server/utils/__tests__/parsePackageURL-test.js b/server/utils/__tests__/parsePackageURL-test.js index 1c3a953..275b6d5 100644 --- a/server/utils/__tests__/parsePackageURL-test.js +++ b/server/utils/__tests__/parsePackageURL-test.js @@ -1,4 +1,4 @@ -const parsePackageURL = require("../parsePackageURL") +const parsePackageURL = require("../parsePackageURL"); describe("parsePackageURL", () => { it("parses plain packages", () => { @@ -9,8 +9,8 @@ describe("parsePackageURL", () => { packageName: "history", packageVersion: "1.0.0", filename: "/umd/history.min.js" - }) - }) + }); + }); it("parses plain packages with a hyphen in the name", () => { expect(parsePackageURL("/query-string@5.0.0/index.js")).toEqual({ @@ -20,8 +20,8 @@ describe("parsePackageURL", () => { packageName: "query-string", packageVersion: "5.0.0", filename: "/index.js" - }) - }) + }); + }); it("parses plain packages with no version specified", () => { expect(parsePackageURL("/query-string/index.js")).toEqual({ @@ -31,8 +31,8 @@ describe("parsePackageURL", () => { packageName: "query-string", packageVersion: "latest", filename: "/index.js" - }) - }) + }); + }); it("parses plain packages with version spec", () => { expect(parsePackageURL("/query-string@>=4.0.0/index.js")).toEqual({ @@ -42,8 +42,8 @@ describe("parsePackageURL", () => { packageName: "query-string", packageVersion: ">=4.0.0", filename: "/index.js" - }) - }) + }); + }); it("parses scoped packages", () => { expect(parsePackageURL("/@angular/router@4.3.3/src/index.d.ts")).toEqual({ @@ -53,8 +53,8 @@ describe("parsePackageURL", () => { packageName: "@angular/router", packageVersion: "4.3.3", filename: "/src/index.d.ts" - }) - }) + }); + }); it("parses package names with a period in them", () => { expect(parsePackageURL("/index.js")).toEqual({ @@ -64,8 +64,8 @@ describe("parsePackageURL", () => { packageName: "index.js", packageVersion: "latest", filename: "" - }) - }) + }); + }); it("parses valid query parameters", () => { expect(parsePackageURL("/history?main=browser")).toEqual({ @@ -75,11 +75,11 @@ describe("parsePackageURL", () => { packageName: "history", packageVersion: "latest", filename: "" - }) - }) + }); + }); it("returns null for invalid pathnames", () => { - expect(parsePackageURL("history")).toBe(null) - expect(parsePackageURL("/.invalid")).toBe(null) - }) -}) + expect(parsePackageURL("history")).toBe(null); + expect(parsePackageURL("/.invalid")).toBe(null); + }); +}); diff --git a/server/utils/__tests__/unpkgRewriteBabelPlugin-test.js b/server/utils/__tests__/unpkgRewriteBabelPlugin-test.js index 7f21220..ce43502 100644 --- a/server/utils/__tests__/unpkgRewriteBabelPlugin-test.js +++ b/server/utils/__tests__/unpkgRewriteBabelPlugin-test.js @@ -1,5 +1,5 @@ -const babel = require("babel-core") -const unpkgRewrite = require("../unpkgRewriteBabelPlugin") +const babel = require("babel-core"); +const unpkgRewrite = require("../unpkgRewriteBabelPlugin"); const testCases = [ { @@ -8,7 +8,8 @@ const testCases = [ }, { before: "import router from '@angular/router';", - after: "import router from 'https://unpkg.com/@angular/router@4.3.5?module';" + after: + "import router from 'https://unpkg.com/@angular/router@4.3.5?module';" }, { before: "import map from 'lodash.map';", @@ -54,23 +55,23 @@ const testCases = [ before: "export var message = 'hello';", after: "export var message = 'hello';" } -] +]; const dependencies = { react: "15.6.1", "@angular/router": "4.3.5", "lodash.map": "4.6.0", pn: "1.0.0" -} +}; describe("Rewriting imports/exports", () => { testCases.forEach(testCase => { it(`successfully rewrites "${testCase.before}"`, () => { const result = babel.transform(testCase.before, { plugins: [unpkgRewrite(dependencies)] - }) + }); - expect(result.code).toEqual(testCase.after) - }) - }) -}) + expect(result.code).toEqual(testCase.after); + }); + }); +}); diff --git a/server/utils/createPackageURL.js b/server/utils/createPackageURL.js index 6e5522c..d9eceb8 100644 --- a/server/utils/createPackageURL.js +++ b/server/utils/createPackageURL.js @@ -1,9 +1,9 @@ function createPackageURL(packageName, version, pathname, search) { - let url = `/${packageName}` - if (version != null) url += `@${version}` - if (pathname) url += pathname - if (search) url += search - return url + let url = `/${packageName}`; + if (version != null) url += `@${version}`; + if (pathname) url += pathname; + if (search) url += search; + return url; } -module.exports = createPackageURL +module.exports = createPackageURL; diff --git a/server/utils/getFileContentType.js b/server/utils/getFileContentType.js index 70bc66b..4304155 100644 --- a/server/utils/getFileContentType.js +++ b/server/utils/getFileContentType.js @@ -1,13 +1,22 @@ -const mime = require("mime") +const mime = require("mime"); mime.define({ - "text/plain": ["authors", "changes", "license", "makefile", "patents", "readme", "ts", "flow"] -}) + "text/plain": [ + "authors", + "changes", + "license", + "makefile", + "patents", + "readme", + "ts", + "flow" + ] +}); -const TextFiles = /\/?(\.[a-z]*rc|\.git[a-z]*|\.[a-z]*ignore)$/i +const TextFiles = /\/?(\.[a-z]*rc|\.git[a-z]*|\.[a-z]*ignore)$/i; function getFileContentType(file) { - return TextFiles.test(file) ? "text/plain" : mime.lookup(file) + return TextFiles.test(file) ? "text/plain" : mime.lookup(file); } -module.exports = getFileContentType +module.exports = getFileContentType; diff --git a/server/utils/getFileStats.js b/server/utils/getFileStats.js index be37b7c..245a582 100644 --- a/server/utils/getFileStats.js +++ b/server/utils/getFileStats.js @@ -1,15 +1,15 @@ -const fs = require("fs") +const fs = require("fs"); function getFileStats(file) { return new Promise((resolve, reject) => { fs.lstat(file, (error, stats) => { if (error) { - reject(error) + reject(error); } else { - resolve(stats) + resolve(stats); } - }) - }) + }); + }); } -module.exports = getFileStats +module.exports = getFileStats; diff --git a/server/utils/getMetadata.js b/server/utils/getMetadata.js index a3a9e99..679f914 100644 --- a/server/utils/getMetadata.js +++ b/server/utils/getMetadata.js @@ -1,44 +1,51 @@ -const fs = require("fs") -const path = require("path") -const SRIToolbox = require("sri-toolbox") -const getFileContentType = require("./getFileContentType") -const getFileStats = require("./getFileStats") -const getFileType = require("./getFileType") +const fs = require("fs"); +const path = require("path"); +const SRIToolbox = require("sri-toolbox"); +const getFileContentType = require("./getFileContentType"); +const getFileStats = require("./getFileStats"); +const getFileType = require("./getFileType"); function getEntries(dir, file, maximumDepth) { return new Promise((resolve, reject) => { fs.readdir(path.join(dir, file), function(error, files) { if (error) { - reject(error) + reject(error); } else { resolve( - Promise.all(files.map(f => getFileStats(path.join(dir, file, f)))).then(statsArray => { + Promise.all( + files.map(f => getFileStats(path.join(dir, file, f))) + ).then(statsArray => { return Promise.all( statsArray.map((stats, index) => - getMetadataRecursive(dir, path.join(file, files[index]), stats, maximumDepth - 1) + getMetadataRecursive( + dir, + path.join(file, files[index]), + stats, + maximumDepth - 1 + ) ) - ) + ); }) - ) + ); } - }) - }) + }); + }); } function formatTime(time) { - return new Date(time).toISOString() + return new Date(time).toISOString(); } function getIntegrity(file) { return new Promise((resolve, reject) => { fs.readFile(file, function(error, data) { if (error) { - reject(error) + reject(error); } else { - resolve(SRIToolbox.generate({ algorithms: ["sha384"] }, data)) + resolve(SRIToolbox.generate({ algorithms: ["sha384"] }, data)); } - }) - }) + }); + }); } function getMetadataRecursive(dir, file, stats, maximumDepth) { @@ -48,27 +55,31 @@ function getMetadataRecursive(dir, file, stats, maximumDepth) { path: file, size: stats.size, type: getFileType(stats) - } + }; if (stats.isFile()) { return getIntegrity(path.join(dir, file)).then(integrity => { - metadata.integrity = integrity - return metadata - }) + metadata.integrity = integrity; + return metadata; + }); } - if (!stats.isDirectory() || maximumDepth === 0) return Promise.resolve(metadata) + if (!stats.isDirectory() || maximumDepth === 0) + return Promise.resolve(metadata); return getEntries(dir, file, maximumDepth).then(files => { - metadata.files = files - return metadata - }) + metadata.files = files; + return metadata; + }); } function getMetadata(baseDir, path, stats, maximumDepth, callback) { - getMetadataRecursive(baseDir, path, stats, maximumDepth).then(function(metadata) { - callback(null, metadata) - }, callback) + getMetadataRecursive(baseDir, path, stats, maximumDepth).then(function( + metadata + ) { + callback(null, metadata); + }, + callback); } -module.exports = getMetadata +module.exports = getMetadata; diff --git a/server/utils/parsePackageURL.js b/server/utils/parsePackageURL.js index d9925f5..481a17d 100644 --- a/server/utils/parsePackageURL.js +++ b/server/utils/parsePackageURL.js @@ -1,35 +1,35 @@ -const url = require("url") -const validatePackageName = require("./validatePackageName") +const url = require("url"); +const validatePackageName = require("./validatePackageName"); -const URLFormat = /^\/((?:@[^/@]+\/)?[^/@]+)(?:@([^/]+))?(\/.*)?$/ +const URLFormat = /^\/((?:@[^/@]+\/)?[^/@]+)(?:@([^/]+))?(\/.*)?$/; function decodeParam(param) { if (param) { try { - return decodeURIComponent(param) + return decodeURIComponent(param); } catch (error) { // Ignore invalid params. } } - return "" + return ""; } function parsePackageURL(packageURL) { - const { pathname, search, query } = url.parse(packageURL, true) + const { pathname, search, query } = url.parse(packageURL, true); - const match = URLFormat.exec(pathname) + const match = URLFormat.exec(pathname); // Disallow invalid URL formats. - if (match == null) return null + if (match == null) return null; - const packageName = match[1] + const packageName = match[1]; // Disallow invalid npm package names. - if (!validatePackageName(packageName)) return null + if (!validatePackageName(packageName)) return null; - const packageVersion = decodeParam(match[2]) || "latest" - const filename = decodeParam(match[3]) + const packageVersion = decodeParam(match[2]) || "latest"; + const filename = decodeParam(match[3]); return { // If the URL is /@scope/name@version/file.js?main=browser: @@ -39,7 +39,7 @@ function parsePackageURL(packageURL) { packageName, // @scope/name packageVersion, // version filename // /file.js - } + }; } -module.exports = parsePackageURL +module.exports = parsePackageURL; diff --git a/server/utils/readCSS.js b/server/utils/readCSS.js index eab7f3c..422bf8e 100644 --- a/server/utils/readCSS.js +++ b/server/utils/readCSS.js @@ -1,9 +1,9 @@ -const fs = require("fs") -const path = require("path") -const csso = require("csso") +const fs = require("fs"); +const path = require("path"); +const csso = require("csso"); function readCSS(...args) { - return csso.minify(fs.readFileSync(path.resolve(...args), "utf8")).css + return csso.minify(fs.readFileSync(path.resolve(...args), "utf8")).css; } -module.exports = readCSS +module.exports = readCSS; diff --git a/server/utils/renderPage.js b/server/utils/renderPage.js index df7a894..c62a827 100644 --- a/server/utils/renderPage.js +++ b/server/utils/renderPage.js @@ -1,11 +1,13 @@ -const React = require("react") -const ReactDOMServer = require("react-dom/server") +const React = require("react"); +const ReactDOMServer = require("react-dom/server"); -const doctype = "" +const doctype = ""; function renderPage(page, props) { - const html = ReactDOMServer.renderToStaticMarkup(React.createElement(page, props)) - return doctype + html + const html = ReactDOMServer.renderToStaticMarkup( + React.createElement(page, props) + ); + return doctype + html; } -module.exports = renderPage +module.exports = renderPage; diff --git a/server/utils/unpkgRewriteBabelPlugin.js b/server/utils/unpkgRewriteBabelPlugin.js index 5059c61..9f8307b 100644 --- a/server/utils/unpkgRewriteBabelPlugin.js +++ b/server/utils/unpkgRewriteBabelPlugin.js @@ -1,7 +1,7 @@ -const URL = require("whatwg-url") -const warning = require("warning") +const URL = require("whatwg-url"); +const warning = require("warning"); -const BareIdentifierFormat = /^((?:@[^\/]+\/)?[^\/]+)(\/.*)?$/ +const BareIdentifierFormat = /^((?:@[^\/]+\/)?[^\/]+)(\/.*)?$/; function unpkgRewriteBabelPlugin(dependencies = {}) { return { @@ -9,36 +9,36 @@ function unpkgRewriteBabelPlugin(dependencies = {}) { visitor: { "ImportDeclaration|ExportNamedDeclaration|ExportAllDeclaration"(path) { - if (!path.node.source) return // probably a variable declaration + if (!path.node.source) return; // probably a variable declaration if ( URL.parseURL(path.node.source.value) != null || path.node.source.value.substr(0, 2) === "//" ) - return // valid URL or URL w/o protocol, leave it alone + return; // valid URL or URL w/o protocol, leave it alone if ([".", "/"].indexOf(path.node.source.value.charAt(0)) >= 0) { // local path - path.node.source.value = `${path.node.source.value}?module` + path.node.source.value = `${path.node.source.value}?module`; } else { // "bare" identifier - const match = BareIdentifierFormat.exec(path.node.source.value) - const packageName = match[1] - const file = match[2] || "" + const match = BareIdentifierFormat.exec(path.node.source.value); + const packageName = match[1]; + const file = match[2] || ""; warning( dependencies[packageName], 'Missing version info for package "%s" in dependencies; falling back to "latest"', packageName - ) + ); - const version = dependencies[packageName] || "latest" + const version = dependencies[packageName] || "latest"; - path.node.source.value = `https://unpkg.com/${packageName}@${version}${file}?module` + path.node.source.value = `https://unpkg.com/${packageName}@${version}${file}?module`; } } } - } + }; } -module.exports = unpkgRewriteBabelPlugin +module.exports = unpkgRewriteBabelPlugin; diff --git a/server/utils/validatePackageName.js b/server/utils/validatePackageName.js index 5c8d28e..29ed28c 100644 --- a/server/utils/validatePackageName.js +++ b/server/utils/validatePackageName.js @@ -1,7 +1,7 @@ -const validateNpmPackageName = require("validate-npm-package-name") +const validateNpmPackageName = require("validate-npm-package-name"); function validatePackageName(packageName) { - return validateNpmPackageName(packageName).errors == null + return validateNpmPackageName(packageName).errors == null; } -module.exports = validatePackageName +module.exports = validatePackageName;